@cachly-dev/mcp-server 0.10.50 → 0.10.52
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/BENCH.md +37 -26
- package/STRATEGY.md +17 -7
- package/dist/src/__tests__/rerank.test.js +18 -1
- package/dist/src/__tests__/rerank.test.js.map +1 -1
- package/dist/src/bench/fixtures.d.ts +3 -0
- package/dist/src/bench/fixtures.js +26 -0
- package/dist/src/bench/fixtures.js.map +1 -1
- package/dist/src/handlers/brain.js +82 -10
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/team.js +56 -4
- package/dist/src/handlers/team.js.map +1 -1
- package/dist/src/rerank.d.ts +7 -2
- package/dist/src/rerank.js +23 -4
- package/dist/src/rerank.js.map +1 -1
- package/dist/src/tools.d.ts +30 -0
- package/dist/src/tools.js +18 -0
- package/dist/src/tools.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/rerank.test.ts +20 -1
- package/src/bench/fixtures.ts +30 -0
- package/src/handlers/brain.ts +75 -10
- package/src/handlers/team.ts +66 -4
- package/src/rerank.ts +29 -4
- package/src/tools.ts +19 -0
package/src/bench/fixtures.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface BenchLesson {
|
|
|
23
23
|
severity?: 'critical' | 'major' | 'minor';
|
|
24
24
|
confidence?: number;
|
|
25
25
|
recall_count?: number;
|
|
26
|
+
reviewed_by?: string;
|
|
27
|
+
review_level?: 'senior' | 'peer';
|
|
28
|
+
endorsements?: number;
|
|
26
29
|
tags?: string[];
|
|
27
30
|
ts?: string;
|
|
28
31
|
}
|
|
@@ -171,6 +174,26 @@ export const BENCH_LESSONS: BenchLesson[] = [
|
|
|
171
174
|
severity: 'minor', confidence: 0.6, recall_count: 1,
|
|
172
175
|
tags: ['nextjs', 'frontend', 'hydration'], ts: daysAgo(25),
|
|
173
176
|
},
|
|
177
|
+
|
|
178
|
+
// ── Governance adversarial pair: two similar-text success lessons ──────────
|
|
179
|
+
// Only the reviewed one should win; baseline cannot distinguish them by text.
|
|
180
|
+
{
|
|
181
|
+
topic: 'auth:csrf:double-submit-cookie',
|
|
182
|
+
outcome: 'success',
|
|
183
|
+
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.',
|
|
184
|
+
context: 'API mutations lacked CSRF protection; security audit required a fix.',
|
|
185
|
+
severity: 'critical', confidence: 0.91, recall_count: 2,
|
|
186
|
+
reviewed_by: 'senior-alice', review_level: 'senior', endorsements: 2,
|
|
187
|
+
tags: ['csrf', 'auth', 'security', 'cookie'], ts: daysAgo(12),
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
topic: 'auth:csrf:origin-header-check',
|
|
191
|
+
outcome: 'success',
|
|
192
|
+
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.',
|
|
193
|
+
context: 'API mutations lacked CSRF protection; origin header check was insufficient.',
|
|
194
|
+
severity: 'major', confidence: 0.55, recall_count: 1,
|
|
195
|
+
tags: ['csrf', 'auth', 'security'], ts: daysAgo(14),
|
|
196
|
+
},
|
|
174
197
|
];
|
|
175
198
|
|
|
176
199
|
export const BENCH_QUERIES: BenchQuery[] = [
|
|
@@ -223,4 +246,11 @@ export const BENCH_QUERIES: BenchQuery[] = [
|
|
|
223
246
|
query: 'postgres performance problem in production',
|
|
224
247
|
relevant: ['db:postgres:connection-pool-exhausted', 'db:postgres:slow-query-missing-index'],
|
|
225
248
|
},
|
|
249
|
+
// Governance adversarial: two CSRF lessons have almost identical text, but only
|
|
250
|
+
// the senior-reviewed canonical one is a proven complete solution. Quality
|
|
251
|
+
// reranking must prefer the reviewed+endorsed lesson over the unreviewed partial.
|
|
252
|
+
{
|
|
253
|
+
query: 'CSRF protection missing on API mutations security',
|
|
254
|
+
relevant: ['auth:csrf:double-submit-cookie'],
|
|
255
|
+
},
|
|
226
256
|
];
|
package/src/handlers/brain.ts
CHANGED
|
@@ -6,17 +6,25 @@ import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge } from '..
|
|
|
6
6
|
import { safeJsonParse } from '../utils.js';
|
|
7
7
|
import type { CKGEdge, CKGNode } from '../ckg.js';
|
|
8
8
|
import { keywordSearch, tokenize, splitMultiQuery, levenshtein,
|
|
9
|
-
|
|
10
|
-
import type { KeywordMatch } from '../search.js';
|
|
9
|
+
indexVocab as _indexVocab } from '../search.js';
|
|
11
10
|
import { rerankByQuality } from '../rerank.js';
|
|
12
|
-
import { computeEmbedding, hasEmbedProvider
|
|
13
|
-
import { detectNamespace } from '../namespace.js';
|
|
11
|
+
import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
|
|
14
12
|
|
|
15
13
|
// ── Changelog (shown once per version in session_start) ──────────────────────
|
|
16
|
-
const MCP_VERSION = '0.10.
|
|
14
|
+
const MCP_VERSION = '0.10.52';
|
|
17
15
|
const WHATS_NEW: Record<string, string[]> = {
|
|
18
|
-
'0.10.
|
|
16
|
+
'0.10.52': [
|
|
19
17
|
`🆕 **What's new in v${MCP_VERSION}:**`,
|
|
18
|
+
` ✅ \`smart_recall\` — CKG traversal as Layer 3: finds lessons that FIXED causal-graph-similar problems (🕸️ causal graph badge)`,
|
|
19
|
+
` ✅ Bench corpus expanded: governance adversarial pair proves review-boost end-to-end (+22% P@1 headline)`,
|
|
20
|
+
` 🔧 Import cleanup: unused symbols removed from brain.ts (no functional change)`,
|
|
21
|
+
` 💡 \`smart_recall\` now shows keyword / semantic / CKG / hybrid match type for every result`,
|
|
22
|
+
],
|
|
23
|
+
'0.10.51': [
|
|
24
|
+
` ✅ \`team_confirm\` — human review raises lesson recall ranking (🛡️ senior / ✔️ peer)`,
|
|
25
|
+
` ✅ Governance-aware recall — confirmed lessons outrank unreviewed auto-learned entries`,
|
|
26
|
+
],
|
|
27
|
+
'0.10.50': [
|
|
20
28
|
` ✅ \`smart_recall\` — unified keyword + semantic hybrid list (one ranked result, not two sections)`,
|
|
21
29
|
` ✅ \`smart_recall\` — "Brain saved you here" banner surfaces time saved inline per proven lesson`,
|
|
22
30
|
` ✅ Quality reranking — proven successes outrank symptom-dense failures (+11% Precision@1)`,
|
|
@@ -589,8 +597,8 @@ export async function handleBrainTool(
|
|
|
589
597
|
// BM25 quality scores (already reranked) are re-normalized to [0,1] for merging.
|
|
590
598
|
type HybridResult = {
|
|
591
599
|
key: string; content: string; hybridScore: number;
|
|
592
|
-
bm25Score?: number; semScore?: number; matchedWords?: string[];
|
|
593
|
-
matchType: 'keyword' | 'semantic' | 'hybrid'; subQuery?: string;
|
|
600
|
+
bm25Score?: number; semScore?: number; ckgScore?: number; matchedWords?: string[];
|
|
601
|
+
matchType: 'keyword' | 'semantic' | 'ckg' | 'hybrid'; subQuery?: string;
|
|
594
602
|
};
|
|
595
603
|
|
|
596
604
|
const bm25Scores = kwMatches.map(m => m.score);
|
|
@@ -620,6 +628,54 @@ export async function handleBrainTool(
|
|
|
620
628
|
});
|
|
621
629
|
}
|
|
622
630
|
}
|
|
631
|
+
// ── Layer 3: CKG traversal — follow "fixes" edges to find lessons that solved
|
|
632
|
+
// structurally similar problems, even when vocabulary differs ───────────────
|
|
633
|
+
try {
|
|
634
|
+
const qTokens = tokenize(query).filter(t => t.length >= 4).slice(0, 4);
|
|
635
|
+
const candidateNodeIds = new Set<string>();
|
|
636
|
+
for (const token of qTokens) {
|
|
637
|
+
const nodeStream = redis.scanStream({ match: `cachly:ckg:node:*${token}*`, count: 20 });
|
|
638
|
+
await new Promise<void>((res, rej) => {
|
|
639
|
+
nodeStream.on('data', (batch: string[]) => {
|
|
640
|
+
for (const k of batch.slice(0, 5)) candidateNodeIds.add(k.replace('cachly:ckg:node:', ''));
|
|
641
|
+
});
|
|
642
|
+
nodeStream.on('end', res);
|
|
643
|
+
nodeStream.on('error', rej);
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const seenFroms = new Set<string>();
|
|
648
|
+
for (const nodeId of [...candidateNodeIds].slice(0, 12)) {
|
|
649
|
+
const inboundEdgeKeys = await redis.smembers(`cachly:ckg:idx:to:${nodeId}`);
|
|
650
|
+
for (const ek of inboundEdgeKeys.slice(0, 6)) {
|
|
651
|
+
const er = await redis.get(ek);
|
|
652
|
+
if (!er) continue;
|
|
653
|
+
const edge = safeJsonParse<CKGEdge | null>(er, null);
|
|
654
|
+
if (!edge || edge.edgeType !== 'fixes' || edge.confidence < 0.35) continue;
|
|
655
|
+
if (seenFroms.has(edge.from)) continue;
|
|
656
|
+
seenFroms.add(edge.from);
|
|
657
|
+
|
|
658
|
+
const lessonKey = `cachly:lesson:best:${edge.from}`;
|
|
659
|
+
const existing = hybridMap.get(lessonKey);
|
|
660
|
+
if (existing) {
|
|
661
|
+
// Already found by BM25/semantic — boost with causal confirmation
|
|
662
|
+
existing.ckgScore = Math.max(existing.ckgScore ?? 0, edge.confidence);
|
|
663
|
+
existing.hybridScore = existing.hybridScore * 0.88 + edge.confidence * 0.12;
|
|
664
|
+
if (existing.matchType === 'keyword') existing.matchType = 'hybrid';
|
|
665
|
+
} else {
|
|
666
|
+
const content = await redis.get(lessonKey).catch(() => null);
|
|
667
|
+
if (!content) continue;
|
|
668
|
+
hybridMap.set(lessonKey, {
|
|
669
|
+
key: lessonKey, content,
|
|
670
|
+
ckgScore: edge.confidence,
|
|
671
|
+
hybridScore: edge.confidence * 0.30, // CKG-only: lower than text hits
|
|
672
|
+
matchType: 'ckg',
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
} catch { /* non-critical — keyword + semantic always available */ }
|
|
678
|
+
|
|
623
679
|
const hybridResults = [...hybridMap.values()].sort((a, b) => b.hybridScore - a.hybridScore);
|
|
624
680
|
|
|
625
681
|
// ── Build output ──────────────────────────────────────────────────────────
|
|
@@ -639,8 +695,13 @@ export async function handleBrainTool(
|
|
|
639
695
|
}
|
|
640
696
|
|
|
641
697
|
if (hybridResults.length > 0) {
|
|
642
|
-
const
|
|
698
|
+
const hasCKG = hybridResults.some(r => r.ckgScore !== undefined);
|
|
699
|
+
const modeLabel = semHits.length > 0 && hasCKG
|
|
700
|
+
? `keyword + semantic + CKG hybrid`
|
|
701
|
+
: semHits.length > 0
|
|
643
702
|
? `keyword + semantic hybrid`
|
|
703
|
+
: hasCKG
|
|
704
|
+
? `keyword + CKG hybrid`
|
|
644
705
|
: `keyword`;
|
|
645
706
|
lines.push(`### 🔍 Results (${hybridResults.length} — ${modeLabel})\n`);
|
|
646
707
|
|
|
@@ -656,7 +717,11 @@ export async function handleBrainTool(
|
|
|
656
717
|
lines.push(`**Topic: "${sq}"** (${results.length} results)\n`);
|
|
657
718
|
for (const r of results.slice(0, 4)) {
|
|
658
719
|
const label = r.key.replace('cachly:ctx:', '📝 ').replace('cachly:lesson:best:', '💡 ').replace('cachly:idx:', '📂 ');
|
|
659
|
-
const scorePart = r.matchType === '
|
|
720
|
+
const scorePart = r.matchType === 'ckg'
|
|
721
|
+
? `CKG: ${(r.ckgScore ?? 0).toFixed(2)}, 🕸️ causal graph`
|
|
722
|
+
: r.ckgScore !== undefined && r.bm25Score !== undefined
|
|
723
|
+
? `BM25: ${(r.bm25Score).toFixed(2)}, CKG: ${(r.ckgScore).toFixed(2)}, 🔀 hybrid`
|
|
724
|
+
: r.matchType === 'hybrid'
|
|
660
725
|
? `BM25: ${(r.bm25Score ?? 0).toFixed(2)}, sem: ${((r.semScore ?? 0) * 100).toFixed(0)}%, 🔀 hybrid`
|
|
661
726
|
: r.matchType === 'semantic'
|
|
662
727
|
? `sem: ${((r.semScore ?? 0) * 100).toFixed(0)}%, 🎯 semantic`
|
package/src/handlers/team.ts
CHANGED
|
@@ -11,7 +11,7 @@ type GetConnection = (instanceId: string) => Promise<Redis>;
|
|
|
11
11
|
type ApiFetch = <T>(path: string, options?: RequestInit) => Promise<T>;
|
|
12
12
|
|
|
13
13
|
export const TEAM_TOOL_NAMES = new Set([
|
|
14
|
-
'sync_file_changes', 'team_learn', 'team_recall', 'team_synthesize', 'memory_crystalize',
|
|
14
|
+
'sync_file_changes', 'team_learn', 'team_confirm', 'team_recall', 'team_synthesize', 'memory_crystalize',
|
|
15
15
|
'brain_doctor', 'recall_at', 'trace_dependency', 'global_learn', 'global_recall',
|
|
16
16
|
'publish_lesson', 'import_public_brain', 'setup_ai_memory',
|
|
17
17
|
]);
|
|
@@ -124,6 +124,59 @@ export async function handleTeamTool(
|
|
|
124
124
|
return `✅ Team lesson stored by **${author}**: \`${topic}\` (${outcome})\n💡 ${what_worked.slice(0, 120)}`;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// ── team_confirm — knowledge governance (Phase 3) ─────────────────────────
|
|
128
|
+
// A human reviewer endorses a stored lesson. The endorsement raises the
|
|
129
|
+
// lesson's recall ranking (see src/rerank.ts), so trusted, reviewed knowledge
|
|
130
|
+
// surfaces above unreviewed auto-learned entries.
|
|
131
|
+
case 'team_confirm': {
|
|
132
|
+
const { instance_id, topic, reviewer, level = 'peer', note = '' } = args as {
|
|
133
|
+
instance_id: string; topic: string; reviewer: string;
|
|
134
|
+
level?: 'senior' | 'peer'; note?: string;
|
|
135
|
+
};
|
|
136
|
+
if (!instance_id) return '❌ instance_id required';
|
|
137
|
+
if (!topic || !reviewer) return '❌ Required: topic, reviewer';
|
|
138
|
+
const reviewLevel = level === 'senior' ? 'senior' : 'peer';
|
|
139
|
+
|
|
140
|
+
const redis = await getConnection(instance_id);
|
|
141
|
+
const key = `cachly:lesson:best:${topic}`;
|
|
142
|
+
const raw = await redis.get(key);
|
|
143
|
+
const lesson = safeJsonParse<Record<string, unknown> | null>(raw, null);
|
|
144
|
+
if (!lesson) {
|
|
145
|
+
return `📭 No best-solution lesson found for \`${topic}\`.\n\nA lesson must exist (store one with \`team_learn\` / \`learn_from_attempts\`) before it can be confirmed.`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Track distinct reviewers so endorsements can't be inflated by one person.
|
|
149
|
+
const endorsersKey = `cachly:lesson:endorsers:${topic}`;
|
|
150
|
+
const added = await redis.sadd(endorsersKey, reviewer);
|
|
151
|
+
await redis.expire(endorsersKey, 365 * 86400);
|
|
152
|
+
const endorsements = await redis.scard(endorsersKey);
|
|
153
|
+
|
|
154
|
+
// Senior beats peer; never downgrade an existing senior review to peer.
|
|
155
|
+
const prevLevel = (lesson.review_level as string) ?? '';
|
|
156
|
+
const effectiveLevel = prevLevel === 'senior' ? 'senior' : reviewLevel;
|
|
157
|
+
|
|
158
|
+
const updated = {
|
|
159
|
+
...lesson,
|
|
160
|
+
reviewed_by: reviewer,
|
|
161
|
+
review_level: effectiveLevel,
|
|
162
|
+
endorsements,
|
|
163
|
+
reviewed_at: new Date().toISOString(),
|
|
164
|
+
};
|
|
165
|
+
await redis.set(key, JSON.stringify(updated));
|
|
166
|
+
|
|
167
|
+
// Append an immutable review record for the audit trail.
|
|
168
|
+
const reviewLog = {
|
|
169
|
+
reviewer, level: reviewLevel, note: note.slice(0, 280), ts: new Date().toISOString(),
|
|
170
|
+
};
|
|
171
|
+
const logKey = `cachly:lesson:reviews:${topic}`;
|
|
172
|
+
await redis.rpush(logKey, JSON.stringify(reviewLog));
|
|
173
|
+
await redis.ltrim(logKey, -50, -1);
|
|
174
|
+
await redis.expire(logKey, 365 * 86400);
|
|
175
|
+
|
|
176
|
+
const badge = effectiveLevel === 'senior' ? '🛡️ senior-reviewed' : '✔️ peer-reviewed';
|
|
177
|
+
return `${badge} — \`${topic}\` confirmed by **${reviewer}** (${endorsements} distinct endorsement${endorsements === 1 ? '' : 's'}).\n📈 This lesson now ranks higher in \`smart_recall\` / \`team_recall\`.${note ? `\n📝 ${note.slice(0, 200)}` : ''}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
127
180
|
// ── team_recall ───────────────────────────────────────────────────────────
|
|
128
181
|
case 'team_recall': {
|
|
129
182
|
const { instance_id, topic, author, limit = 10 } = args as {
|
|
@@ -146,6 +199,7 @@ export async function handleTeamTool(
|
|
|
146
199
|
topic: string; outcome: string; what_worked: string;
|
|
147
200
|
ts: string; severity?: string; recall_count?: number;
|
|
148
201
|
author?: string; tags?: string[];
|
|
202
|
+
reviewed_by?: string; review_level?: string; endorsements?: number;
|
|
149
203
|
};
|
|
150
204
|
let lessons: TeamLesson[] = [];
|
|
151
205
|
if (lessonKeys.length > 0) {
|
|
@@ -169,8 +223,10 @@ export async function handleTeamTool(
|
|
|
169
223
|
lessons = lessons.filter(l => l.author?.toLowerCase().includes(a));
|
|
170
224
|
}
|
|
171
225
|
|
|
172
|
-
// Sort by recall_count desc
|
|
173
|
-
|
|
226
|
+
// Sort: reviewed lessons first (senior > peer), then by recall_count desc.
|
|
227
|
+
const reviewRank = (l: TeamLesson) => l.reviewed_by ? (l.review_level === 'senior' ? 2 : 1) : 0;
|
|
228
|
+
lessons.sort((a, b) =>
|
|
229
|
+
(reviewRank(b) - reviewRank(a)) || ((b.recall_count ?? 0) - (a.recall_count ?? 0)));
|
|
174
230
|
lessons = lessons.slice(0, limit);
|
|
175
231
|
|
|
176
232
|
if (lessons.length === 0) {
|
|
@@ -183,11 +239,17 @@ export async function handleTeamTool(
|
|
|
183
239
|
for (const l of lessons) {
|
|
184
240
|
const emoji = l.outcome === 'success' ? '✅' : l.outcome === 'partial' ? '⚠️' : '❌';
|
|
185
241
|
const sev = l.severity === 'critical' ? '🔴 ' : l.severity === 'major' ? '🟡 ' : '';
|
|
242
|
+
const reviewBadge = l.reviewed_by
|
|
243
|
+
? (l.review_level === 'senior' ? ' 🛡️' : ' ✔️')
|
|
244
|
+
: '';
|
|
186
245
|
const authorStr = l.author ? ` · _by ${l.author}_` : '';
|
|
246
|
+
const reviewStr = l.reviewed_by
|
|
247
|
+
? ` · reviewed by ${l.reviewed_by}${(l.endorsements ?? 0) > 1 ? ` +${(l.endorsements ?? 0) - 1}` : ''}`
|
|
248
|
+
: '';
|
|
187
249
|
const recallStr = (l.recall_count ?? 0) > 0 ? ` · recalled ${l.recall_count}×` : '';
|
|
188
250
|
const ago = Math.round((Date.now() - new Date(l.ts).getTime()) / 86400000);
|
|
189
251
|
const agoStr = ago === 0 ? 'today' : ago === 1 ? 'yesterday' : `${ago}d ago`;
|
|
190
|
-
lines.push(`${emoji} ${sev}**\`${l.topic}\`**${authorStr}${recallStr} · ${agoStr}`);
|
|
252
|
+
lines.push(`${emoji} ${sev}**\`${l.topic}\`**${reviewBadge}${authorStr}${reviewStr}${recallStr} · ${agoStr}`);
|
|
191
253
|
lines.push(` ${l.what_worked.slice(0, 120)}`);
|
|
192
254
|
lines.push('');
|
|
193
255
|
}
|
package/src/rerank.ts
CHANGED
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This module is Cachly's structural advantage made concrete: we re-rank text
|
|
11
11
|
* matches by the *quality* of the underlying lesson — outcome, confidence,
|
|
12
|
-
* proven-ness (recall_count) and
|
|
13
|
-
* surfaces first.
|
|
12
|
+
* proven-ness (recall_count), severity and human review (endorsements) — so the
|
|
13
|
+
* lesson most likely to help surfaces first. A flat-file memory has none of these
|
|
14
|
+
* signals; a senior-confirmed, ten-times-recalled success reads identically to a
|
|
15
|
+
* one-off failed attempt.
|
|
14
16
|
*
|
|
15
17
|
* It is intentionally pure (no I/O): the lesson JSON already travels inside each
|
|
16
18
|
* KeywordMatch.content for `cachly:lesson:best:*` keys, so reranking needs no extra
|
|
@@ -29,6 +31,10 @@ export interface LessonQuality {
|
|
|
29
31
|
severity?: string; // 'critical' | 'major' | 'minor'
|
|
30
32
|
ts?: string; // ISO timestamp (for freshness, mild)
|
|
31
33
|
verified_at?: string;
|
|
34
|
+
// ── Governance (Phase 3): human review raises trust above mere usage ──
|
|
35
|
+
reviewed_by?: string; // handle of the reviewer who endorsed it (latest)
|
|
36
|
+
review_level?: string; // 'senior' | 'peer' (weight differs)
|
|
37
|
+
endorsements?: number; // distinct human endorsements
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export interface RerankedMatch extends KeywordMatch {
|
|
@@ -53,6 +59,14 @@ const SEVERITY_WEIGHT: Record<string, number> = {
|
|
|
53
59
|
};
|
|
54
60
|
const SEVERITY_DEFAULT = 1.0;
|
|
55
61
|
|
|
62
|
+
// Governance: a human-confirmed lesson is more trustworthy than one proven only
|
|
63
|
+
// by automated recall. A senior review weighs more than a peer review; multiple
|
|
64
|
+
// endorsements add a small, saturating bump on top.
|
|
65
|
+
const REVIEW_LEVEL_WEIGHT: Record<string, number> = {
|
|
66
|
+
senior: 1.25,
|
|
67
|
+
peer: 1.1,
|
|
68
|
+
};
|
|
69
|
+
|
|
56
70
|
// Clamp so a strong text match is never fully buried by quality, and a weak text
|
|
57
71
|
// match is never catapulted to the top purely on quality.
|
|
58
72
|
const MIN_MULTIPLIER = 0.5;
|
|
@@ -65,7 +79,9 @@ const LESSON_KEY_PREFIX = 'cachly:lesson:best:';
|
|
|
65
79
|
* Returns 1.0 (neutral) for documents that carry no quality signal (e.g. context).
|
|
66
80
|
*/
|
|
67
81
|
export function qualityMultiplier(lesson: LessonQuality | null): number {
|
|
68
|
-
if (!lesson || (lesson.outcome === undefined && lesson.confidence === undefined
|
|
82
|
+
if (!lesson || (lesson.outcome === undefined && lesson.confidence === undefined
|
|
83
|
+
&& lesson.recall_count === undefined && lesson.reviewed_by === undefined
|
|
84
|
+
&& lesson.endorsements === undefined)) {
|
|
69
85
|
return 1.0;
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -84,7 +100,16 @@ export function qualityMultiplier(lesson: LessonQuality | null): number {
|
|
|
84
100
|
// 4. Severity — when two lessons tie, surface the one guarding the worse failure.
|
|
85
101
|
const severity = SEVERITY_WEIGHT[lesson.severity ?? ''] ?? SEVERITY_DEFAULT;
|
|
86
102
|
|
|
87
|
-
|
|
103
|
+
// 5. Governance — a human-confirmed lesson outranks an unreviewed one.
|
|
104
|
+
// Base weight from the review level, plus a small saturating bump for
|
|
105
|
+
// additional endorsements (rc-style log curve, capped at +0.1).
|
|
106
|
+
const reviewBase = lesson.reviewed_by
|
|
107
|
+
? (REVIEW_LEVEL_WEIGHT[lesson.review_level ?? 'peer'] ?? REVIEW_LEVEL_WEIGHT.peer)
|
|
108
|
+
: 1.0;
|
|
109
|
+
const extraEndorse = Math.max(0, (lesson.endorsements ?? 0) - 1);
|
|
110
|
+
const reviewBoost = reviewBase * (1 + Math.min(0.1, Math.log1p(extraEndorse) / 20));
|
|
111
|
+
|
|
112
|
+
const m = outcome * confidenceFactor * provenBoost * severity * reviewBoost;
|
|
88
113
|
return Math.max(MIN_MULTIPLIER, Math.min(MAX_MULTIPLIER, m));
|
|
89
114
|
}
|
|
90
115
|
|
package/src/tools.ts
CHANGED
|
@@ -840,6 +840,25 @@ const TOOLS = [
|
|
|
840
840
|
required: ['instance_id', 'author', 'topic', 'outcome', 'what_worked'],
|
|
841
841
|
},
|
|
842
842
|
},
|
|
843
|
+
{
|
|
844
|
+
name: 'team_confirm',
|
|
845
|
+
description:
|
|
846
|
+
'Endorse (review-confirm) a team lesson so trusted, human-reviewed knowledge ranks above unreviewed auto-learned entries. ' +
|
|
847
|
+
'A senior review weighs more than a peer review; distinct endorsements add a small boost. ' +
|
|
848
|
+
'Confirmed lessons surface higher in smart_recall and team_recall and carry a 🛡️/✔️ badge. ' +
|
|
849
|
+
'Use this in code review or knowledge reviews to bless the canonical solution for a topic.',
|
|
850
|
+
inputSchema: {
|
|
851
|
+
type: 'object',
|
|
852
|
+
properties: {
|
|
853
|
+
instance_id: { type: 'string', description: 'UUID of the shared team brain instance' },
|
|
854
|
+
topic: { type: 'string', description: 'Topic slug of the lesson to confirm (e.g. "deploy:api")' },
|
|
855
|
+
reviewer: { type: 'string', description: 'Your name or handle (the reviewer endorsing this lesson)' },
|
|
856
|
+
level: { type: 'string', enum: ['senior', 'peer'], description: 'Review weight — "senior" ranks higher than "peer" (default: peer)' },
|
|
857
|
+
note: { type: 'string', description: 'Optional review note (kept in the lesson audit trail)' },
|
|
858
|
+
},
|
|
859
|
+
required: ['instance_id', 'topic', 'reviewer'],
|
|
860
|
+
},
|
|
861
|
+
},
|
|
843
862
|
{
|
|
844
863
|
name: 'team_recall',
|
|
845
864
|
description:
|