@cachly-dev/mcp-server 0.10.89 → 0.10.91

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/README.md CHANGED
@@ -226,6 +226,7 @@ causal_trace(problem="auth breaks after restart")
226
226
  ```bash
227
227
  npx @cachly-dev/mcp-server@latest autopilot # One command — signs in, configures every editor, bootstraps from git
228
228
  npx @cachly-dev/mcp-server@latest demo # Preview your Brain (no account needed)
229
+ npx @cachly-dev/mcp-server@latest bench # Recall quality vs flat-file memory (no auth required)
229
230
  npx @cachly-dev/mcp-server@latest setup # Interactive variant — pick editors yourself
230
231
  npx @cachly-dev/mcp-server@latest health # Check token, API, editors, git hook
231
232
  npx @cachly-dev/mcp-server@latest digest # Weekly Brain summary — shareable
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Cachly-Bench — the moat proof.
3
+ *
4
+ * Measures recall quality on a fixed, labeled corpus and compares two rankers
5
+ * over the *same* BM25 candidate set, so the comparison isolates ranking quality:
6
+ *
7
+ * baseline — raw BM25+ keyword ranking (what a text-only / flat-file memory does)
8
+ * cachly — BM25+ followed by quality-aware reranking (rerank.ts)
9
+ *
10
+ * It reports standard IR metrics (Precision@k, Recall@k, MRR, nDCG@k) and the
11
+ * relative lift. The corpus (fixtures.ts) is fair: gold answers are the lessons
12
+ * that actually solve each problem, while text-similar failed attempts act as
13
+ * distractors — exactly where ranking by quality should pull ahead.
14
+ *
15
+ * Run: npm run bench (pretty report)
16
+ * npm run bench -- --json (machine-readable)
17
+ */
18
+ import { type BenchLesson, type BenchQuery } from './fixtures.js';
19
+ export interface BenchMetrics {
20
+ precisionAt1: number;
21
+ precisionAt3: number;
22
+ recallAt3: number;
23
+ mrr: number;
24
+ ndcgAt5: number;
25
+ }
26
+ export interface BenchResult {
27
+ /** Flat-file memory simulation: naive term-overlap, no IDF, no quality signal. */
28
+ flatfile: BenchMetrics;
29
+ baseline: BenchMetrics;
30
+ cachly: BenchMetrics;
31
+ /** Relative lift of cachly vs the BM25 baseline. */
32
+ lift: BenchMetrics;
33
+ /** Relative lift of cachly vs a flat-file memory (the head-to-head that matters). */
34
+ liftVsFlatfile: BenchMetrics;
35
+ queryCount: number;
36
+ corpusSize: number;
37
+ }
38
+ /**
39
+ * Run the three-ranker benchmark over any labeled corpus + query set. Pure-ish:
40
+ * no network, no real Redis. Shared by the built-in fixture bench and the external
41
+ * labeled-corpus bench (external-corpus.ts) so both prove lift the same way.
42
+ */
43
+ export declare function runBenchmarkOn(lessons: BenchLesson[], queries: BenchQuery[]): Promise<BenchResult>;
44
+ /** Run the benchmark over the built-in fixture corpus. */
45
+ export declare function runBenchmark(): Promise<BenchResult>;
46
+ export declare function formatReport(r: BenchResult): string;
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Cachly-Bench — the moat proof.
3
+ *
4
+ * Measures recall quality on a fixed, labeled corpus and compares two rankers
5
+ * over the *same* BM25 candidate set, so the comparison isolates ranking quality:
6
+ *
7
+ * baseline — raw BM25+ keyword ranking (what a text-only / flat-file memory does)
8
+ * cachly — BM25+ followed by quality-aware reranking (rerank.ts)
9
+ *
10
+ * It reports standard IR metrics (Precision@k, Recall@k, MRR, nDCG@k) and the
11
+ * relative lift. The corpus (fixtures.ts) is fair: gold answers are the lessons
12
+ * that actually solve each problem, while text-similar failed attempts act as
13
+ * distractors — exactly where ranking by quality should pull ahead.
14
+ *
15
+ * Run: npm run bench (pretty report)
16
+ * npm run bench -- --json (machine-readable)
17
+ */
18
+ import { EventEmitter } from 'node:events';
19
+ import { keywordSearch } from '../search.js';
20
+ import { rerankByQuality } from '../rerank.js';
21
+ import { BENCH_LESSONS, BENCH_QUERIES } from './fixtures.js';
22
+ const LESSON_PREFIX = 'cachly:lesson:best:';
23
+ // ── Minimal in-memory Redis (only what keywordSearch needs) ────────────────────
24
+ class MiniRedis {
25
+ store = new Map();
26
+ set(key, value) { this.store.set(key, value); }
27
+ async get(key) { return this.store.get(key) ?? null; }
28
+ scanStream(opts) {
29
+ const emitter = new EventEmitter();
30
+ const pattern = '^' + opts.match.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$';
31
+ const regex = new RegExp(pattern);
32
+ const matches = [...this.store.keys()].filter(k => regex.test(k));
33
+ setImmediate(() => { emitter.emit('data', matches); emitter.emit('end'); });
34
+ return emitter;
35
+ }
36
+ pipeline() {
37
+ const cmds = [];
38
+ const store = this.store;
39
+ return {
40
+ get(key) { cmds.push(key); return this; },
41
+ async exec() {
42
+ return cmds.map(k => [null, store.get(k) ?? null]);
43
+ },
44
+ };
45
+ }
46
+ }
47
+ function indexCorpus(lessons) {
48
+ const redis = new MiniRedis();
49
+ for (const l of lessons) {
50
+ redis.set(`${LESSON_PREFIX}${l.topic}`, JSON.stringify(l));
51
+ }
52
+ return redis;
53
+ }
54
+ // ── IR metrics ─────────────────────────────────────────────────────────────────
55
+ function precisionAtK(ranked, relevant, k) {
56
+ const top = ranked.slice(0, k);
57
+ if (top.length === 0)
58
+ return 0;
59
+ const hits = top.filter(t => relevant.has(t)).length;
60
+ return hits / Math.min(k, top.length);
61
+ }
62
+ function recallAtK(ranked, relevant, k) {
63
+ if (relevant.size === 0)
64
+ return 0;
65
+ const top = ranked.slice(0, k);
66
+ const hits = top.filter(t => relevant.has(t)).length;
67
+ return hits / relevant.size;
68
+ }
69
+ function reciprocalRank(ranked, relevant) {
70
+ for (let i = 0; i < ranked.length; i++) {
71
+ if (relevant.has(ranked[i]))
72
+ return 1 / (i + 1);
73
+ }
74
+ return 0;
75
+ }
76
+ function ndcgAtK(ranked, relevant, k) {
77
+ let dcg = 0;
78
+ for (let i = 0; i < Math.min(k, ranked.length); i++) {
79
+ if (relevant.has(ranked[i]))
80
+ dcg += 1 / Math.log2(i + 2);
81
+ }
82
+ let idcg = 0;
83
+ for (let i = 0; i < Math.min(k, relevant.size); i++) {
84
+ idcg += 1 / Math.log2(i + 2);
85
+ }
86
+ return idcg === 0 ? 0 : dcg / idcg;
87
+ }
88
+ /**
89
+ * Simulates how a flat-file memory (e.g. an LLM reading a `/memories` directory)
90
+ * actually surfaces knowledge: it has NO ranking engine. Practical retrieval is
91
+ * naive lexical overlap — count how many distinct query terms appear in the file's
92
+ * text — with NO inverse-document-frequency, NO length normalization, and crucially
93
+ * NO quality signal (a proven fix and a failed attempt are just text). Ties break by
94
+ * recency, mimicking "most recently edited note first". This is a fair, and honestly
95
+ * weaker, stand-in for "Claude reads its own memory files".
96
+ */
97
+ function flatFileRank(lessons, query) {
98
+ const qTerms = new Set(query.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/).filter(t => t.length >= 3));
99
+ const scored = lessons.map(l => {
100
+ const blob = [l.topic, l.what_worked, l.what_failed ?? '', l.context ?? '', (l.tags ?? []).join(' ')]
101
+ .join(' ').toLowerCase();
102
+ let overlap = 0;
103
+ for (const t of qTerms)
104
+ if (blob.includes(t))
105
+ overlap++;
106
+ const recency = l.ts ? new Date(l.ts).getTime() : 0;
107
+ return { topic: l.topic, overlap, recency };
108
+ });
109
+ return scored
110
+ .filter(s => s.overlap > 0)
111
+ .sort((a, b) => (b.overlap - a.overlap) || (b.recency - a.recency))
112
+ .map(s => s.topic);
113
+ }
114
+ const matchTopic = (m) => m.key.replace(LESSON_PREFIX, '');
115
+ function mean(xs) {
116
+ return xs.length === 0 ? 0 : xs.reduce((a, b) => a + b, 0) / xs.length;
117
+ }
118
+ function aggregate(rows) {
119
+ return {
120
+ precisionAt1: mean(rows.map(r => r.precisionAt1)),
121
+ precisionAt3: mean(rows.map(r => r.precisionAt3)),
122
+ recallAt3: mean(rows.map(r => r.recallAt3)),
123
+ mrr: mean(rows.map(r => r.mrr)),
124
+ ndcgAt5: mean(rows.map(r => r.ndcgAt5)),
125
+ };
126
+ }
127
+ function scoreRanking(ranked, relevant) {
128
+ return {
129
+ precisionAt1: precisionAtK(ranked, relevant, 1),
130
+ precisionAt3: precisionAtK(ranked, relevant, 3),
131
+ recallAt3: recallAtK(ranked, relevant, 3),
132
+ mrr: reciprocalRank(ranked, relevant),
133
+ ndcgAt5: ndcgAtK(ranked, relevant, 5),
134
+ };
135
+ }
136
+ /**
137
+ * Run the three-ranker benchmark over any labeled corpus + query set. Pure-ish:
138
+ * no network, no real Redis. Shared by the built-in fixture bench and the external
139
+ * labeled-corpus bench (external-corpus.ts) so both prove lift the same way.
140
+ */
141
+ export async function runBenchmarkOn(lessons, queries) {
142
+ const redis = indexCorpus(lessons);
143
+ const flatfileRows = [];
144
+ const baselineRows = [];
145
+ const cachlyRows = [];
146
+ for (const q of queries) {
147
+ const relevant = new Set(q.relevant);
148
+ // BM25 baseline + cachly share the same candidate set — isolates ranking quality.
149
+ const matches = await keywordSearch(redis, [`${LESSON_PREFIX}*`], q.query, 10);
150
+ const baselineRanked = matches.map(matchTopic);
151
+ const cachlyRanked = rerankByQuality(matches).map(matchTopic);
152
+ // Flat-file memory sees the whole corpus but has no ranking engine.
153
+ const flatfileRanked = flatFileRank(lessons, q.query);
154
+ flatfileRows.push(scoreRanking(flatfileRanked, relevant));
155
+ baselineRows.push(scoreRanking(baselineRanked, relevant));
156
+ cachlyRows.push(scoreRanking(cachlyRanked, relevant));
157
+ }
158
+ const flatfile = aggregate(flatfileRows);
159
+ const baseline = aggregate(baselineRows);
160
+ const cachly = aggregate(cachlyRows);
161
+ const liftOf = (b, c) => (b === 0 ? (c > 0 ? 1 : 0) : (c - b) / b);
162
+ const liftBetween = (from) => ({
163
+ precisionAt1: liftOf(from.precisionAt1, cachly.precisionAt1),
164
+ precisionAt3: liftOf(from.precisionAt3, cachly.precisionAt3),
165
+ recallAt3: liftOf(from.recallAt3, cachly.recallAt3),
166
+ mrr: liftOf(from.mrr, cachly.mrr),
167
+ ndcgAt5: liftOf(from.ndcgAt5, cachly.ndcgAt5),
168
+ });
169
+ return {
170
+ flatfile, baseline, cachly,
171
+ lift: liftBetween(baseline),
172
+ liftVsFlatfile: liftBetween(flatfile),
173
+ queryCount: queries.length,
174
+ corpusSize: lessons.length,
175
+ };
176
+ }
177
+ /** Run the benchmark over the built-in fixture corpus. */
178
+ export async function runBenchmark() {
179
+ return runBenchmarkOn(BENCH_LESSONS, BENCH_QUERIES);
180
+ }
181
+ // ── Reporting ──────────────────────────────────────────────────────────────────
182
+ const pct = (x) => `${(x * 100).toFixed(1)}%`;
183
+ const signedPct = (x) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}%`;
184
+ export function formatReport(r) {
185
+ 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)}`;
186
+ return [
187
+ '',
188
+ '🧠 Cachly-Bench — recall quality head-to-head',
189
+ '──────────────────────────────────────────────────────────────────────────',
190
+ ` corpus: ${r.corpusSize} lessons · queries: ${r.queryCount}`,
191
+ ' flat-file = naive term-overlap, no quality signal (≈ LLM reading memory files)',
192
+ ' baseline = raw BM25+ keyword ranking',
193
+ ' cachly = BM25+ followed by quality-aware reranking',
194
+ '',
195
+ ` ${'metric'.padEnd(12)} ${'flatfile'.padStart(8)} ${'baseline'.padStart(8)} ${'cachly'.padStart(8)} ${'vs flat'.padStart(8)}`,
196
+ row('Precision@1', r.flatfile.precisionAt1, r.baseline.precisionAt1, r.cachly.precisionAt1, r.liftVsFlatfile.precisionAt1),
197
+ row('Precision@3', r.flatfile.precisionAt3, r.baseline.precisionAt3, r.cachly.precisionAt3, r.liftVsFlatfile.precisionAt3),
198
+ row('Recall@3', r.flatfile.recallAt3, r.baseline.recallAt3, r.cachly.recallAt3, r.liftVsFlatfile.recallAt3),
199
+ row('MRR', r.flatfile.mrr, r.baseline.mrr, r.cachly.mrr, r.liftVsFlatfile.mrr),
200
+ row('nDCG@5', r.flatfile.ndcgAt5, r.baseline.ndcgAt5, r.cachly.ndcgAt5, r.liftVsFlatfile.ndcgAt5),
201
+ '──────────────────────────────────────────────────────────────────────────',
202
+ ` vs BM25 baseline : MRR ${signedPct(r.lift.mrr)} · Precision@1 ${signedPct(r.lift.precisionAt1)}`,
203
+ ` vs flat-file mem : MRR ${signedPct(r.liftVsFlatfile.mrr)} · Precision@1 ${signedPct(r.liftVsFlatfile.precisionAt1)}`,
204
+ '',
205
+ ].join('\n');
206
+ }
207
+ // ── CLI entry ───────────────────────────────────────────────────────────────────
208
+ const isMain = (() => {
209
+ try {
210
+ return import.meta.url === `file://${process.argv[1]}`
211
+ || process.argv[1]?.endsWith('cachly-bench.ts')
212
+ || process.argv[1]?.endsWith('cachly-bench.js');
213
+ }
214
+ catch {
215
+ return false;
216
+ }
217
+ })();
218
+ if (isMain) {
219
+ runBenchmark().then(result => {
220
+ if (process.argv.includes('--json')) {
221
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
222
+ }
223
+ else {
224
+ process.stdout.write(formatReport(result));
225
+ }
226
+ }).catch(err => {
227
+ process.stderr.write(`bench failed: ${err instanceof Error ? err.stack : String(err)}\n`);
228
+ process.exit(1);
229
+ });
230
+ }
231
+ //# sourceMappingURL=cachly-bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cachly-bench.js","sourceRoot":"","sources":["../../../src/bench/cachly-bench.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
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 { type BenchResult } from './cachly-bench.js';
33
+ import type { BenchLesson, BenchQuery } from './fixtures.js';
34
+ export interface ExternalCorpus {
35
+ name?: string;
36
+ lessons: BenchLesson[];
37
+ queries: BenchQuery[];
38
+ }
39
+ /** Parse + validate a portable corpus object. Throws a clear error on bad shape. */
40
+ export declare function parseExternalCorpus(raw: unknown): ExternalCorpus;
41
+ /** Load + validate a corpus from a JSON file path. */
42
+ export declare function loadExternalCorpus(path: string): Promise<ExternalCorpus>;
43
+ /** Run the three-ranker benchmark on an external corpus. */
44
+ export declare function runExternalBenchmark(corpus: ExternalCorpus): Promise<BenchResult>;
@@ -0,0 +1,135 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-corpus.js","sourceRoot":"","sources":["../../../src/bench/external-corpus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAoB,MAAM,mBAAmB,CAAC;AASnF,oFAAoF;AACpF,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,GAAG,GAAG,GAA8B,CAAC;IAC3C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAC5B,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,CAA4B,CAAC;QAC5C,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7D,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,qCAAqC,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,iCAAiC,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,KAAK,4BAA4B,CAAC,CAAC;QACvF,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,CAA4B,CAAC;QAC3C,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,qCAAqC,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,iDAAiD,CAAC,CAAC;QACjF,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,QAAqB,EAAE,CAAC;YAC9C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChD,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,wCAAwC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACzD,OAAO,EAAE,OAAwB;QACjC,OAAO,EAAE,OAAuB;KACjC,CAAC;AACJ,CAAC;AAED,sDAAsD;AACtD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAY;IACnD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,kCAAmC,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,4DAA4D;AAC5D,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAAsB;IAC/D,OAAO,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;AACxD,CAAC;AAED,mFAAmF;AACnF,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE;IACnB,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;eACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC;eAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AAC3B,CAAC,CAAC,EAAE,CAAC;AAEL,IAAI,MAAM,EAAE,CAAC;IACX,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,kFAAkF;IAClF,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC;QACxB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,oBAAoB,CAAC,CAAC;IAEpD,kBAAkB,CAAC,UAAU,CAAC;SAC3B,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;QACrB,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,IAAI,UAAU,EAAE,GAAG,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACzF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,0BAA0B,MAAM,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,CAAQ,EAAE,EAAE;QAClB,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,37 @@
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
+ export interface BenchLesson {
16
+ /** Stored under cachly:lesson:best:{topic} */
17
+ topic: string;
18
+ outcome: 'success' | 'partial' | 'failure';
19
+ what_worked: string;
20
+ what_failed?: string;
21
+ context?: string;
22
+ severity?: 'critical' | 'major' | 'minor';
23
+ confidence?: number;
24
+ recall_count?: number;
25
+ reviewed_by?: string;
26
+ review_level?: 'senior' | 'peer';
27
+ endorsements?: number;
28
+ tags?: string[];
29
+ ts?: string;
30
+ }
31
+ export interface BenchQuery {
32
+ query: string;
33
+ /** Topics that genuinely answer the query (the gold set). */
34
+ relevant: string[];
35
+ }
36
+ export declare const BENCH_LESSONS: BenchLesson[];
37
+ export declare const BENCH_QUERIES: BenchQuery[];