bilmem 0.1.0

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.
@@ -0,0 +1,44 @@
1
+ import { K as Knowledge, S as Scope, J as JevResult } from '../types-Nxq5Gzay.js';
2
+
3
+ /**
4
+ * Rebuildable in-memory search index powered by Orama.
5
+ * Note: Orama is strictly a candidate retrieval cache, NEVER the canonical source of truth.
6
+ */
7
+ declare class SearchIndex {
8
+ private db;
9
+ private idToDocId;
10
+ init(): Promise<void>;
11
+ buildIndex(knowledgeList: Knowledge[]): Promise<void>;
12
+ upsert(item: Knowledge): Promise<void>;
13
+ delete(id: string): Promise<void>;
14
+ searchCandidates(term: string, limit?: number): Promise<string[]>;
15
+ size(): number;
16
+ }
17
+
18
+ interface LookupOptions {
19
+ query?: string;
20
+ positive?: string[];
21
+ negative?: string[];
22
+ scope?: Scope;
23
+ limit?: number;
24
+ includeRejected?: boolean;
25
+ }
26
+ interface KnowledgeStoreProvider {
27
+ getKnowledge(id: string): Promise<Knowledge | undefined>;
28
+ getAllKnowledge(): Promise<Knowledge[]>;
29
+ }
30
+ declare class Retriever {
31
+ private index;
32
+ private store;
33
+ constructor(index: SearchIndex, store: KnowledgeStoreProvider);
34
+ /**
35
+ * Executes the full Bilmem retrieval pipeline:
36
+ * 1. Query / Context
37
+ * 2. Candidate retrieval (small candidate set)
38
+ * 3. JEV contextual reranking
39
+ * 4. MATCH / POSSIBLE / REJECT classification with explanations
40
+ */
41
+ lookup(options?: LookupOptions): Promise<JevResult[]>;
42
+ }
43
+
44
+ export { Retriever as BilmemRetriever, type KnowledgeStoreProvider, type LookupOptions, SearchIndex as OramaIndex, Retriever, SearchIndex };
@@ -0,0 +1,301 @@
1
+ // src/search/indexer.ts
2
+ import { create, insert, remove, search } from "@orama/orama";
3
+ var SearchIndex = class {
4
+ db = null;
5
+ idToDocId = /* @__PURE__ */ new Map();
6
+ async init() {
7
+ this.db = await create({
8
+ schema: {
9
+ id: "string",
10
+ statement: "string",
11
+ type: "string",
12
+ scope: "string",
13
+ status: "string",
14
+ positive: "string[]",
15
+ negative: "string[]",
16
+ tags: "string[]"
17
+ }
18
+ });
19
+ this.idToDocId.clear();
20
+ }
21
+ async buildIndex(knowledgeList) {
22
+ await this.init();
23
+ for (const item of knowledgeList) {
24
+ await this.upsert(item);
25
+ }
26
+ }
27
+ async upsert(item) {
28
+ if (!this.db) {
29
+ await this.init();
30
+ }
31
+ const existingDocId = this.idToDocId.get(item.id);
32
+ if (existingDocId) {
33
+ try {
34
+ await remove(this.db, existingDocId);
35
+ } catch {
36
+ }
37
+ this.idToDocId.delete(item.id);
38
+ }
39
+ const docId = await insert(this.db, {
40
+ id: item.id,
41
+ statement: item.statement,
42
+ type: item.type,
43
+ scope: item.scope,
44
+ status: item.status,
45
+ positive: item.positive,
46
+ negative: item.negative,
47
+ tags: item.tags ?? []
48
+ });
49
+ this.idToDocId.set(item.id, docId);
50
+ }
51
+ async delete(id) {
52
+ if (!this.db) return;
53
+ const docId = this.idToDocId.get(id);
54
+ if (docId) {
55
+ try {
56
+ await remove(this.db, docId);
57
+ } catch {
58
+ }
59
+ this.idToDocId.delete(id);
60
+ }
61
+ }
62
+ async searchCandidates(term, limit = 25) {
63
+ if (!this.db) {
64
+ return [];
65
+ }
66
+ const cleanTerm = term.trim();
67
+ if (!cleanTerm) {
68
+ return Array.from(this.idToDocId.keys()).slice(0, limit);
69
+ }
70
+ try {
71
+ const results = await search(this.db, {
72
+ term: cleanTerm,
73
+ limit,
74
+ threshold: 0.2
75
+ });
76
+ return results.hits.map((h) => h.document.id);
77
+ } catch {
78
+ return Array.from(this.idToDocId.keys()).slice(0, limit);
79
+ }
80
+ }
81
+ size() {
82
+ return this.idToDocId.size;
83
+ }
84
+ };
85
+
86
+ // src/jev/scorer.ts
87
+ var statementTokenCache = /* @__PURE__ */ new WeakMap();
88
+ function extractTokens(text) {
89
+ if (!text) return /* @__PURE__ */ new Set();
90
+ return new Set(
91
+ text.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2)
92
+ );
93
+ }
94
+ function getKnowledgeStatementTokens(k) {
95
+ let tokens = statementTokenCache.get(k);
96
+ if (!tokens) {
97
+ tokens = extractTokens(k.statement);
98
+ statementTokenCache.set(k, tokens);
99
+ }
100
+ return tokens;
101
+ }
102
+ function evaluateJev(knowledge, context) {
103
+ const queryTokens = extractTokens(context.query);
104
+ const contextPos = new Set((context.positive ?? []).map((p) => p.trim().toLowerCase()));
105
+ const contextNeg = new Set((context.negative ?? []).map((n) => n.trim().toLowerCase()));
106
+ const knowPos = new Set((knowledge.positive ?? []).map((p) => p.trim().toLowerCase()));
107
+ const knowNeg = new Set((knowledge.negative ?? []).map((n) => n.trim().toLowerCase()));
108
+ const negativeConflicts = [];
109
+ for (const n of knowNeg) {
110
+ if (contextPos.has(n) || queryTokens.has(n)) {
111
+ negativeConflicts.push(`Knowledge forbids "${n}", which was present in context`);
112
+ }
113
+ }
114
+ for (const p of knowPos) {
115
+ if (contextNeg.has(p)) {
116
+ negativeConflicts.push(`Context forbids "${p}", which is required by knowledge`);
117
+ }
118
+ }
119
+ const positiveMatches = [];
120
+ for (const p of knowPos) {
121
+ if (contextPos.has(p)) {
122
+ positiveMatches.push(`Context cue: ${p}`);
123
+ } else {
124
+ for (const q of queryTokens) {
125
+ if (p === q || p.startsWith(q) || q.startsWith(p)) {
126
+ positiveMatches.push(`Query term match: ${p}`);
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ const stmtTokens = getKnowledgeStatementTokens(knowledge);
133
+ let queryOverlapCount = 0;
134
+ for (const q of queryTokens) {
135
+ let matched = false;
136
+ for (const st of stmtTokens) {
137
+ if (q === st || q.startsWith(st) || st.startsWith(q)) {
138
+ matched = true;
139
+ break;
140
+ }
141
+ }
142
+ if (matched) {
143
+ queryOverlapCount++;
144
+ positiveMatches.push(`Statement term: ${q}`);
145
+ }
146
+ }
147
+ const totalCues = Math.max(1, knowPos.size);
148
+ const matchedCuesCount = positiveMatches.filter((m2) => m2.startsWith("Context cue") || m2.startsWith("Query term")).length;
149
+ const cueMatchRatio = matchedCuesCount / totalCues;
150
+ const tokenOverlapScore = queryTokens.size > 0 ? queryOverlapCount / Math.max(1, queryTokens.size) : 0;
151
+ const rawPositiveScore = 0.65 * cueMatchRatio + 0.35 * tokenOverlapScore;
152
+ let scopeMultiplier = 1;
153
+ const targetScope = (context.scope ?? "project").toLowerCase();
154
+ const knowScope = (knowledge.scope ?? "project").toLowerCase();
155
+ if (targetScope === knowScope) {
156
+ scopeMultiplier = 1;
157
+ } else if (knowScope === "global" || targetScope.startsWith(knowScope)) {
158
+ scopeMultiplier = 0.85;
159
+ } else if (knowScope === "project" && targetScope.startsWith("branch:")) {
160
+ scopeMultiplier = 0.9;
161
+ } else {
162
+ scopeMultiplier = 0.3;
163
+ }
164
+ const m = knowledge.metrics;
165
+ const evRatio = (m.support_count + 1) / (m.support_count + 2 * m.contradiction_count + 1);
166
+ const confidenceFactor = knowledge.confidence * evRatio;
167
+ let outcomeAdjustment = 0;
168
+ if (m.successful_outcomes > 0) {
169
+ outcomeAdjustment += Math.min(0.1, m.successful_outcomes * 0.03);
170
+ }
171
+ if (m.failed_outcomes > 0) {
172
+ outcomeAdjustment -= Math.min(0.2, m.failed_outcomes * 0.05);
173
+ }
174
+ let score = (rawPositiveScore * 0.45 + confidenceFactor * 0.4 + outcomeAdjustment) * scopeMultiplier;
175
+ if (knowledge.status === "stale") {
176
+ score *= 0.6;
177
+ } else if (knowledge.status === "contradicted") {
178
+ score *= 0.3;
179
+ } else if (knowledge.status === "invalid" || knowledge.status === "superseded") {
180
+ score *= 0.1;
181
+ }
182
+ let status = "POSSIBLE";
183
+ const reasons = [];
184
+ if (negativeConflicts.length > 0) {
185
+ score = Math.max(0.01, score * 0.2);
186
+ status = "REJECT";
187
+ reasons.push(`Rejected due to ${negativeConflicts.length} negative contextual conflicts.`);
188
+ } else if (score >= 0.45 && positiveMatches.length > 0) {
189
+ status = "MATCH";
190
+ reasons.push(`Strong contextual match with ${positiveMatches.length} matching signals.`);
191
+ } else if (score >= 0.25) {
192
+ status = "POSSIBLE";
193
+ reasons.push(`Potential match; partial contextual overlap.`);
194
+ } else {
195
+ status = "REJECT";
196
+ reasons.push(`Insufficient contextual match score (${score.toFixed(2)} < 0.25).`);
197
+ }
198
+ const finalScore = Math.round(Math.min(1, Math.max(0, score)) * 100) / 100;
199
+ const explanation = {
200
+ positiveMatches,
201
+ negativeConflicts,
202
+ score: finalScore,
203
+ confidence: knowledge.confidence,
204
+ status,
205
+ reasons
206
+ };
207
+ return {
208
+ knowledge,
209
+ status,
210
+ score: finalScore,
211
+ explanation
212
+ };
213
+ }
214
+
215
+ // src/jev/matcher.ts
216
+ function rerankCandidates(candidates, context) {
217
+ const results = [];
218
+ for (const candidate of candidates) {
219
+ const res = evaluateJev(candidate, context);
220
+ results.push(res);
221
+ }
222
+ const statusPriority = {
223
+ MATCH: 3,
224
+ POSSIBLE: 2,
225
+ REJECT: 1
226
+ };
227
+ results.sort((a, b) => {
228
+ const prioDiff = (statusPriority[b.status] ?? 0) - (statusPriority[a.status] ?? 0);
229
+ if (prioDiff !== 0) return prioDiff;
230
+ const scoreDiff = b.score - a.score;
231
+ if (Math.abs(scoreDiff) > 1e-3) return scoreDiff;
232
+ return b.knowledge.confidence - a.knowledge.confidence;
233
+ });
234
+ return results;
235
+ }
236
+
237
+ // src/search/retriever.ts
238
+ var Retriever = class {
239
+ constructor(index, store) {
240
+ this.index = index;
241
+ this.store = store;
242
+ }
243
+ index;
244
+ store;
245
+ /**
246
+ * Executes the full Bilmem retrieval pipeline:
247
+ * 1. Query / Context
248
+ * 2. Candidate retrieval (small candidate set)
249
+ * 3. JEV contextual reranking
250
+ * 4. MATCH / POSSIBLE / REJECT classification with explanations
251
+ */
252
+ async lookup(options = {}) {
253
+ const limit = options.limit ?? 5;
254
+ const query = options.query?.trim() ?? "";
255
+ const positive = options.positive ?? [];
256
+ const negative = options.negative ?? [];
257
+ const scope = options.scope ?? "project";
258
+ const searchTerms = [];
259
+ if (query) searchTerms.push(query);
260
+ if (positive.length > 0) searchTerms.push(...positive.slice(0, 3));
261
+ const searchTerm = searchTerms.join(" ").trim();
262
+ let candidateIds = await this.index.searchCandidates(searchTerm, 30);
263
+ let candidates = [];
264
+ if (candidateIds.length === 0) {
265
+ const all = await this.store.getAllKnowledge();
266
+ candidates = all.filter((k) => k.status !== "invalid" && k.status !== "superseded");
267
+ } else {
268
+ for (const id of candidateIds) {
269
+ const item = await this.store.getKnowledge(id);
270
+ if (item && item.status !== "invalid" && item.status !== "superseded") {
271
+ candidates.push(item);
272
+ }
273
+ }
274
+ if (candidates.length < 5) {
275
+ const all = await this.store.getAllKnowledge();
276
+ for (const k of all) {
277
+ if (!candidateIds.includes(k.id) && k.status !== "invalid" && k.status !== "superseded") {
278
+ candidates.push(k);
279
+ if (candidates.length >= 20) break;
280
+ }
281
+ }
282
+ }
283
+ }
284
+ const queryContext = {
285
+ query,
286
+ positive,
287
+ negative,
288
+ scope
289
+ };
290
+ const reranked = rerankCandidates(candidates, queryContext);
291
+ const filtered = options.includeRejected ? reranked : reranked.filter((r) => r.status !== "REJECT");
292
+ return filtered.slice(0, limit);
293
+ }
294
+ };
295
+ export {
296
+ Retriever as BilmemRetriever,
297
+ SearchIndex as OramaIndex,
298
+ Retriever,
299
+ SearchIndex
300
+ };
301
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/search/indexer.ts","../../src/jev/scorer.ts","../../src/jev/matcher.ts","../../src/search/retriever.ts"],"sourcesContent":["import { create, insert, remove, search, type AnyOrama } from '@orama/orama';\nimport type { Knowledge } from '../core/types.js';\n\n/**\n * Rebuildable in-memory search index powered by Orama.\n * Note: Orama is strictly a candidate retrieval cache, NEVER the canonical source of truth.\n */\nexport class SearchIndex {\n private db: AnyOrama | null = null;\n private idToDocId = new Map<string, string>();\n\n async init(): Promise<void> {\n this.db = await create({\n schema: {\n id: 'string',\n statement: 'string',\n type: 'string',\n scope: 'string',\n status: 'string',\n positive: 'string[]',\n negative: 'string[]',\n tags: 'string[]'\n }\n });\n this.idToDocId.clear();\n }\n\n async buildIndex(knowledgeList: Knowledge[]): Promise<void> {\n await this.init();\n for (const item of knowledgeList) {\n await this.upsert(item);\n }\n }\n\n async upsert(item: Knowledge): Promise<void> {\n if (!this.db) {\n await this.init();\n }\n\n // If already in index, remove first\n const existingDocId = this.idToDocId.get(item.id);\n if (existingDocId) {\n try {\n await remove(this.db!, existingDocId);\n } catch {\n // Ignore if already deleted\n }\n this.idToDocId.delete(item.id);\n }\n\n const docId = await insert(this.db!, {\n id: item.id,\n statement: item.statement,\n type: item.type,\n scope: item.scope,\n status: item.status,\n positive: item.positive,\n negative: item.negative,\n tags: item.tags ?? []\n });\n\n this.idToDocId.set(item.id, docId);\n }\n\n async delete(id: string): Promise<void> {\n if (!this.db) return;\n const docId = this.idToDocId.get(id);\n if (docId) {\n try {\n await remove(this.db, docId);\n } catch {\n // Ignore\n }\n this.idToDocId.delete(id);\n }\n }\n\n async searchCandidates(term: string, limit = 25): Promise<string[]> {\n if (!this.db) {\n return [];\n }\n\n const cleanTerm = term.trim();\n if (!cleanTerm) {\n return Array.from(this.idToDocId.keys()).slice(0, limit);\n }\n\n try {\n const results = await search(this.db, {\n term: cleanTerm,\n limit,\n threshold: 0.2\n });\n\n return results.hits.map((h) => (h.document as any).id as string);\n } catch {\n // Fallback: term might contain special characters or fail tokenization\n return Array.from(this.idToDocId.keys()).slice(0, limit);\n }\n }\n\n size(): number {\n return this.idToDocId.size;\n }\n}\n\nexport { SearchIndex as OramaIndex };\n\n","import type {\n Knowledge,\n JevMatchStatus,\n JevExplanation,\n JevResult,\n Scope\n} from '../core/types.js';\n\nexport interface QueryContext {\n query?: string;\n positive?: string[];\n negative?: string[];\n scope?: Scope;\n}\n\nconst statementTokenCache = new WeakMap<Knowledge, Set<string>>();\n\nfunction extractTokens(text?: string): Set<string> {\n if (!text) return new Set();\n return new Set(\n text\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, ' ')\n .split(/\\s+/)\n .filter((t) => t.length > 2)\n );\n}\n\nfunction getKnowledgeStatementTokens(k: Knowledge): Set<string> {\n let tokens = statementTokenCache.get(k);\n if (!tokens) {\n tokens = extractTokens(k.statement);\n statementTokenCache.set(k, tokens);\n }\n return tokens;\n}\n\n/**\n * Evaluates JEV-style positive and negative contextual proximity for a knowledge unit.\n */\nexport function evaluateJev(\n knowledge: Knowledge,\n context: QueryContext\n): JevResult {\n const queryTokens = extractTokens(context.query);\n const contextPos = new Set((context.positive ?? []).map((p) => p.trim().toLowerCase()));\n const contextNeg = new Set((context.negative ?? []).map((n) => n.trim().toLowerCase()));\n\n const knowPos = new Set((knowledge.positive ?? []).map((p) => p.trim().toLowerCase()));\n const knowNeg = new Set((knowledge.negative ?? []).map((n) => n.trim().toLowerCase()));\n\n // 1. Negative Conflict Detection\n const negativeConflicts: string[] = [];\n\n // Knowledge forbids something present in positive context\n for (const n of knowNeg) {\n if (contextPos.has(n) || queryTokens.has(n)) {\n negativeConflicts.push(`Knowledge forbids \"${n}\", which was present in context`);\n }\n }\n\n // Context forbids something present in knowledge\n for (const p of knowPos) {\n if (contextNeg.has(p)) {\n negativeConflicts.push(`Context forbids \"${p}\", which is required by knowledge`);\n }\n }\n\n // 2. Positive Matches\n const positiveMatches: string[] = [];\n\n // Direct cue overlap\n for (const p of knowPos) {\n if (contextPos.has(p)) {\n positiveMatches.push(`Context cue: ${p}`);\n } else {\n // Check if any query token matches or stems into cue\n for (const q of queryTokens) {\n if (p === q || p.startsWith(q) || q.startsWith(p)) {\n positiveMatches.push(`Query term match: ${p}`);\n break;\n }\n }\n }\n }\n\n // Statement query token overlap\n const stmtTokens = getKnowledgeStatementTokens(knowledge);\n let queryOverlapCount = 0;\n for (const q of queryTokens) {\n let matched = false;\n for (const st of stmtTokens) {\n if (q === st || q.startsWith(st) || st.startsWith(q)) {\n matched = true;\n break;\n }\n }\n if (matched) {\n queryOverlapCount++;\n positiveMatches.push(`Statement term: ${q}`);\n }\n }\n\n // 3. Positive Score Calculation\n const totalCues = Math.max(1, knowPos.size);\n const matchedCuesCount = positiveMatches.filter((m) => m.startsWith('Context cue') || m.startsWith('Query term')).length;\n const cueMatchRatio = matchedCuesCount / totalCues;\n const tokenOverlapScore = queryTokens.size > 0 ? queryOverlapCount / Math.max(1, queryTokens.size) : 0;\n const rawPositiveScore = 0.65 * cueMatchRatio + 0.35 * tokenOverlapScore;\n\n // 4. Scope Alignment\n let scopeMultiplier = 1.0;\n const targetScope = (context.scope ?? 'project').toLowerCase();\n const knowScope = (knowledge.scope ?? 'project').toLowerCase();\n\n if (targetScope === knowScope) {\n scopeMultiplier = 1.0;\n } else if (knowScope === 'global' || targetScope.startsWith(knowScope)) {\n scopeMultiplier = 0.85;\n } else if (knowScope === 'project' && targetScope.startsWith('branch:')) {\n scopeMultiplier = 0.90;\n } else {\n // Mismatched scopes\n scopeMultiplier = 0.30;\n }\n\n // 5. Confidence and Evidence Ratio\n const m = knowledge.metrics;\n const evRatio = (m.support_count + 1) / (m.support_count + 2 * m.contradiction_count + 1);\n const confidenceFactor = knowledge.confidence * evRatio;\n\n // 6. Outcome Boost / Penalty\n let outcomeAdjustment = 0;\n if (m.successful_outcomes > 0) {\n outcomeAdjustment += Math.min(0.10, m.successful_outcomes * 0.03);\n }\n if (m.failed_outcomes > 0) {\n outcomeAdjustment -= Math.min(0.20, m.failed_outcomes * 0.05);\n }\n\n // 7. Base Combined Score\n let score = (rawPositiveScore * 0.45 + confidenceFactor * 0.40 + outcomeAdjustment) * scopeMultiplier;\n\n // Stale or Contradicted Status Penalty\n if (knowledge.status === 'stale') {\n score *= 0.6;\n } else if (knowledge.status === 'contradicted') {\n score *= 0.3;\n } else if (knowledge.status === 'invalid' || knowledge.status === 'superseded') {\n score *= 0.1;\n }\n\n // 8. Negative Conflict Penalty & Decision\n let status: JevMatchStatus = 'POSSIBLE';\n const reasons: string[] = [];\n\n if (negativeConflicts.length > 0) {\n // Severe penalty for negative conflicts\n score = Math.max(0.01, score * 0.2);\n status = 'REJECT';\n reasons.push(`Rejected due to ${negativeConflicts.length} negative contextual conflicts.`);\n } else if (score >= 0.45 && positiveMatches.length > 0) {\n status = 'MATCH';\n reasons.push(`Strong contextual match with ${positiveMatches.length} matching signals.`);\n } else if (score >= 0.25) {\n status = 'POSSIBLE';\n reasons.push(`Potential match; partial contextual overlap.`);\n } else {\n status = 'REJECT';\n reasons.push(`Insufficient contextual match score (${score.toFixed(2)} < 0.25).`);\n }\n\n const finalScore = Math.round(Math.min(1.0, Math.max(0.0, score)) * 100) / 100;\n\n const explanation: JevExplanation = {\n positiveMatches,\n negativeConflicts,\n score: finalScore,\n confidence: knowledge.confidence,\n status,\n reasons\n };\n\n return {\n knowledge,\n status,\n score: finalScore,\n explanation\n };\n}\n","import type { Knowledge, JevResult } from '../core/types.js';\nimport { evaluateJev, type QueryContext } from './scorer.js';\n\n/**\n * Re-ranks a small set of candidate knowledge units using JEV contextual evaluation.\n */\nexport function rerankCandidates(\n candidates: Knowledge[],\n context: QueryContext\n): JevResult[] {\n const results: JevResult[] = [];\n\n for (const candidate of candidates) {\n const res = evaluateJev(candidate, context);\n results.push(res);\n }\n\n // Sort: MATCH first, then POSSIBLE, then REJECT; within each group, highest score first\n const statusPriority: Record<string, number> = {\n MATCH: 3,\n POSSIBLE: 2,\n REJECT: 1\n };\n\n results.sort((a, b) => {\n const prioDiff = (statusPriority[b.status] ?? 0) - (statusPriority[a.status] ?? 0);\n if (prioDiff !== 0) return prioDiff;\n const scoreDiff = b.score - a.score;\n if (Math.abs(scoreDiff) > 0.001) return scoreDiff;\n return b.knowledge.confidence - a.knowledge.confidence;\n });\n\n return results;\n}\n","import type { Knowledge, JevResult, Scope } from '../core/types.js';\nimport { rerankCandidates } from '../jev/matcher.js';\nimport type { QueryContext } from '../jev/scorer.js';\nimport type { SearchIndex } from './indexer.js';\n\nexport interface LookupOptions {\n query?: string;\n positive?: string[];\n negative?: string[];\n scope?: Scope;\n limit?: number;\n includeRejected?: boolean;\n}\n\nexport interface KnowledgeStoreProvider {\n getKnowledge(id: string): Promise<Knowledge | undefined>;\n getAllKnowledge(): Promise<Knowledge[]>;\n}\n\nexport class Retriever {\n constructor(\n private index: SearchIndex,\n private store: KnowledgeStoreProvider\n ) {}\n\n /**\n * Executes the full Bilmem retrieval pipeline:\n * 1. Query / Context\n * 2. Candidate retrieval (small candidate set)\n * 3. JEV contextual reranking\n * 4. MATCH / POSSIBLE / REJECT classification with explanations\n */\n async lookup(options: LookupOptions = {}): Promise<JevResult[]> {\n const limit = options.limit ?? 5;\n const query = options.query?.trim() ?? '';\n const positive = options.positive ?? [];\n const negative = options.negative ?? [];\n const scope = options.scope ?? 'project';\n\n const searchTerms: string[] = [];\n if (query) searchTerms.push(query);\n if (positive.length > 0) searchTerms.push(...positive.slice(0, 3));\n\n const searchTerm = searchTerms.join(' ').trim();\n\n // 1. Retrieve Candidate IDs from Orama\n let candidateIds = await this.index.searchCandidates(searchTerm, 30);\n\n // If no candidate found via search or empty term, get active items from store\n let candidates: Knowledge[] = [];\n if (candidateIds.length === 0) {\n const all = await this.store.getAllKnowledge();\n candidates = all.filter((k) => k.status !== 'invalid' && k.status !== 'superseded');\n } else {\n for (const id of candidateIds) {\n const item = await this.store.getKnowledge(id);\n if (item && item.status !== 'invalid' && item.status !== 'superseded') {\n candidates.push(item);\n }\n }\n\n // If candidates are very few (< 5), broaden candidate pool with active knowledge\n if (candidates.length < 5) {\n const all = await this.store.getAllKnowledge();\n for (const k of all) {\n if (!candidateIds.includes(k.id) && k.status !== 'invalid' && k.status !== 'superseded') {\n candidates.push(k);\n if (candidates.length >= 20) break;\n }\n }\n }\n }\n\n // 2. JEV Reranking on Candidates\n const queryContext: QueryContext = {\n query,\n positive,\n negative,\n scope\n };\n\n const reranked = rerankCandidates(candidates, queryContext);\n\n // 3. Filter by includeRejected and limit\n const filtered = options.includeRejected\n ? reranked\n : reranked.filter((r) => r.status !== 'REJECT');\n\n return filtered.slice(0, limit);\n }\n}\n\nexport { Retriever as BilmemRetriever };\n\n"],"mappings":";AAAA,SAAS,QAAQ,QAAQ,QAAQ,cAA6B;AAOvD,IAAM,cAAN,MAAkB;AAAA,EACf,KAAsB;AAAA,EACtB,YAAY,oBAAI,IAAoB;AAAA,EAE5C,MAAM,OAAsB;AAC1B,SAAK,KAAK,MAAM,OAAO;AAAA,MACrB,QAAQ;AAAA,QACN,IAAI;AAAA,QACJ,WAAW;AAAA,QACX,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,UAAU;AAAA,QACV,MAAM;AAAA,MACR;AAAA,IACF,CAAC;AACD,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEA,MAAM,WAAW,eAA2C;AAC1D,UAAM,KAAK,KAAK;AAChB,eAAW,QAAQ,eAAe;AAChC,YAAM,KAAK,OAAO,IAAI;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,MAAgC;AAC3C,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,KAAK,KAAK;AAAA,IAClB;AAGA,UAAM,gBAAgB,KAAK,UAAU,IAAI,KAAK,EAAE;AAChD,QAAI,eAAe;AACjB,UAAI;AACF,cAAM,OAAO,KAAK,IAAK,aAAa;AAAA,MACtC,QAAQ;AAAA,MAER;AACA,WAAK,UAAU,OAAO,KAAK,EAAE;AAAA,IAC/B;AAEA,UAAM,QAAQ,MAAM,OAAO,KAAK,IAAK;AAAA,MACnC,IAAI,KAAK;AAAA,MACT,WAAW,KAAK;AAAA,MAChB,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,MAAM,KAAK,QAAQ,CAAC;AAAA,IACtB,CAAC;AAED,SAAK,UAAU,IAAI,KAAK,IAAI,KAAK;AAAA,EACnC;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,QAAI,CAAC,KAAK,GAAI;AACd,UAAM,QAAQ,KAAK,UAAU,IAAI,EAAE;AACnC,QAAI,OAAO;AACT,UAAI;AACF,cAAM,OAAO,KAAK,IAAI,KAAK;AAAA,MAC7B,QAAQ;AAAA,MAER;AACA,WAAK,UAAU,OAAO,EAAE;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB,MAAc,QAAQ,IAAuB;AAClE,QAAI,CAAC,KAAK,IAAI;AACZ,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,YAAY,KAAK,KAAK;AAC5B,QAAI,CAAC,WAAW;AACd,aAAO,MAAM,KAAK,KAAK,UAAU,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK;AAAA,IACzD;AAEA,QAAI;AACF,YAAM,UAAU,MAAM,OAAO,KAAK,IAAI;AAAA,QACpC,MAAM;AAAA,QACN;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAED,aAAO,QAAQ,KAAK,IAAI,CAAC,MAAO,EAAE,SAAiB,EAAY;AAAA,IACjE,QAAQ;AAEN,aAAO,MAAM,KAAK,KAAK,UAAU,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK;AAAA,IACzD;AAAA,EACF;AAAA,EAEA,OAAe;AACb,WAAO,KAAK,UAAU;AAAA,EACxB;AACF;;;ACzFA,IAAM,sBAAsB,oBAAI,QAAgC;AAEhE,SAAS,cAAc,MAA4B;AACjD,MAAI,CAAC,KAAM,QAAO,oBAAI,IAAI;AAC1B,SAAO,IAAI;AAAA,IACT,KACG,YAAY,EACZ,QAAQ,gBAAgB,GAAG,EAC3B,MAAM,KAAK,EACX,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA,EAC/B;AACF;AAEA,SAAS,4BAA4B,GAA2B;AAC9D,MAAI,SAAS,oBAAoB,IAAI,CAAC;AACtC,MAAI,CAAC,QAAQ;AACX,aAAS,cAAc,EAAE,SAAS;AAClC,wBAAoB,IAAI,GAAG,MAAM;AAAA,EACnC;AACA,SAAO;AACT;AAKO,SAAS,YACd,WACA,SACW;AACX,QAAM,cAAc,cAAc,QAAQ,KAAK;AAC/C,QAAM,aAAa,IAAI,KAAK,QAAQ,YAAY,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;AACtF,QAAM,aAAa,IAAI,KAAK,QAAQ,YAAY,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;AAEtF,QAAM,UAAU,IAAI,KAAK,UAAU,YAAY,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;AACrF,QAAM,UAAU,IAAI,KAAK,UAAU,YAAY,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;AAGrF,QAAM,oBAA8B,CAAC;AAGrC,aAAW,KAAK,SAAS;AACvB,QAAI,WAAW,IAAI,CAAC,KAAK,YAAY,IAAI,CAAC,GAAG;AAC3C,wBAAkB,KAAK,sBAAsB,CAAC,iCAAiC;AAAA,IACjF;AAAA,EACF;AAGA,aAAW,KAAK,SAAS;AACvB,QAAI,WAAW,IAAI,CAAC,GAAG;AACrB,wBAAkB,KAAK,oBAAoB,CAAC,mCAAmC;AAAA,IACjF;AAAA,EACF;AAGA,QAAM,kBAA4B,CAAC;AAGnC,aAAW,KAAK,SAAS;AACvB,QAAI,WAAW,IAAI,CAAC,GAAG;AACrB,sBAAgB,KAAK,gBAAgB,CAAC,EAAE;AAAA,IAC1C,OAAO;AAEL,iBAAW,KAAK,aAAa;AAC3B,YAAI,MAAM,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG;AACjD,0BAAgB,KAAK,qBAAqB,CAAC,EAAE;AAC7C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa,4BAA4B,SAAS;AACxD,MAAI,oBAAoB;AACxB,aAAW,KAAK,aAAa;AAC3B,QAAI,UAAU;AACd,eAAW,MAAM,YAAY;AAC3B,UAAI,MAAM,MAAM,EAAE,WAAW,EAAE,KAAK,GAAG,WAAW,CAAC,GAAG;AACpD,kBAAU;AACV;AAAA,MACF;AAAA,IACF;AACA,QAAI,SAAS;AACX;AACA,sBAAgB,KAAK,mBAAmB,CAAC,EAAE;AAAA,IAC7C;AAAA,EACF;AAGA,QAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,IAAI;AAC1C,QAAM,mBAAmB,gBAAgB,OAAO,CAACA,OAAMA,GAAE,WAAW,aAAa,KAAKA,GAAE,WAAW,YAAY,CAAC,EAAE;AAClH,QAAM,gBAAgB,mBAAmB;AACzC,QAAM,oBAAoB,YAAY,OAAO,IAAI,oBAAoB,KAAK,IAAI,GAAG,YAAY,IAAI,IAAI;AACrG,QAAM,mBAAmB,OAAO,gBAAgB,OAAO;AAGvD,MAAI,kBAAkB;AACtB,QAAM,eAAe,QAAQ,SAAS,WAAW,YAAY;AAC7D,QAAM,aAAa,UAAU,SAAS,WAAW,YAAY;AAE7D,MAAI,gBAAgB,WAAW;AAC7B,sBAAkB;AAAA,EACpB,WAAW,cAAc,YAAY,YAAY,WAAW,SAAS,GAAG;AACtE,sBAAkB;AAAA,EACpB,WAAW,cAAc,aAAa,YAAY,WAAW,SAAS,GAAG;AACvE,sBAAkB;AAAA,EACpB,OAAO;AAEL,sBAAkB;AAAA,EACpB;AAGA,QAAM,IAAI,UAAU;AACpB,QAAM,WAAW,EAAE,gBAAgB,MAAM,EAAE,gBAAgB,IAAI,EAAE,sBAAsB;AACvF,QAAM,mBAAmB,UAAU,aAAa;AAGhD,MAAI,oBAAoB;AACxB,MAAI,EAAE,sBAAsB,GAAG;AAC7B,yBAAqB,KAAK,IAAI,KAAM,EAAE,sBAAsB,IAAI;AAAA,EAClE;AACA,MAAI,EAAE,kBAAkB,GAAG;AACzB,yBAAqB,KAAK,IAAI,KAAM,EAAE,kBAAkB,IAAI;AAAA,EAC9D;AAGA,MAAI,SAAS,mBAAmB,OAAO,mBAAmB,MAAO,qBAAqB;AAGtF,MAAI,UAAU,WAAW,SAAS;AAChC,aAAS;AAAA,EACX,WAAW,UAAU,WAAW,gBAAgB;AAC9C,aAAS;AAAA,EACX,WAAW,UAAU,WAAW,aAAa,UAAU,WAAW,cAAc;AAC9E,aAAS;AAAA,EACX;AAGA,MAAI,SAAyB;AAC7B,QAAM,UAAoB,CAAC;AAE3B,MAAI,kBAAkB,SAAS,GAAG;AAEhC,YAAQ,KAAK,IAAI,MAAM,QAAQ,GAAG;AAClC,aAAS;AACT,YAAQ,KAAK,mBAAmB,kBAAkB,MAAM,iCAAiC;AAAA,EAC3F,WAAW,SAAS,QAAQ,gBAAgB,SAAS,GAAG;AACtD,aAAS;AACT,YAAQ,KAAK,gCAAgC,gBAAgB,MAAM,oBAAoB;AAAA,EACzF,WAAW,SAAS,MAAM;AACxB,aAAS;AACT,YAAQ,KAAK,8CAA8C;AAAA,EAC7D,OAAO;AACL,aAAS;AACT,YAAQ,KAAK,wCAAwC,MAAM,QAAQ,CAAC,CAAC,WAAW;AAAA,EAClF;AAEA,QAAM,aAAa,KAAK,MAAM,KAAK,IAAI,GAAK,KAAK,IAAI,GAAK,KAAK,CAAC,IAAI,GAAG,IAAI;AAE3E,QAAM,cAA8B;AAAA,IAClC;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,YAAY,UAAU;AAAA,IACtB;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACF;AACF;;;ACvLO,SAAS,iBACd,YACA,SACa;AACb,QAAM,UAAuB,CAAC;AAE9B,aAAW,aAAa,YAAY;AAClC,UAAM,MAAM,YAAY,WAAW,OAAO;AAC1C,YAAQ,KAAK,GAAG;AAAA,EAClB;AAGA,QAAM,iBAAyC;AAAA,IAC7C,OAAO;AAAA,IACP,UAAU;AAAA,IACV,QAAQ;AAAA,EACV;AAEA,UAAQ,KAAK,CAAC,GAAG,MAAM;AACrB,UAAM,YAAY,eAAe,EAAE,MAAM,KAAK,MAAM,eAAe,EAAE,MAAM,KAAK;AAChF,QAAI,aAAa,EAAG,QAAO;AAC3B,UAAM,YAAY,EAAE,QAAQ,EAAE;AAC9B,QAAI,KAAK,IAAI,SAAS,IAAI,KAAO,QAAO;AACxC,WAAO,EAAE,UAAU,aAAa,EAAE,UAAU;AAAA,EAC9C,CAAC;AAED,SAAO;AACT;;;ACdO,IAAM,YAAN,MAAgB;AAAA,EACrB,YACU,OACA,OACR;AAFQ;AACA;AAAA,EACP;AAAA,EAFO;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUV,MAAM,OAAO,UAAyB,CAAC,GAAyB;AAC9D,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,QAAQ,QAAQ,OAAO,KAAK,KAAK;AACvC,UAAM,WAAW,QAAQ,YAAY,CAAC;AACtC,UAAM,WAAW,QAAQ,YAAY,CAAC;AACtC,UAAM,QAAQ,QAAQ,SAAS;AAE/B,UAAM,cAAwB,CAAC;AAC/B,QAAI,MAAO,aAAY,KAAK,KAAK;AACjC,QAAI,SAAS,SAAS,EAAG,aAAY,KAAK,GAAG,SAAS,MAAM,GAAG,CAAC,CAAC;AAEjE,UAAM,aAAa,YAAY,KAAK,GAAG,EAAE,KAAK;AAG9C,QAAI,eAAe,MAAM,KAAK,MAAM,iBAAiB,YAAY,EAAE;AAGnE,QAAI,aAA0B,CAAC;AAC/B,QAAI,aAAa,WAAW,GAAG;AAC7B,YAAM,MAAM,MAAM,KAAK,MAAM,gBAAgB;AAC7C,mBAAa,IAAI,OAAO,CAAC,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,YAAY;AAAA,IACpF,OAAO;AACL,iBAAW,MAAM,cAAc;AAC7B,cAAM,OAAO,MAAM,KAAK,MAAM,aAAa,EAAE;AAC7C,YAAI,QAAQ,KAAK,WAAW,aAAa,KAAK,WAAW,cAAc;AACrE,qBAAW,KAAK,IAAI;AAAA,QACtB;AAAA,MACF;AAGA,UAAI,WAAW,SAAS,GAAG;AACzB,cAAM,MAAM,MAAM,KAAK,MAAM,gBAAgB;AAC7C,mBAAW,KAAK,KAAK;AACnB,cAAI,CAAC,aAAa,SAAS,EAAE,EAAE,KAAK,EAAE,WAAW,aAAa,EAAE,WAAW,cAAc;AACvF,uBAAW,KAAK,CAAC;AACjB,gBAAI,WAAW,UAAU,GAAI;AAAA,UAC/B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,eAA6B;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,WAAW,iBAAiB,YAAY,YAAY;AAG1D,UAAM,WAAW,QAAQ,kBACrB,WACA,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ;AAEhD,WAAO,SAAS,MAAM,GAAG,KAAK;AAAA,EAChC;AACF;","names":["m"]}
@@ -0,0 +1,60 @@
1
+ import { M as MetricEvent, K as KnowledgeHealthCategory, S as Storage, a as KnowledgeStatsReport, b as StatsReport } from '../canonical-CglQQ0xc.js';
2
+ export { E as EffectivenessRatio, J as JevRejectReason } from '../canonical-CglQQ0xc.js';
3
+ import { K as Knowledge } from '../types-Nxq5Gzay.js';
4
+ import '../search/index.js';
5
+
6
+ declare function getEventsFilePath(rootDir: string): string;
7
+ /**
8
+ * Appends a metric event to .bilmem/events.jsonl.
9
+ * Fire-and-forget safe: will not crash caller if filesystem error occurs.
10
+ */
11
+ declare function appendMetricEvent(rootDir: string, event: MetricEvent): Promise<void>;
12
+ /**
13
+ * Reads all metric events from .bilmem/events.jsonl with resilience against
14
+ * corrupted tails, empty lines, and malformed entries.
15
+ */
16
+ declare function readMetricEvents(rootDir: string, filter?: {
17
+ sinceTimestamp?: number;
18
+ knowledgeId?: string;
19
+ }): Promise<MetricEvent[]>;
20
+ /**
21
+ * Safely resets only the metric events log.
22
+ * Preserves all knowledge, evidence, unknowns, rules, and receipts.
23
+ */
24
+ declare function resetMetricEvents(rootDir: string): Promise<void>;
25
+
26
+ declare function classifyKnowledgeHealth(knowledge: Knowledge, knowledgeEvents: MetricEvent[], independentSourcesCount: number): {
27
+ category: KnowledgeHealthCategory;
28
+ reason: string;
29
+ };
30
+ declare function computeStats(storage: Storage, events: MetricEvent[], options?: {
31
+ since?: string;
32
+ sinceTimestamp?: number;
33
+ }): Promise<StatsReport>;
34
+ declare function computeKnowledgeStats(storage: Storage, knowledgeId: string, events: MetricEvent[]): Promise<KnowledgeStatsReport | undefined>;
35
+
36
+ /**
37
+ * Resolves a --since argument into a numeric unix timestamp in milliseconds.
38
+ * Supports relative duration (e.g. 24h, 7d, 30d) or Git revision (e.g. HEAD~20, commit SHA).
39
+ */
40
+ declare function resolveSinceTimestamp(since: string, projectDir?: string): Promise<number>;
41
+
42
+ declare function formatStatsReport(report: StatsReport): string;
43
+ declare function formatKnowledgeStatsReport(report: KnowledgeStatsReport): string;
44
+
45
+ interface RunStatsOptions {
46
+ rootDir?: string;
47
+ since?: string;
48
+ knowledgeId?: string;
49
+ reset?: boolean;
50
+ }
51
+ /**
52
+ * High-level stats runner for CLI and programmatic API.
53
+ */
54
+ declare function runStats(options?: RunStatsOptions): Promise<{
55
+ report?: StatsReport;
56
+ knowledgeReport?: KnowledgeStatsReport;
57
+ reset?: boolean;
58
+ }>;
59
+
60
+ export { KnowledgeHealthCategory, KnowledgeStatsReport, MetricEvent, type RunStatsOptions, StatsReport, appendMetricEvent, classifyKnowledgeHealth, computeKnowledgeStats, computeStats, formatKnowledgeStatsReport, formatStatsReport, getEventsFilePath, readMetricEvents, resetMetricEvents, resolveSinceTimestamp, runStats };