@cachly-dev/mcp-server 0.10.129 → 0.10.131

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 (223) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +1 -7
  3. package/dist/packages/telegram-notify/client.js +2 -75
  4. package/dist/src/ambient-cli.js +7 -253
  5. package/dist/src/ambient-deps.js +1 -55
  6. package/dist/src/ambient-hooks.js +5 -278
  7. package/dist/src/ambient-ledger.js +6 -95
  8. package/dist/src/ambient-memory.js +1 -67
  9. package/dist/src/ambient-recall.js +1 -221
  10. package/dist/src/aussetzer.js +1 -225
  11. package/dist/src/auth.js +9 -140
  12. package/dist/src/autor-abzeichen.js +1 -56
  13. package/dist/src/autor.js +1 -136
  14. package/dist/src/bauabdruck.js +1 -93
  15. package/dist/src/bedeutung.js +2 -323
  16. package/dist/src/bench/cachly-bench.js +4 -249
  17. package/dist/src/bench/external-corpus.js +4 -135
  18. package/dist/src/bench/fixtures.js +1 -225
  19. package/dist/src/brain-export.js +16 -119
  20. package/dist/src/brain-watch-hook.js +8 -86
  21. package/dist/src/cachly-url.js +1 -21
  22. package/dist/src/ckg.js +1 -108
  23. package/dist/src/cls-hook.js +8 -94
  24. package/dist/src/confidence.js +1 -50
  25. package/dist/src/credentials.js +1 -85
  26. package/dist/src/deutsch.js +1 -223
  27. package/dist/src/doctor.js +2 -248
  28. package/dist/src/editor.js +1 -63
  29. package/dist/src/eingaenge-nachruesten.js +1 -290
  30. package/dist/src/eingaenge.js +2 -260
  31. package/dist/src/embeddings.js +13 -223
  32. package/dist/src/feldreparatur.js +3 -113
  33. package/dist/src/first-contact.js +4 -108
  34. package/dist/src/first-recall-proof.js +4 -41
  35. package/dist/src/funnel-milestones.js +2 -48
  36. package/dist/src/geliefert.js +1 -114
  37. package/dist/src/handlers/advanced.js +38 -537
  38. package/dist/src/handlers/brain.js +69 -4397
  39. package/dist/src/handlers/cache.js +59 -638
  40. package/dist/src/handlers/context.js +17 -182
  41. package/dist/src/handlers/fedbrain.js +40 -1436
  42. package/dist/src/handlers/instances.js +16 -201
  43. package/dist/src/handlers/roadmap.js +13 -175
  44. package/dist/src/handlers/share.js +19 -530
  45. package/dist/src/handlers/syndicate.js +38 -904
  46. package/dist/src/handlers/team.js +89 -1865
  47. package/dist/src/handlers/viz.js +3 -148
  48. package/dist/src/index.js +321 -4281
  49. package/dist/src/interferenz.js +1 -141
  50. package/dist/src/kostprobe.js +7 -125
  51. package/dist/src/lesson-preview.js +2 -133
  52. package/dist/src/namespace.js +1 -128
  53. package/dist/src/notifier.js +1 -6
  54. package/dist/src/rangfolge-stellschrauben.js +1 -0
  55. package/dist/src/rangfolge.js +1 -249
  56. package/dist/src/raumkorrektur.js +1 -197
  57. package/dist/src/recall-tiefe.js +1 -42
  58. package/dist/src/rerank.js +1 -154
  59. package/dist/src/resonanz.js +1 -140
  60. package/dist/src/search.js +1 -2104
  61. package/dist/src/seltenheitsbestand.js +1 -92
  62. package/dist/src/speicherlage.js +1 -84
  63. package/dist/src/spuren.js +1 -150
  64. package/dist/src/starter-corpus.js +1 -165
  65. package/dist/src/telemetry-types.js +0 -13
  66. package/dist/src/tools.js +10 -2465
  67. package/dist/src/toolspecs.js +1 -121
  68. package/dist/src/upgrade-nudge.js +1 -29
  69. package/dist/src/utils.js +1 -89
  70. package/dist/src/versuch.js +1 -264
  71. package/dist/src/vorspann.js +1 -146
  72. package/dist/src/wartung/vektoren-verdichten.js +1 -200
  73. package/dist/src/werkzeug-nutzung.js +1 -72
  74. package/dist/src/wertbeitrag.js +1 -203
  75. package/dist/src/wirkungsspur.js +1 -0
  76. package/glama.json +1 -1
  77. package/package.json +8 -7
  78. package/server.json +3 -3
  79. package/dist/index.d.ts +0 -1
  80. package/dist/index.js.map +0 -1
  81. package/dist/packages/telegram-notify/client.d.ts +0 -19
  82. package/dist/packages/telegram-notify/client.js.map +0 -1
  83. package/dist/src/ambient-cli.d.ts +0 -101
  84. package/dist/src/ambient-cli.js.map +0 -1
  85. package/dist/src/ambient-deps.d.ts +0 -26
  86. package/dist/src/ambient-deps.js.map +0 -1
  87. package/dist/src/ambient-hooks.d.ts +0 -137
  88. package/dist/src/ambient-hooks.js.map +0 -1
  89. package/dist/src/ambient-ledger.d.ts +0 -19
  90. package/dist/src/ambient-ledger.js.map +0 -1
  91. package/dist/src/ambient-memory.d.ts +0 -12
  92. package/dist/src/ambient-memory.js.map +0 -1
  93. package/dist/src/ambient-recall.d.ts +0 -127
  94. package/dist/src/ambient-recall.js.map +0 -1
  95. package/dist/src/aussetzer.d.ts +0 -156
  96. package/dist/src/aussetzer.js.map +0 -1
  97. package/dist/src/auth.d.ts +0 -43
  98. package/dist/src/auth.js.map +0 -1
  99. package/dist/src/autor-abzeichen.d.ts +0 -41
  100. package/dist/src/autor-abzeichen.js.map +0 -1
  101. package/dist/src/autor.d.ts +0 -101
  102. package/dist/src/autor.js.map +0 -1
  103. package/dist/src/bauabdruck-daten.generiert.json +0 -5
  104. package/dist/src/bauabdruck.d.ts +0 -38
  105. package/dist/src/bauabdruck.js.map +0 -1
  106. package/dist/src/bedeutung.d.ts +0 -168
  107. package/dist/src/bedeutung.js.map +0 -1
  108. package/dist/src/bench/cachly-bench.d.ts +0 -61
  109. package/dist/src/bench/cachly-bench.js.map +0 -1
  110. package/dist/src/bench/external-corpus.d.ts +0 -44
  111. package/dist/src/bench/external-corpus.js.map +0 -1
  112. package/dist/src/bench/fixtures.d.ts +0 -37
  113. package/dist/src/bench/fixtures.js.map +0 -1
  114. package/dist/src/brain-export.d.ts +0 -64
  115. package/dist/src/brain-export.js.map +0 -1
  116. package/dist/src/brain-watch-hook.d.ts +0 -21
  117. package/dist/src/brain-watch-hook.js.map +0 -1
  118. package/dist/src/cachly-url.d.ts +0 -68
  119. package/dist/src/cachly-url.js.map +0 -1
  120. package/dist/src/ckg.d.ts +0 -54
  121. package/dist/src/ckg.js.map +0 -1
  122. package/dist/src/cls-hook.d.ts +0 -15
  123. package/dist/src/cls-hook.js.map +0 -1
  124. package/dist/src/confidence.d.ts +0 -19
  125. package/dist/src/confidence.js.map +0 -1
  126. package/dist/src/credentials.d.ts +0 -37
  127. package/dist/src/credentials.js.map +0 -1
  128. package/dist/src/deutsch.d.ts +0 -98
  129. package/dist/src/deutsch.js.map +0 -1
  130. package/dist/src/doctor.d.ts +0 -47
  131. package/dist/src/doctor.js.map +0 -1
  132. package/dist/src/editor.d.ts +0 -9
  133. package/dist/src/editor.js.map +0 -1
  134. package/dist/src/eingaenge-nachruesten.d.ts +0 -41
  135. package/dist/src/eingaenge-nachruesten.js.map +0 -1
  136. package/dist/src/eingaenge.d.ts +0 -159
  137. package/dist/src/eingaenge.js.map +0 -1
  138. package/dist/src/embeddings.d.ts +0 -48
  139. package/dist/src/embeddings.js.map +0 -1
  140. package/dist/src/feldreparatur.d.ts +0 -58
  141. package/dist/src/feldreparatur.js.map +0 -1
  142. package/dist/src/first-contact.d.ts +0 -56
  143. package/dist/src/first-contact.js.map +0 -1
  144. package/dist/src/first-recall-proof.d.ts +0 -19
  145. package/dist/src/first-recall-proof.js.map +0 -1
  146. package/dist/src/funnel-milestones.d.ts +0 -5
  147. package/dist/src/funnel-milestones.js.map +0 -1
  148. package/dist/src/geliefert.d.ts +0 -63
  149. package/dist/src/geliefert.js.map +0 -1
  150. package/dist/src/handlers/advanced.d.ts +0 -6
  151. package/dist/src/handlers/advanced.js.map +0 -1
  152. package/dist/src/handlers/brain.d.ts +0 -41
  153. package/dist/src/handlers/brain.js.map +0 -1
  154. package/dist/src/handlers/cache.d.ts +0 -6
  155. package/dist/src/handlers/cache.js.map +0 -1
  156. package/dist/src/handlers/context.d.ts +0 -6
  157. package/dist/src/handlers/context.js.map +0 -1
  158. package/dist/src/handlers/fedbrain.d.ts +0 -18
  159. package/dist/src/handlers/fedbrain.js.map +0 -1
  160. package/dist/src/handlers/instances.d.ts +0 -33
  161. package/dist/src/handlers/instances.js.map +0 -1
  162. package/dist/src/handlers/roadmap.d.ts +0 -6
  163. package/dist/src/handlers/roadmap.js.map +0 -1
  164. package/dist/src/handlers/share.d.ts +0 -8
  165. package/dist/src/handlers/share.js.map +0 -1
  166. package/dist/src/handlers/syndicate.d.ts +0 -6
  167. package/dist/src/handlers/syndicate.js.map +0 -1
  168. package/dist/src/handlers/team.d.ts +0 -45
  169. package/dist/src/handlers/team.js.map +0 -1
  170. package/dist/src/handlers/viz.d.ts +0 -6
  171. package/dist/src/handlers/viz.js.map +0 -1
  172. package/dist/src/index.d.ts +0 -93
  173. package/dist/src/index.js.map +0 -1
  174. package/dist/src/interferenz.d.ts +0 -111
  175. package/dist/src/interferenz.js.map +0 -1
  176. package/dist/src/kostprobe.d.ts +0 -25
  177. package/dist/src/kostprobe.js.map +0 -1
  178. package/dist/src/lesson-preview.d.ts +0 -81
  179. package/dist/src/lesson-preview.js.map +0 -1
  180. package/dist/src/namespace.d.ts +0 -20
  181. package/dist/src/namespace.js.map +0 -1
  182. package/dist/src/notifier.d.ts +0 -5
  183. package/dist/src/notifier.js.map +0 -1
  184. package/dist/src/rangfolge.d.ts +0 -205
  185. package/dist/src/rangfolge.js.map +0 -1
  186. package/dist/src/raumkorrektur.d.ts +0 -100
  187. package/dist/src/raumkorrektur.js.map +0 -1
  188. package/dist/src/recall-tiefe.d.ts +0 -48
  189. package/dist/src/recall-tiefe.js.map +0 -1
  190. package/dist/src/rerank.d.ts +0 -48
  191. package/dist/src/rerank.js.map +0 -1
  192. package/dist/src/resonanz.d.ts +0 -85
  193. package/dist/src/resonanz.js.map +0 -1
  194. package/dist/src/search.d.ts +0 -153
  195. package/dist/src/search.js.map +0 -1
  196. package/dist/src/seltenheitsbestand.d.ts +0 -53
  197. package/dist/src/seltenheitsbestand.js.map +0 -1
  198. package/dist/src/speicherlage.d.ts +0 -51
  199. package/dist/src/speicherlage.js.map +0 -1
  200. package/dist/src/spuren.d.ts +0 -97
  201. package/dist/src/spuren.js.map +0 -1
  202. package/dist/src/starter-corpus.d.ts +0 -28
  203. package/dist/src/starter-corpus.js.map +0 -1
  204. package/dist/src/telemetry-types.d.ts +0 -87
  205. package/dist/src/telemetry-types.js.map +0 -1
  206. package/dist/src/tools.d.ts +0 -2933
  207. package/dist/src/tools.js.map +0 -1
  208. package/dist/src/toolspecs.d.ts +0 -43
  209. package/dist/src/toolspecs.js.map +0 -1
  210. package/dist/src/upgrade-nudge.d.ts +0 -15
  211. package/dist/src/upgrade-nudge.js.map +0 -1
  212. package/dist/src/utils.d.ts +0 -34
  213. package/dist/src/utils.js.map +0 -1
  214. package/dist/src/versuch.d.ts +0 -187
  215. package/dist/src/versuch.js.map +0 -1
  216. package/dist/src/vorspann.d.ts +0 -73
  217. package/dist/src/vorspann.js.map +0 -1
  218. package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
  219. package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
  220. package/dist/src/werkzeug-nutzung.d.ts +0 -62
  221. package/dist/src/werkzeug-nutzung.js.map +0 -1
  222. package/dist/src/wertbeitrag.d.ts +0 -148
  223. package/dist/src/wertbeitrag.js.map +0 -1
@@ -1,249 +1,4 @@
1
- /**
2
- * ⚠ DIESER MESSSTAND MISST FALSCH HERUM. Er heisst seit dem 20.08.2026
3
- * `npm run bench:spielzeug` und nicht mehr `npm run bench`.
4
- *
5
- * Belegt an diesem Tag: mit 17 Lektionen und 13 Fragen sortiert er die beiden
6
- * Rangfolge-Formeln GENAU VERKEHRT HERUM ein — 92,3 gegen 69,2 Prozent fuer die
7
- * Fassung, die auf 498 echten Lektionen 15 statt 30 Prozent erreichte.
8
- *
9
- * Er bleibt hier, weil seine gegnerischen Beispielpaare (zwei fast gleiche
10
- * CSRF-Lektionen, von denen nur eine stimmt) etwas pruefen, was im grossen
11
- * Korpus untergeht. Als Beleg fuer Rangfolge-Qualitaet taugt er NICHT.
12
- *
13
- * Der Messstand, der zaehlt: src/bench/echter-korpus.ts — 499 echte Lektionen,
14
- * 100 von Hand geschriebene Fragen, mit Untergrenzen.
15
- */
16
- /**
17
- * Cachly-Bench — the moat proof.
18
- *
19
- * Measures recall quality on a fixed, labeled corpus and compares two rankers
20
- * over the *same* BM25 candidate set, so the comparison isolates ranking quality:
21
- *
22
- * baseline — raw BM25+ keyword ranking (what a text-only / flat-file memory does)
23
- * cachly — BM25+ followed by quality-aware reranking (rerank.ts)
24
- *
25
- * It reports standard IR metrics (Precision@k, Recall@k, MRR, nDCG@k) and the
26
- * relative lift. The corpus (fixtures.ts) is fair: gold answers are the lessons
27
- * that actually solve each problem, while text-similar failed attempts act as
28
- * distractors — exactly where ranking by quality should pull ahead.
29
- *
30
- * Run: npm run bench (pretty report)
31
- * npm run bench -- --json (machine-readable)
32
- */
33
- import { EventEmitter } from 'node:events';
34
- import { keywordSearch } from '../search.js';
35
- import { rerankByQuality } from '../rerank.js';
36
- import { BENCH_LESSONS, BENCH_QUERIES } from './fixtures.js';
37
- const LESSON_PREFIX = 'cachly:lesson:best:';
38
- // ── Minimal in-memory Redis (only what keywordSearch needs) ──────────────────
39
- class MiniRedis {
40
- store = new Map();
41
- set(key, value) { this.store.set(key, value); }
42
- async get(key) { return this.store.get(key) ?? null; }
43
- scanStream(opts) {
44
- const emitter = new EventEmitter();
45
- const pattern = '^' + opts.match.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$';
46
- const regex = new RegExp(pattern);
47
- const matches = [...this.store.keys()].filter(k => regex.test(k));
48
- setImmediate(() => { emitter.emit('data', matches); emitter.emit('end'); });
49
- return emitter;
50
- }
51
- pipeline() {
52
- const cmds = [];
53
- const store = this.store;
54
- return {
55
- get(key) { cmds.push(key); return this; },
56
- async exec() {
57
- return cmds.map(k => [null, store.get(k) ?? null]);
58
- },
59
- };
60
- }
61
- }
62
- function indexCorpus(lessons) {
63
- const redis = new MiniRedis();
64
- for (const l of lessons) {
65
- redis.set(`${LESSON_PREFIX}${l.topic}`, JSON.stringify(l));
66
- }
67
- return redis;
68
- }
69
- // ── IR metrics ──────────────────────────────────────────────────────────────────
70
- function precisionAtK(ranked, relevant, k) {
71
- const top = ranked.slice(0, k);
72
- if (top.length === 0)
73
- return 0;
74
- const hits = top.filter(t => relevant.has(t)).length;
75
- return hits / Math.min(k, top.length);
76
- }
77
- function recallAtK(ranked, relevant, k) {
78
- if (relevant.size === 0)
79
- return 0;
80
- const top = ranked.slice(0, k);
81
- const hits = top.filter(t => relevant.has(t)).length;
82
- return hits / relevant.size;
83
- }
84
- function reciprocalRank(ranked, relevant) {
85
- for (let i = 0; i < ranked.length; i++) {
86
- if (relevant.has(ranked[i]))
87
- return 1 / (i + 1);
88
- }
89
- return 0;
90
- }
91
- function ndcgAtK(ranked, relevant, k) {
92
- let dcg = 0;
93
- for (let i = 0; i < Math.min(k, ranked.length); i++) {
94
- if (relevant.has(ranked[i]))
95
- dcg += 1 / Math.log2(i + 2);
96
- }
97
- let idcg = 0;
98
- for (let i = 0; i < Math.min(k, relevant.size); i++) {
99
- idcg += 1 / Math.log2(i + 2);
100
- }
101
- return idcg === 0 ? 0 : dcg / idcg;
102
- }
103
- /**
104
- * Simulates how a flat-file memory (e.g. an LLM reading a `/memories` directory)
105
- * actually surfaces knowledge: it has NO ranking engine. Practical retrieval is
106
- * naive lexical overlap — count how many distinct query terms appear in the file's
107
- * text — with NO inverse-document-frequency, NO length normalization, and crucially
108
- * NO quality signal (a proven fix and a failed attempt are just text). Ties break by
109
- * recency, mimicking "most recently edited note first". This is a fair, and honestly
110
- * weaker, stand-in for "Claude reads its own memory files".
111
- */
112
- function flatFileRank(lessons, query) {
113
- const qTerms = new Set(query.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/).filter(t => t.length >= 3));
114
- const scored = lessons.map(l => {
115
- const blob = [l.topic, l.what_worked, l.what_failed ?? '', l.context ?? '', (l.tags ?? []).join(' ')]
116
- .join(' ').toLowerCase();
117
- let overlap = 0;
118
- for (const t of qTerms)
119
- if (blob.includes(t))
120
- overlap++;
121
- const recency = l.ts ? new Date(l.ts).getTime() : 0;
122
- return { topic: l.topic, overlap, recency };
123
- });
124
- return scored
125
- .filter(s => s.overlap > 0)
126
- .sort((a, b) => (b.overlap - a.overlap) || (b.recency - a.recency))
127
- .map(s => s.topic);
128
- }
129
- const matchTopic = (m) => m.key.replace(LESSON_PREFIX, '');
130
- function mean(xs) {
131
- return xs.length === 0 ? 0 : xs.reduce((a, b) => a + b, 0) / xs.length;
132
- }
133
- function aggregate(rows) {
134
- return {
135
- precisionAt1: mean(rows.map(r => r.precisionAt1)),
136
- precisionAt3: mean(rows.map(r => r.precisionAt3)),
137
- recallAt3: mean(rows.map(r => r.recallAt3)),
138
- mrr: mean(rows.map(r => r.mrr)),
139
- ndcgAt5: mean(rows.map(r => r.ndcgAt5)),
140
- };
141
- }
142
- function scoreRanking(ranked, relevant) {
143
- return {
144
- precisionAt1: precisionAtK(ranked, relevant, 1),
145
- precisionAt3: precisionAtK(ranked, relevant, 3),
146
- recallAt3: recallAtK(ranked, relevant, 3),
147
- mrr: reciprocalRank(ranked, relevant),
148
- ndcgAt5: ndcgAtK(ranked, relevant, 5),
149
- };
150
- }
151
- /**
152
- * Run the three-ranker benchmark over any labeled corpus + query set. Pure-ish:
153
- * no network, no real Redis. Shared by the built-in fixture bench and the external
154
- * labeled-corpus bench (external-corpus.ts) so both prove lift the same way.
155
- */
156
- export async function runBenchmarkOn(lessons, queries) {
157
- const redis = indexCorpus(lessons);
158
- const flatfileRows = [];
159
- const baselineRows = [];
160
- const cachlyRows = [];
161
- for (const q of queries) {
162
- const relevant = new Set(q.relevant);
163
- // Baseline: BM25 top-10 only (no reranking), mirrors what a plain keyword search gives.
164
- const baselineMatches = await keywordSearch(redis, [`${LESSON_PREFIX}*`], q.query, 10);
165
- const baselineRanked = baselineMatches.map(matchTopic);
166
- // Cachly: wider candidate pool (top-25) → quality reranker selects the best 3–5.
167
- // Wider pool lets the reranker rescue relevant items that BM25 pushed to ranks 11–25.
168
- const cachlyMatches = await keywordSearch(redis, [`${LESSON_PREFIX}*`], q.query, 25);
169
- const cachlyRanked = rerankByQuality(cachlyMatches).map(matchTopic);
170
- // Flat-file memory sees the whole corpus but has no ranking engine.
171
- const flatfileRanked = flatFileRank(lessons, q.query);
172
- flatfileRows.push(scoreRanking(flatfileRanked, relevant));
173
- baselineRows.push(scoreRanking(baselineRanked, relevant));
174
- cachlyRows.push(scoreRanking(cachlyRanked, relevant));
175
- }
176
- const flatfile = aggregate(flatfileRows);
177
- const baseline = aggregate(baselineRows);
178
- const cachly = aggregate(cachlyRows);
179
- const liftOf = (b, c) => (b === 0 ? (c > 0 ? 1 : 0) : (c - b) / b);
180
- const liftBetween = (from) => ({
181
- precisionAt1: liftOf(from.precisionAt1, cachly.precisionAt1),
182
- precisionAt3: liftOf(from.precisionAt3, cachly.precisionAt3),
183
- recallAt3: liftOf(from.recallAt3, cachly.recallAt3),
184
- mrr: liftOf(from.mrr, cachly.mrr),
185
- ndcgAt5: liftOf(from.ndcgAt5, cachly.ndcgAt5),
186
- });
187
- return {
188
- flatfile, baseline, cachly,
189
- lift: liftBetween(baseline),
190
- liftVsFlatfile: liftBetween(flatfile),
191
- queryCount: queries.length,
192
- corpusSize: lessons.length,
193
- };
194
- }
195
- /** Run the benchmark over the built-in fixture corpus. */
196
- export async function runBenchmark() {
197
- return runBenchmarkOn(BENCH_LESSONS, BENCH_QUERIES);
198
- }
199
- // ── Reporting ───────────────────────────────────────────────────────────────────────────
200
- const pct = (x) => `${(x * 100).toFixed(1)}%`;
201
- const signedPct = (x) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}%`;
202
- export function formatReport(r) {
203
- const row = (label, f, b, c, lf) => ` ${label.padEnd(12)} ${pct(f).padStart(8)} ${pct(b).padStart(8)} ${pct(c).padStart(8)} ${signedPct(lf).padStart(8)}`;
204
- return [
205
- '',
206
- '🧠 Cachly-Bench — recall quality head-to-head',
207
- '──────────────────────────────────────────────────────────────────────────',
208
- ` corpus: ${r.corpusSize} lessons · queries: ${r.queryCount}`,
209
- ' flat-file = naive term-overlap, no quality signal (≈ LLM reading memory files)',
210
- ' baseline = raw BM25+ keyword ranking',
211
- ' cachly = BM25+ followed by quality-aware reranking',
212
- '',
213
- ` ${'metric'.padEnd(12)} ${'flatfile'.padStart(8)} ${'baseline'.padStart(8)} ${'cachly'.padStart(8)} ${'vs flat'.padStart(8)}`,
214
- row('Precision@1', r.flatfile.precisionAt1, r.baseline.precisionAt1, r.cachly.precisionAt1, r.liftVsFlatfile.precisionAt1),
215
- row('Precision@3', r.flatfile.precisionAt3, r.baseline.precisionAt3, r.cachly.precisionAt3, r.liftVsFlatfile.precisionAt3),
216
- row('Recall@3', r.flatfile.recallAt3, r.baseline.recallAt3, r.cachly.recallAt3, r.liftVsFlatfile.recallAt3),
217
- row('MRR', r.flatfile.mrr, r.baseline.mrr, r.cachly.mrr, r.liftVsFlatfile.mrr),
218
- row('nDCG@5', r.flatfile.ndcgAt5, r.baseline.ndcgAt5, r.cachly.ndcgAt5, r.liftVsFlatfile.ndcgAt5),
219
- '──────────────────────────────────────────────────────────────────────────',
220
- ` vs BM25 baseline : MRR ${signedPct(r.lift.mrr)} · Precision@1 ${signedPct(r.lift.precisionAt1)}`,
221
- ` vs flat-file mem : MRR ${signedPct(r.liftVsFlatfile.mrr)} · Precision@1 ${signedPct(r.liftVsFlatfile.precisionAt1)}`,
222
- '',
223
- ].join('\n');
224
- }
225
- // ── CLI entry ───────────────────────────────────────────────────────────────────────────
226
- const isMain = (() => {
227
- try {
228
- return import.meta.url === `file://${process.argv[1]}`
229
- || process.argv[1]?.endsWith('cachly-bench.ts')
230
- || process.argv[1]?.endsWith('cachly-bench.js');
231
- }
232
- catch {
233
- return false;
234
- }
235
- })();
236
- if (isMain) {
237
- runBenchmark().then(result => {
238
- if (process.argv.includes('--json')) {
239
- process.stdout.write(JSON.stringify(result, null, 2) + '\n');
240
- }
241
- else {
242
- process.stdout.write(formatReport(result));
243
- }
244
- }).catch(err => {
245
- process.stderr.write(`bench failed: ${err instanceof Error ? err.stack : String(err)}\n`);
246
- process.exit(1);
247
- });
248
- }
249
- //# sourceMappingURL=cachly-bench.js.map
1
+ import{EventEmitter as F}from"node:events";import{keywordSearch as $}from"../search.js";import{rerankByQuality as q}from"../rerank.js";import{BENCH_LESSONS as B,BENCH_QUERIES as k}from"./fixtures.js";const m="cachly:lesson:best:";class v{store=new Map;set(e,n){this.store.set(e,n)}async get(e){return this.store.get(e)??null}scanStream(e){const n=new F,s="^"+e.match.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")+"$",i=new RegExp(s),c=[...this.store.keys()].filter(a=>i.test(a));return setImmediate(()=>{n.emit("data",c),n.emit("end")}),n}pipeline(){const e=[],n=this.store;return{get(s){return e.push(s),this},async exec(){return e.map(s=>[null,n.get(s)??null])}}}}function C(t){const e=new v;for(const n of t)e.set(`${m}${n.topic}`,JSON.stringify(n));return e}function w(t,e,n){const s=t.slice(0,n);return s.length===0?0:s.filter(c=>e.has(c)).length/Math.min(n,s.length)}function x(t,e,n){return e.size===0?0:t.slice(0,n).filter(c=>e.has(c)).length/e.size}function V(t,e){for(let n=0;n<t.length;n++)if(e.has(t[n]))return 1/(n+1);return 0}function z(t,e,n){let s=0;for(let c=0;c<Math.min(n,t.length);c++)e.has(t[c])&&(s+=1/Math.log2(c+2));let i=0;for(let c=0;c<Math.min(n,e.size);c++)i+=1/Math.log2(c+2);return i===0?0:s/i}function L(t,e){const n=new Set(e.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(i=>i.length>=3));return t.map(i=>{const c=[i.topic,i.what_worked,i.what_failed??"",i.context??"",(i.tags??[]).join(" ")].join(" ").toLowerCase();let a=0;for(const o of n)c.includes(o)&&a++;const u=i.ts?new Date(i.ts).getTime():0;return{topic:i.topic,overlap:a,recency:u}}).filter(i=>i.overlap>0).sort((i,c)=>c.overlap-i.overlap||c.recency-i.recency).map(i=>i.topic)}const S=t=>t.key.replace(m,"");function f(t){return t.length===0?0:t.reduce((e,n)=>e+n,0)/t.length}function g(t){return{precisionAt1:f(t.map(e=>e.precisionAt1)),precisionAt3:f(t.map(e=>e.precisionAt3)),recallAt3:f(t.map(e=>e.recallAt3)),mrr:f(t.map(e=>e.mrr)),ndcgAt5:f(t.map(e=>e.ndcgAt5))}}function d(t,e){return{precisionAt1:w(t,e,1),precisionAt3:w(t,e,3),recallAt3:x(t,e,3),mrr:V(t,e),ndcgAt5:z(t,e,5)}}async function N(t,e){const n=C(t),s=[],i=[],c=[];for(const r of e){const l=new Set(r.relevant),R=(await $(n,[`${m}*`],r.query,10)).map(S),M=await $(n,[`${m}*`],r.query,25),b=q(M).map(S),E=L(t,r.query);s.push(d(E,l)),i.push(d(R,l)),c.push(d(b,l))}const a=g(s),u=g(i),o=g(c),p=(r,l)=>r===0?l>0?1:0:(l-r)/r,y=r=>({precisionAt1:p(r.precisionAt1,o.precisionAt1),precisionAt3:p(r.precisionAt3,o.precisionAt3),recallAt3:p(r.recallAt3,o.recallAt3),mrr:p(r.mrr,o.mrr),ndcgAt5:p(r.ndcgAt5,o.ndcgAt5)});return{flatfile:a,baseline:u,cachly:o,lift:y(u),liftVsFlatfile:y(a),queryCount:e.length,corpusSize:t.length}}async function O(){return N(B,k)}const A=t=>`${(t*100).toFixed(1)}%`,h=t=>`${t>=0?"+":""}${(t*100).toFixed(1)}%`;function P(t){const e=(n,s,i,c,a)=>` ${n.padEnd(12)} ${A(s).padStart(8)} ${A(i).padStart(8)} ${A(c).padStart(8)} ${h(a).padStart(8)}`;return["","\u{1F9E0} Cachly-Bench \u2014 recall quality head-to-head","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",` corpus: ${t.corpusSize} lessons \xB7 queries: ${t.queryCount}`," flat-file = naive term-overlap, no quality signal (\u2248 LLM reading memory files)"," baseline = raw BM25+ keyword ranking"," cachly = BM25+ followed by quality-aware reranking","",` ${"metric".padEnd(12)} ${"flatfile".padStart(8)} ${"baseline".padStart(8)} ${"cachly".padStart(8)} ${"vs flat".padStart(8)}`,e("Precision@1",t.flatfile.precisionAt1,t.baseline.precisionAt1,t.cachly.precisionAt1,t.liftVsFlatfile.precisionAt1),e("Precision@3",t.flatfile.precisionAt3,t.baseline.precisionAt3,t.cachly.precisionAt3,t.liftVsFlatfile.precisionAt3),e("Recall@3",t.flatfile.recallAt3,t.baseline.recallAt3,t.cachly.recallAt3,t.liftVsFlatfile.recallAt3),e("MRR",t.flatfile.mrr,t.baseline.mrr,t.cachly.mrr,t.liftVsFlatfile.mrr),e("nDCG@5",t.flatfile.ndcgAt5,t.baseline.ndcgAt5,t.cachly.ndcgAt5,t.liftVsFlatfile.ndcgAt5),"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",` vs BM25 baseline : MRR ${h(t.lift.mrr)} \xB7 Precision@1 ${h(t.lift.precisionAt1)}`,` vs flat-file mem : MRR ${h(t.liftVsFlatfile.mrr)} \xB7 Precision@1 ${h(t.liftVsFlatfile.precisionAt1)}`,""].join(`
2
+ `)}const j=(()=>{try{return import.meta.url===`file://${process.argv[1]}`||process.argv[1]?.endsWith("cachly-bench.ts")||process.argv[1]?.endsWith("cachly-bench.js")}catch{return!1}})();j&&O().then(t=>{process.argv.includes("--json")?process.stdout.write(JSON.stringify(t,null,2)+`
3
+ `):process.stdout.write(P(t))}).catch(t=>{process.stderr.write(`bench failed: ${t instanceof Error?t.stack:String(t)}
4
+ `),process.exit(1)});export{P as formatReport,O as runBenchmark,N as runBenchmarkOn};
@@ -1,135 +1,4 @@
1
- /**
2
- * External labeled-corpus benchmark.
3
- *
4
- * The built-in fixture bench (fixtures.ts) proves lift on a corpus *we* wrote.
5
- * The honest next step is to prove the same lift on a corpus authored by a third
6
- * party. This module loads a labeled corpus from a portable JSON file and runs the
7
- * exact same three-ranker comparison (flat-file vs BM25 baseline vs cachly).
8
- *
9
- * Portable format (BEIR-ish, but with the optional quality metadata our reranker
10
- * uses — a generic IR corpus without it will simply show cachly ≈ BM25, which is
11
- * itself an honest, documented result):
12
- *
13
- * {
14
- * "name": "my-team-incidents-2025",
15
- * "lessons": [
16
- * { "topic": "deploy:oom", "outcome": "success", "what_worked": "...",
17
- * "confidence": 0.9, "recall_count": 4, "severity": "critical",
18
- * "review_level": "senior", "reviewed_by": "alice", "endorsements": 2,
19
- * "ts": "2025-01-02T00:00:00.000Z" },
20
- * ...
21
- * ],
22
- * "queries": [
23
- * { "query": "pod keeps restarting out of memory", "relevant": ["deploy:oom"] },
24
- * ...
25
- * ]
26
- * }
27
- *
28
- * Run: npm run bench:external (uses the bundled sample)
29
- * npm run bench:external -- ./path/corpus.json (your own labeled corpus)
30
- * npm run bench:external -- ./corpus.json --json
31
- */
32
- import { readFile } from 'node:fs/promises';
33
- import { fileURLToPath } from 'node:url';
34
- import { dirname, resolve } from 'node:path';
35
- import { runBenchmarkOn, formatReport } from './cachly-bench.js';
36
- /** Parse + validate a portable corpus object. Throws a clear error on bad shape. */
37
- export function parseExternalCorpus(raw) {
38
- if (!raw || typeof raw !== 'object') {
39
- throw new Error('Corpus must be a JSON object with `lessons` and `queries`.');
40
- }
41
- const obj = raw;
42
- const lessons = obj.lessons;
43
- const queries = obj.queries;
44
- if (!Array.isArray(lessons) || lessons.length === 0) {
45
- throw new Error('Corpus.lessons must be a non-empty array.');
46
- }
47
- if (!Array.isArray(queries) || queries.length === 0) {
48
- throw new Error('Corpus.queries must be a non-empty array.');
49
- }
50
- const topics = new Set();
51
- for (const [i, l] of lessons.entries()) {
52
- const lesson = l;
53
- if (typeof lesson.topic !== 'string' || !lesson.topic.trim()) {
54
- throw new Error(`lessons[${i}].topic must be a non-empty string.`);
55
- }
56
- if (typeof lesson.what_worked !== 'string') {
57
- throw new Error(`lessons[${i}].what_worked must be a string.`);
58
- }
59
- if (topics.has(lesson.topic)) {
60
- throw new Error(`Duplicate lesson topic "${lesson.topic}" — topics must be unique.`);
61
- }
62
- topics.add(lesson.topic);
63
- }
64
- for (const [i, q] of queries.entries()) {
65
- const query = q;
66
- if (typeof query.query !== 'string' || !query.query.trim()) {
67
- throw new Error(`queries[${i}].query must be a non-empty string.`);
68
- }
69
- if (!Array.isArray(query.relevant) || query.relevant.length === 0) {
70
- throw new Error(`queries[${i}].relevant must be a non-empty array of topics.`);
71
- }
72
- for (const rel of query.relevant) {
73
- if (typeof rel !== 'string' || !topics.has(rel)) {
74
- throw new Error(`queries[${i}] references unknown relevant topic "${String(rel)}".`);
75
- }
76
- }
77
- }
78
- return {
79
- name: typeof obj.name === 'string' ? obj.name : undefined,
80
- lessons: lessons,
81
- queries: queries,
82
- };
83
- }
84
- /** Load + validate a corpus from a JSON file path. */
85
- export async function loadExternalCorpus(path) {
86
- const text = await readFile(path, 'utf-8');
87
- let raw;
88
- try {
89
- raw = JSON.parse(text);
90
- }
91
- catch (e) {
92
- throw new Error(`Corpus file is not valid JSON: ${e.message}`);
93
- }
94
- return parseExternalCorpus(raw);
95
- }
96
- /** Run the three-ranker benchmark on an external corpus. */
97
- export async function runExternalBenchmark(corpus) {
98
- return runBenchmarkOn(corpus.lessons, corpus.queries);
99
- }
100
- // ── CLI entry ───────────────────────────────────────────────────────────────────
101
- const isMain = (() => {
102
- try {
103
- return import.meta.url === `file://${process.argv[1]}`
104
- || process.argv[1]?.endsWith('external-corpus.ts')
105
- || process.argv[1]?.endsWith('external-corpus.js');
106
- }
107
- catch {
108
- return false;
109
- }
110
- })();
111
- if (isMain) {
112
- const args = process.argv.slice(2).filter(a => a !== '--json');
113
- const asJson = process.argv.includes('--json');
114
- const here = dirname(fileURLToPath(import.meta.url));
115
- // Default to the bundled sample so `npm run bench:external` works out of the box.
116
- const corpusPath = args[0]
117
- ? resolve(process.cwd(), args[0])
118
- : resolve(here, 'external', 'sample-corpus.json');
119
- loadExternalCorpus(corpusPath)
120
- .then(async (corpus) => {
121
- const result = await runExternalBenchmark(corpus);
122
- if (asJson) {
123
- console.log(JSON.stringify({ corpus: corpus.name ?? corpusPath, ...result }, null, 2));
124
- }
125
- else {
126
- console.log(`\n📦 External corpus: ${corpus.name ?? corpusPath}`);
127
- console.log(formatReport(result));
128
- }
129
- })
130
- .catch((e) => {
131
- console.error(`\n❌ External bench failed: ${e.message}\n`);
132
- process.exit(1);
133
- });
134
- }
135
- //# sourceMappingURL=external-corpus.js.map
1
+ import{readFile as p}from"node:fs/promises";import{fileURLToPath as u}from"node:url";import{dirname as f,resolve as l}from"node:path";import{runBenchmarkOn as m,formatReport as h}from"./cachly-bench.js";function y(e){if(!e||typeof e!="object")throw new Error("Corpus must be a JSON object with `lessons` and `queries`.");const i=e,t=i.lessons,o=i.queries;if(!Array.isArray(t)||t.length===0)throw new Error("Corpus.lessons must be a non-empty array.");if(!Array.isArray(o)||o.length===0)throw new Error("Corpus.queries must be a non-empty array.");const s=new Set;for(const[n,a]of t.entries()){const r=a;if(typeof r.topic!="string"||!r.topic.trim())throw new Error(`lessons[${n}].topic must be a non-empty string.`);if(typeof r.what_worked!="string")throw new Error(`lessons[${n}].what_worked must be a string.`);if(s.has(r.topic))throw new Error(`Duplicate lesson topic "${r.topic}" \u2014 topics must be unique.`);s.add(r.topic)}for(const[n,a]of o.entries()){const r=a;if(typeof r.query!="string"||!r.query.trim())throw new Error(`queries[${n}].query must be a non-empty string.`);if(!Array.isArray(r.relevant)||r.relevant.length===0)throw new Error(`queries[${n}].relevant must be a non-empty array of topics.`);for(const c of r.relevant)if(typeof c!="string"||!s.has(c))throw new Error(`queries[${n}] references unknown relevant topic "${String(c)}".`)}return{name:typeof i.name=="string"?i.name:void 0,lessons:t,queries:o}}async function w(e){const i=await p(e,"utf-8");let t;try{t=JSON.parse(i)}catch(o){throw new Error(`Corpus file is not valid JSON: ${o.message}`)}return y(t)}async function g(e){return m(e.lessons,e.queries)}const d=(()=>{try{return import.meta.url===`file://${process.argv[1]}`||process.argv[1]?.endsWith("external-corpus.ts")||process.argv[1]?.endsWith("external-corpus.js")}catch{return!1}})();if(d){const e=process.argv.slice(2).filter(s=>s!=="--json"),i=process.argv.includes("--json"),t=f(u(import.meta.url)),o=e[0]?l(process.cwd(),e[0]):l(t,"external","sample-corpus.json");w(o).then(async s=>{const n=await g(s);i?console.log(JSON.stringify({corpus:s.name??o,...n},null,2)):(console.log(`
2
+ \u{1F4E6} External corpus: ${s.name??o}`),console.log(h(n)))}).catch(s=>{console.error(`
3
+ \u274C External bench failed: ${s.message}
4
+ `),process.exit(1)})}export{w as loadExternalCorpus,y as parseExternalCorpus,g as runExternalBenchmark};
@@ -1,225 +1 @@
1
- /**
2
- * Cachly-Bench fixture corpus.
3
- *
4
- * A small but realistic corpus of engineering "lessons" plus a set of labeled
5
- * natural-language queries. Each query declares which lesson topics are *truly*
6
- * relevant — the answer an agent actually wants.
7
- *
8
- * The corpus is deliberately adversarial in a fair way: for several queries there
9
- * is a text-similar but LOW-QUALITY distractor (a failed attempt, an unverified
10
- * note) competing with the proven success lesson. This is exactly the situation
11
- * where raw text ranking (and a flat-file memory) goes wrong, and where Cachly's
12
- * quality-aware reranking should win. The labels reward the lesson that solves the
13
- * problem, not the one that merely shares vocabulary.
14
- */
15
- const NOW = Date.now();
16
- const daysAgo = (d) => new Date(NOW - d * 86400_000).toISOString();
17
- export const BENCH_LESSONS = [
18
- // ── Deploy domain ──────────────────────────────────────────────────────────
19
- {
20
- topic: 'deploy:k8s:rollout-stuck',
21
- outcome: 'success',
22
- what_worked: 'Set a readinessProbe with a longer initialDelaySeconds and a higher failureThreshold so the liveness probe stopped terminating the container before startup completed. The pod then reached ready and traffic flowed.',
23
- context: 'Production release never became healthy.',
24
- severity: 'critical', confidence: 0.95, recall_count: 12,
25
- tags: ['kubernetes', 'deploy', 'probe'], ts: daysAgo(8),
26
- },
27
- {
28
- topic: 'deploy:k8s:rollout-attempt-restart',
29
- outcome: 'failure',
30
- what_worked: '',
31
- what_failed: 'The kubernetes deploy was stuck, the rollout was not finishing, so I restarted the deploy. The kubernetes deploy stayed stuck and the rollout still was not finishing. Restarted the stuck rollout again, deploy still stuck.',
32
- context: 'Kubernetes deploy stuck, rollout not finishing.',
33
- severity: 'major', confidence: 0.3, recall_count: 0,
34
- tags: ['kubernetes', 'deploy'], ts: daysAgo(9),
35
- },
36
- {
37
- topic: 'deploy:docker:image-too-large',
38
- outcome: 'success',
39
- what_worked: 'Reduced Docker image size dramatically with a multi-stage build and a slim base image, moving build-only deps out of the final layer.',
40
- context: 'CI pushes were slow due to a 1.8GB image.',
41
- severity: 'minor', confidence: 0.9, recall_count: 6,
42
- tags: ['docker', 'deploy', 'optimization'], ts: daysAgo(20),
43
- },
44
- // ── Database domain ────────────────────────────────────────────────────────
45
- {
46
- topic: 'db:postgres:connection-pool-exhausted',
47
- outcome: 'success',
48
- what_worked: 'Added PgBouncer in transaction pooling mode and lowered the per-service max pool size so backends were shared. Peak traffic stopped exhausting backends.',
49
- context: 'API returned 500s at peak traffic.',
50
- severity: 'critical', confidence: 0.92, recall_count: 9,
51
- tags: ['postgres', 'database', 'pool'], ts: daysAgo(15),
52
- },
53
- {
54
- topic: 'db:postgres:slow-query-missing-index',
55
- outcome: 'success',
56
- what_worked: 'A slow Postgres query was fixed by adding a composite index on (tenant_id, created_at); query dropped from 4s to 12ms.',
57
- context: 'Dashboard timed out loading recent events.',
58
- severity: 'major', confidence: 0.88, recall_count: 4,
59
- tags: ['postgres', 'database', 'index', 'performance'], ts: daysAgo(30),
60
- },
61
- {
62
- topic: 'db:postgres:pool-raise-limit-attempt',
63
- outcome: 'failure',
64
- what_worked: '',
65
- what_failed: 'Postgres had too many connections under load, so I raised max_connections. Postgres still had too many connections under load and the load made it worse — too many connections again under load.',
66
- context: 'Postgres too many connections under load.',
67
- severity: 'major', confidence: 0.25, recall_count: 0,
68
- tags: ['postgres', 'database', 'pool'], ts: daysAgo(16),
69
- },
70
- // ── Auth domain ────────────────────────────────────────────────────────────
71
- {
72
- topic: 'auth:jwt:expired-on-restart',
73
- outcome: 'success',
74
- what_worked: 'JWT was lost on server restart because it was only kept in memory; persisting the token to disk after the device flow fixed recall counts staying at zero.',
75
- context: 'Telemetry showed zero authenticated calls after restarts.',
76
- severity: 'critical', confidence: 0.93, recall_count: 7,
77
- tags: ['jwt', 'auth', 'persistence'], ts: daysAgo(3),
78
- },
79
- {
80
- topic: 'auth:pkce:keycloak-rejects-exchange',
81
- outcome: 'success',
82
- what_worked: 'Keycloak rejected the PKCE code exchange because the verifier used non-URL-safe Base64; switching to RFC 7636 Base64URL random bytes fixed the login.',
83
- context: 'Mobile login failed at the token exchange step.',
84
- severity: 'major', confidence: 0.9, recall_count: 3,
85
- tags: ['pkce', 'auth', 'keycloak', 'oauth'], ts: daysAgo(2),
86
- },
87
- // ── Redis / cache domain ───────────────────────────────────────────────────
88
- {
89
- topic: 'redis:noauth-on-every-call',
90
- outcome: 'success',
91
- what_worked: 'The connection endpoint omitted the password field, so the client connected unauthenticated; returning the credential explicitly in the response let the client authenticate and the calls succeeded.',
92
- context: 'Hosted Valkey requires a credential.',
93
- severity: 'critical', confidence: 0.94, recall_count: 11,
94
- tags: ['redis', 'valkey', 'auth'], ts: daysAgo(4),
95
- },
96
- {
97
- topic: 'redis:noauth-disable-auth-attempt',
98
- outcome: 'failure',
99
- what_worked: '',
100
- what_failed: 'Redis threw NOAUTH on every call, so I tried disabling auth on Redis. Redis still threw NOAUTH on every call. Tried again to stop the NOAUTH error on every Redis call — no luck.',
101
- context: 'Redis NOAUTH error on every call.',
102
- severity: 'major', confidence: 0.2, recall_count: 0,
103
- tags: ['redis', 'auth'], ts: daysAgo(5),
104
- },
105
- {
106
- topic: 'redis:keys-no-ttl-memory-leak',
107
- outcome: 'success',
108
- what_worked: 'Redis memory grew unbounded because dependency-index and history keys had no TTL; adding a 90-day expire and an ltrim bounded the memory.',
109
- context: 'Instance memory crept up over weeks of use.',
110
- severity: 'major', confidence: 0.85, recall_count: 2,
111
- tags: ['redis', 'ttl', 'memory'], ts: daysAgo(1),
112
- },
113
- // ── CI / build domain ──────────────────────────────────────────────────────
114
- {
115
- topic: 'ci:npm-ci-lockfile-mismatch',
116
- outcome: 'success',
117
- what_worked: 'CI "npm ci" failed because a dependency was added to package.json but the lockfile was never regenerated; running npm install to update package-lock.json fixed the build.',
118
- context: 'GitHub Actions install step failed after adding a dep.',
119
- severity: 'major', confidence: 0.91, recall_count: 5,
120
- tags: ['ci', 'npm', 'lockfile'], ts: daysAgo(0),
121
- },
122
- {
123
- topic: 'ci:golangci-lint-errcheck',
124
- outcome: 'success',
125
- what_worked: 'golangci-lint failed on an unchecked recover() return value; assigning it to _ satisfied errcheck.',
126
- context: 'Go lint job blocked the deploy pipeline.',
127
- severity: 'minor', confidence: 0.8, recall_count: 1,
128
- tags: ['ci', 'go', 'lint'], ts: daysAgo(1),
129
- },
130
- // ── Frontend domain ────────────────────────────────────────────────────────
131
- {
132
- topic: 'frontend:cors-preflight-blocked',
133
- outcome: 'success',
134
- what_worked: 'Dashboard mutations failed because the CORS middleware did not allow PUT/PATCH; adding them to AllowMethods fixed the preflight.',
135
- context: 'BYOK and webhook settings could not be saved from the browser.',
136
- severity: 'major', confidence: 0.87, recall_count: 4,
137
- tags: ['cors', 'frontend', 'api'], ts: daysAgo(6),
138
- },
139
- {
140
- topic: 'frontend:hydration-mismatch',
141
- outcome: 'partial',
142
- what_worked: 'A Next.js hydration mismatch was reduced by guarding a date render with a client-only effect, though the root cause (locale formatting) still needs work.',
143
- context: 'Console hydration warnings on the landing page.',
144
- severity: 'minor', confidence: 0.6, recall_count: 1,
145
- tags: ['nextjs', 'frontend', 'hydration'], ts: daysAgo(25),
146
- },
147
- // ── Governance adversarial pair: two similar-text success lessons ──────────
148
- // Only the reviewed one should win; baseline cannot distinguish them by text.
149
- {
150
- topic: 'auth:csrf:double-submit-cookie',
151
- outcome: 'success',
152
- what_worked: 'CSRF protection was added via the double-submit cookie pattern: the server sets a random CSRF token cookie; the client echoes it as a request header. Requests with a mismatched or absent header are rejected.',
153
- context: 'API mutations lacked CSRF protection; security audit required a fix.',
154
- severity: 'critical', confidence: 0.91, recall_count: 2,
155
- reviewed_by: 'senior-alice', review_level: 'senior', endorsements: 2,
156
- tags: ['csrf', 'auth', 'security', 'cookie'], ts: daysAgo(12),
157
- },
158
- {
159
- topic: 'auth:csrf:origin-header-check',
160
- outcome: 'success',
161
- what_worked: 'CSRF mitigation attempted by checking the Origin header on API requests, but this was bypassed in a penetration test because same-site requests from subdomains also sent the header.',
162
- context: 'API mutations lacked CSRF protection; origin header check was insufficient.',
163
- severity: 'major', confidence: 0.55, recall_count: 1,
164
- tags: ['csrf', 'auth', 'security'], ts: daysAgo(14),
165
- },
166
- ];
167
- export const BENCH_QUERIES = [
168
- {
169
- query: 'kubernetes deploy stuck rollout not finishing',
170
- relevant: ['deploy:k8s:rollout-stuck'],
171
- },
172
- {
173
- query: 'postgres too many connections under load',
174
- relevant: ['db:postgres:connection-pool-exhausted'],
175
- },
176
- {
177
- query: 'dashboard query is slow and times out',
178
- relevant: ['db:postgres:slow-query-missing-index'],
179
- },
180
- {
181
- query: 'jwt lost after restart recall count zero',
182
- relevant: ['auth:jwt:expired-on-restart'],
183
- },
184
- {
185
- query: 'keycloak rejects pkce token exchange on login',
186
- relevant: ['auth:pkce:keycloak-rejects-exchange'],
187
- },
188
- {
189
- query: 'redis noauth error on every call',
190
- relevant: ['redis:noauth-on-every-call'],
191
- },
192
- {
193
- query: 'redis memory keeps growing unbounded',
194
- relevant: ['redis:keys-no-ttl-memory-leak'],
195
- },
196
- {
197
- query: 'npm ci fails in github actions after adding dependency',
198
- relevant: ['ci:npm-ci-lockfile-mismatch'],
199
- },
200
- {
201
- query: 'cannot save settings cors preflight blocked',
202
- relevant: ['frontend:cors-preflight-blocked'],
203
- },
204
- {
205
- query: 'docker image too big slow ci push',
206
- relevant: ['deploy:docker:image-too-large'],
207
- },
208
- {
209
- query: 'go lint failing on unchecked return value',
210
- relevant: ['ci:golangci-lint-errcheck'],
211
- },
212
- // Multi-relevant: both postgres performance lessons are reasonable answers.
213
- {
214
- query: 'postgres performance problem in production',
215
- relevant: ['db:postgres:connection-pool-exhausted', 'db:postgres:slow-query-missing-index'],
216
- },
217
- // Governance adversarial: two CSRF lessons have almost identical text, but only
218
- // the senior-reviewed canonical one is a proven complete solution. Quality
219
- // reranking must prefer the reviewed+endorsed lesson over the unreviewed partial.
220
- {
221
- query: 'CSRF protection missing on API mutations security',
222
- relevant: ['auth:csrf:double-submit-cookie'],
223
- },
224
- ];
225
- //# sourceMappingURL=fixtures.js.map
1
+ const o=Date.now(),e=t=>new Date(o-t*864e5).toISOString(),a=[{topic:"deploy:k8s:rollout-stuck",outcome:"success",what_worked:"Set a readinessProbe with a longer initialDelaySeconds and a higher failureThreshold so the liveness probe stopped terminating the container before startup completed. The pod then reached ready and traffic flowed.",context:"Production release never became healthy.",severity:"critical",confidence:.95,recall_count:12,tags:["kubernetes","deploy","probe"],ts:e(8)},{topic:"deploy:k8s:rollout-attempt-restart",outcome:"failure",what_worked:"",what_failed:"The kubernetes deploy was stuck, the rollout was not finishing, so I restarted the deploy. The kubernetes deploy stayed stuck and the rollout still was not finishing. Restarted the stuck rollout again, deploy still stuck.",context:"Kubernetes deploy stuck, rollout not finishing.",severity:"major",confidence:.3,recall_count:0,tags:["kubernetes","deploy"],ts:e(9)},{topic:"deploy:docker:image-too-large",outcome:"success",what_worked:"Reduced Docker image size dramatically with a multi-stage build and a slim base image, moving build-only deps out of the final layer.",context:"CI pushes were slow due to a 1.8GB image.",severity:"minor",confidence:.9,recall_count:6,tags:["docker","deploy","optimization"],ts:e(20)},{topic:"db:postgres:connection-pool-exhausted",outcome:"success",what_worked:"Added PgBouncer in transaction pooling mode and lowered the per-service max pool size so backends were shared. Peak traffic stopped exhausting backends.",context:"API returned 500s at peak traffic.",severity:"critical",confidence:.92,recall_count:9,tags:["postgres","database","pool"],ts:e(15)},{topic:"db:postgres:slow-query-missing-index",outcome:"success",what_worked:"A slow Postgres query was fixed by adding a composite index on (tenant_id, created_at); query dropped from 4s to 12ms.",context:"Dashboard timed out loading recent events.",severity:"major",confidence:.88,recall_count:4,tags:["postgres","database","index","performance"],ts:e(30)},{topic:"db:postgres:pool-raise-limit-attempt",outcome:"failure",what_worked:"",what_failed:"Postgres had too many connections under load, so I raised max_connections. Postgres still had too many connections under load and the load made it worse \u2014 too many connections again under load.",context:"Postgres too many connections under load.",severity:"major",confidence:.25,recall_count:0,tags:["postgres","database","pool"],ts:e(16)},{topic:"auth:jwt:expired-on-restart",outcome:"success",what_worked:"JWT was lost on server restart because it was only kept in memory; persisting the token to disk after the device flow fixed recall counts staying at zero.",context:"Telemetry showed zero authenticated calls after restarts.",severity:"critical",confidence:.93,recall_count:7,tags:["jwt","auth","persistence"],ts:e(3)},{topic:"auth:pkce:keycloak-rejects-exchange",outcome:"success",what_worked:"Keycloak rejected the PKCE code exchange because the verifier used non-URL-safe Base64; switching to RFC 7636 Base64URL random bytes fixed the login.",context:"Mobile login failed at the token exchange step.",severity:"major",confidence:.9,recall_count:3,tags:["pkce","auth","keycloak","oauth"],ts:e(2)},{topic:"redis:noauth-on-every-call",outcome:"success",what_worked:"The connection endpoint omitted the password field, so the client connected unauthenticated; returning the credential explicitly in the response let the client authenticate and the calls succeeded.",context:"Hosted Valkey requires a credential.",severity:"critical",confidence:.94,recall_count:11,tags:["redis","valkey","auth"],ts:e(4)},{topic:"redis:noauth-disable-auth-attempt",outcome:"failure",what_worked:"",what_failed:"Redis threw NOAUTH on every call, so I tried disabling auth on Redis. Redis still threw NOAUTH on every call. Tried again to stop the NOAUTH error on every Redis call \u2014 no luck.",context:"Redis NOAUTH error on every call.",severity:"major",confidence:.2,recall_count:0,tags:["redis","auth"],ts:e(5)},{topic:"redis:keys-no-ttl-memory-leak",outcome:"success",what_worked:"Redis memory grew unbounded because dependency-index and history keys had no TTL; adding a 90-day expire and an ltrim bounded the memory.",context:"Instance memory crept up over weeks of use.",severity:"major",confidence:.85,recall_count:2,tags:["redis","ttl","memory"],ts:e(1)},{topic:"ci:npm-ci-lockfile-mismatch",outcome:"success",what_worked:'CI "npm ci" failed because a dependency was added to package.json but the lockfile was never regenerated; running npm install to update package-lock.json fixed the build.',context:"GitHub Actions install step failed after adding a dep.",severity:"major",confidence:.91,recall_count:5,tags:["ci","npm","lockfile"],ts:e(0)},{topic:"ci:golangci-lint-errcheck",outcome:"success",what_worked:"golangci-lint failed on an unchecked recover() return value; assigning it to _ satisfied errcheck.",context:"Go lint job blocked the deploy pipeline.",severity:"minor",confidence:.8,recall_count:1,tags:["ci","go","lint"],ts:e(1)},{topic:"frontend:cors-preflight-blocked",outcome:"success",what_worked:"Dashboard mutations failed because the CORS middleware did not allow PUT/PATCH; adding them to AllowMethods fixed the preflight.",context:"BYOK and webhook settings could not be saved from the browser.",severity:"major",confidence:.87,recall_count:4,tags:["cors","frontend","api"],ts:e(6)},{topic:"frontend:hydration-mismatch",outcome:"partial",what_worked:"A Next.js hydration mismatch was reduced by guarding a date render with a client-only effect, though the root cause (locale formatting) still needs work.",context:"Console hydration warnings on the landing page.",severity:"minor",confidence:.6,recall_count:1,tags:["nextjs","frontend","hydration"],ts:e(25)},{topic:"auth:csrf:double-submit-cookie",outcome:"success",what_worked:"CSRF protection was added via the double-submit cookie pattern: the server sets a random CSRF token cookie; the client echoes it as a request header. Requests with a mismatched or absent header are rejected.",context:"API mutations lacked CSRF protection; security audit required a fix.",severity:"critical",confidence:.91,recall_count:2,reviewed_by:"senior-alice",review_level:"senior",endorsements:2,tags:["csrf","auth","security","cookie"],ts:e(12)},{topic:"auth:csrf:origin-header-check",outcome:"success",what_worked:"CSRF mitigation attempted by checking the Origin header on API requests, but this was bypassed in a penetration test because same-site requests from subdomains also sent the header.",context:"API mutations lacked CSRF protection; origin header check was insufficient.",severity:"major",confidence:.55,recall_count:1,tags:["csrf","auth","security"],ts:e(14)}],n=[{query:"kubernetes deploy stuck rollout not finishing",relevant:["deploy:k8s:rollout-stuck"]},{query:"postgres too many connections under load",relevant:["db:postgres:connection-pool-exhausted"]},{query:"dashboard query is slow and times out",relevant:["db:postgres:slow-query-missing-index"]},{query:"jwt lost after restart recall count zero",relevant:["auth:jwt:expired-on-restart"]},{query:"keycloak rejects pkce token exchange on login",relevant:["auth:pkce:keycloak-rejects-exchange"]},{query:"redis noauth error on every call",relevant:["redis:noauth-on-every-call"]},{query:"redis memory keeps growing unbounded",relevant:["redis:keys-no-ttl-memory-leak"]},{query:"npm ci fails in github actions after adding dependency",relevant:["ci:npm-ci-lockfile-mismatch"]},{query:"cannot save settings cors preflight blocked",relevant:["frontend:cors-preflight-blocked"]},{query:"docker image too big slow ci push",relevant:["deploy:docker:image-too-large"]},{query:"go lint failing on unchecked return value",relevant:["ci:golangci-lint-errcheck"]},{query:"postgres performance problem in production",relevant:["db:postgres:connection-pool-exhausted","db:postgres:slow-query-missing-index"]},{query:"CSRF protection missing on API mutations security",relevant:["auth:csrf:double-submit-cookie"]}];export{a as BENCH_LESSONS,n as BENCH_QUERIES};