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.
- package/README.md +439 -0
- package/dist/canonical-CglQQ0xc.d.ts +262 -0
- package/dist/cli/index.js +7601 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +311 -0
- package/dist/core/index.js +1975 -0
- package/dist/core/index.js.map +1 -0
- package/dist/filesystem-B3qAej0l.d.ts +69 -0
- package/dist/index.d.ts +163 -0
- package/dist/index.js +7765 -0
- package/dist/index.js.map +1 -0
- package/dist/install/index.d.ts +373 -0
- package/dist/install/index.js +6632 -0
- package/dist/install/index.js.map +1 -0
- package/dist/jev/index.d.ts +32 -0
- package/dist/jev/index.js +269 -0
- package/dist/jev/index.js.map +1 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +5479 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/rules/index.d.ts +59 -0
- package/dist/rules/index.js +388 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/scanner/index.d.ts +169 -0
- package/dist/scanner/index.js +3829 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/search/index.d.ts +44 -0
- package/dist/search/index.js +301 -0
- package/dist/search/index.js.map +1 -0
- package/dist/stats/index.d.ts +60 -0
- package/dist/stats/index.js +1373 -0
- package/dist/stats/index.js.map +1 -0
- package/dist/storage/index.d.ts +82 -0
- package/dist/storage/index.js +1748 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types-Nxq5Gzay.d.ts +390 -0
- package/package.json +71 -0
|
@@ -0,0 +1,1975 @@
|
|
|
1
|
+
// src/core/relations.ts
|
|
2
|
+
var EvidenceGraph = class _EvidenceGraph {
|
|
3
|
+
fromMap = /* @__PURE__ */ new Map();
|
|
4
|
+
toMap = /* @__PURE__ */ new Map();
|
|
5
|
+
constructor(relations = []) {
|
|
6
|
+
for (const rel of relations) {
|
|
7
|
+
this.addRelation(rel);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
addRelation(relation) {
|
|
11
|
+
if (!relation || !relation.from || !relation.to || !relation.type) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const existing = this.getRelationsBetween(relation.from, relation.to);
|
|
15
|
+
const dup = existing.find((r) => r.type === relation.type);
|
|
16
|
+
if (dup) {
|
|
17
|
+
dup.weight = (dup.weight ?? 1) + (relation.weight ?? 1);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const fromList = this.fromMap.get(relation.from) ?? [];
|
|
21
|
+
fromList.push(relation);
|
|
22
|
+
this.fromMap.set(relation.from, fromList);
|
|
23
|
+
const toList = this.toMap.get(relation.to) ?? [];
|
|
24
|
+
toList.push(relation);
|
|
25
|
+
this.toMap.set(relation.to, toList);
|
|
26
|
+
}
|
|
27
|
+
getRelationsFrom(fromId) {
|
|
28
|
+
return this.fromMap.get(fromId) ?? [];
|
|
29
|
+
}
|
|
30
|
+
getRelationsTo(toId) {
|
|
31
|
+
return this.toMap.get(toId) ?? [];
|
|
32
|
+
}
|
|
33
|
+
getRelationsBetween(fromId, toId) {
|
|
34
|
+
const fromList = this.fromMap.get(fromId);
|
|
35
|
+
if (!fromList) return [];
|
|
36
|
+
return fromList.filter((r) => r.to === toId);
|
|
37
|
+
}
|
|
38
|
+
getAllRelations() {
|
|
39
|
+
const all = [];
|
|
40
|
+
for (const list of this.fromMap.values()) {
|
|
41
|
+
all.push(...list);
|
|
42
|
+
}
|
|
43
|
+
return all;
|
|
44
|
+
}
|
|
45
|
+
findCoChanged(file, threshold = 1) {
|
|
46
|
+
const relations = this.getRelationsFrom(file).filter((r) => r.type === "CO_CHANGED");
|
|
47
|
+
const result = [];
|
|
48
|
+
for (const rel of relations) {
|
|
49
|
+
const w = rel.weight ?? 1;
|
|
50
|
+
if (w >= threshold) {
|
|
51
|
+
result.push({ file: rel.to, weight: w });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result.sort((a, b) => b.weight - a.weight);
|
|
55
|
+
}
|
|
56
|
+
findConnected(id, maxDepth = 2) {
|
|
57
|
+
const visited = /* @__PURE__ */ new Set();
|
|
58
|
+
const queue = [{ node: id, depth: 0 }];
|
|
59
|
+
while (queue.length > 0) {
|
|
60
|
+
const { node, depth } = queue.shift();
|
|
61
|
+
if (visited.has(node)) continue;
|
|
62
|
+
visited.add(node);
|
|
63
|
+
if (depth < maxDepth) {
|
|
64
|
+
const outRels = this.getRelationsFrom(node);
|
|
65
|
+
for (const r of outRels) {
|
|
66
|
+
if (!visited.has(r.to)) {
|
|
67
|
+
queue.push({ node: r.to, depth: depth + 1 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const inRels = this.getRelationsTo(node);
|
|
71
|
+
for (const r of inRels) {
|
|
72
|
+
if (!visited.has(r.from)) {
|
|
73
|
+
queue.push({ node: r.from, depth: depth + 1 });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
visited.delete(id);
|
|
79
|
+
return Array.from(visited);
|
|
80
|
+
}
|
|
81
|
+
toJSON() {
|
|
82
|
+
return this.getAllRelations();
|
|
83
|
+
}
|
|
84
|
+
static fromJSON(relations) {
|
|
85
|
+
return new _EvidenceGraph(relations);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/core/knowledge.ts
|
|
90
|
+
import { createHash } from "crypto";
|
|
91
|
+
|
|
92
|
+
// src/core/quarantine.ts
|
|
93
|
+
function determineInitialStage(knowledge) {
|
|
94
|
+
if (knowledge.type === "fact" || knowledge.type === "architecture" || knowledge.confidenceTier === "high" || knowledge.confidence !== void 0 && knowledge.confidence >= 0.85) {
|
|
95
|
+
return "active";
|
|
96
|
+
}
|
|
97
|
+
if (knowledge.type === "rule" && ((knowledge.evidence?.length ?? 0) >= 2 || knowledge.confidence !== void 0 && knowledge.confidence >= 0.75)) {
|
|
98
|
+
return "probation";
|
|
99
|
+
}
|
|
100
|
+
if (knowledge.confidence !== void 0 && knowledge.confidence >= 0.7) {
|
|
101
|
+
return "probation";
|
|
102
|
+
}
|
|
103
|
+
return "candidate";
|
|
104
|
+
}
|
|
105
|
+
function evaluatePromotion(knowledge) {
|
|
106
|
+
const stage = knowledge.stage ?? "candidate";
|
|
107
|
+
const supportCount = knowledge.metrics.support_count;
|
|
108
|
+
const reuseCount = knowledge.metrics.reuse_count;
|
|
109
|
+
const successCount = Math.max(knowledge.outcomesSummary?.successful ?? 0, knowledge.metrics.successful_outcomes ?? 0);
|
|
110
|
+
const misleadingCount = Math.max(knowledge.outcomesSummary?.misleading ?? 0, knowledge.metrics.failed_outcomes ?? 0);
|
|
111
|
+
if (stage === "candidate") {
|
|
112
|
+
if (supportCount >= 2 || knowledge.evidence && knowledge.evidence.length >= 2 || knowledge.confidence >= 0.85) {
|
|
113
|
+
return {
|
|
114
|
+
promoted: true,
|
|
115
|
+
newStage: "probation",
|
|
116
|
+
reason: `Supported by ${supportCount} sources / ${knowledge.evidence.length} evidence nodes`
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
} else if (stage === "probation") {
|
|
120
|
+
if (successCount > 0 && misleadingCount === 0 || reuseCount >= 2 && misleadingCount === 0) {
|
|
121
|
+
return {
|
|
122
|
+
promoted: true,
|
|
123
|
+
newStage: "active",
|
|
124
|
+
reason: `Verified by ${successCount} successful task outcomes and ${reuseCount} reuses`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { promoted: false, newStage: stage };
|
|
129
|
+
}
|
|
130
|
+
function calculateKnowledgeValue(knowledge) {
|
|
131
|
+
const reuse = knowledge.metrics.reuse_count;
|
|
132
|
+
const success = knowledge.outcomesSummary?.successful ?? knowledge.metrics.successful_outcomes;
|
|
133
|
+
const misleading = knowledge.outcomesSummary?.misleading ?? knowledge.metrics.failed_outcomes;
|
|
134
|
+
const evidenceCount = Math.min(knowledge.evidence.length, 6);
|
|
135
|
+
const confidence = knowledge.confidence;
|
|
136
|
+
let value = reuse * 2.5 + success * 4 + evidenceCount * 1.5 + confidence * 2 - misleading * 5;
|
|
137
|
+
if (knowledge.status === "active") value += 5;
|
|
138
|
+
if (knowledge.status === "needs_revalidation") value -= 3;
|
|
139
|
+
if (knowledge.status === "contradicted") value -= 8;
|
|
140
|
+
if (knowledge.status === "stale") value -= 4;
|
|
141
|
+
if (knowledge.stage === "active") value += 3;
|
|
142
|
+
if (knowledge.stage === "candidate") value -= 1;
|
|
143
|
+
return Math.round(value * 10) / 10;
|
|
144
|
+
}
|
|
145
|
+
function recordOutcomeOnKnowledge(knowledge, outcome) {
|
|
146
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
147
|
+
const summary = knowledge.outcomesSummary ?? {
|
|
148
|
+
applied: 0,
|
|
149
|
+
successful: 0,
|
|
150
|
+
misleading: 0,
|
|
151
|
+
neutral: 0
|
|
152
|
+
};
|
|
153
|
+
summary.applied++;
|
|
154
|
+
let actionTaken;
|
|
155
|
+
if (outcome.result === "success") {
|
|
156
|
+
summary.successful++;
|
|
157
|
+
knowledge.metrics.successful_outcomes++;
|
|
158
|
+
knowledge.metrics.reuse_count++;
|
|
159
|
+
const promo = evaluatePromotion({ ...knowledge, outcomesSummary: summary });
|
|
160
|
+
if (promo.promoted) {
|
|
161
|
+
knowledge.stage = promo.newStage;
|
|
162
|
+
actionTaken = `Promoted to ${promo.newStage} (${promo.reason})`;
|
|
163
|
+
const history = knowledge.history ?? [];
|
|
164
|
+
history.push({
|
|
165
|
+
timestamp: now,
|
|
166
|
+
status: knowledge.status,
|
|
167
|
+
action: "promote",
|
|
168
|
+
reason: promo.reason
|
|
169
|
+
});
|
|
170
|
+
knowledge.history = history;
|
|
171
|
+
}
|
|
172
|
+
} else if (outcome.result === "failure" || outcome.result === "reverted") {
|
|
173
|
+
summary.misleading++;
|
|
174
|
+
knowledge.metrics.failed_outcomes++;
|
|
175
|
+
if (summary.misleading >= 2 && summary.misleading > summary.successful) {
|
|
176
|
+
knowledge.status = "needs_revalidation";
|
|
177
|
+
actionTaken = `Marked needs_revalidation: ${summary.misleading} misleading outcomes recorded`;
|
|
178
|
+
const history = knowledge.history ?? [];
|
|
179
|
+
history.push({
|
|
180
|
+
timestamp: now,
|
|
181
|
+
status: "needs_revalidation",
|
|
182
|
+
action: "demote",
|
|
183
|
+
reason: actionTaken
|
|
184
|
+
});
|
|
185
|
+
knowledge.history = history;
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
summary.neutral++;
|
|
189
|
+
}
|
|
190
|
+
knowledge.outcomesSummary = summary;
|
|
191
|
+
knowledge.value = calculateKnowledgeValue(knowledge);
|
|
192
|
+
knowledge.updatedAt = now;
|
|
193
|
+
return { updatedKnowledge: knowledge, actionTaken };
|
|
194
|
+
}
|
|
195
|
+
function enforceKnowledgeBudget(knowledgeList, budget) {
|
|
196
|
+
if (!budget || !budget.active || budget.active <= 0) {
|
|
197
|
+
return { modifiedList: knowledgeList, demotedCount: 0 };
|
|
198
|
+
}
|
|
199
|
+
const activeItems = knowledgeList.filter((k) => k.status === "active" && k.stage === "active");
|
|
200
|
+
if (activeItems.length <= budget.active) {
|
|
201
|
+
return { modifiedList: knowledgeList, demotedCount: 0 };
|
|
202
|
+
}
|
|
203
|
+
activeItems.sort((a, b) => calculateKnowledgeValue(a) - calculateKnowledgeValue(b));
|
|
204
|
+
const excessCount = activeItems.length - budget.active;
|
|
205
|
+
const toDemote = new Set(activeItems.slice(0, excessCount).map((k) => k.id));
|
|
206
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
207
|
+
const modifiedList = knowledgeList.map((k) => {
|
|
208
|
+
if (toDemote.has(k.id)) {
|
|
209
|
+
const history = k.history ?? [];
|
|
210
|
+
k.stage = "probation";
|
|
211
|
+
k.status = "probation";
|
|
212
|
+
k.updatedAt = now;
|
|
213
|
+
k.history = [
|
|
214
|
+
...history,
|
|
215
|
+
{
|
|
216
|
+
timestamp: now,
|
|
217
|
+
stage: "probation",
|
|
218
|
+
status: "probation",
|
|
219
|
+
action: "budget_demotion",
|
|
220
|
+
reason: `Active items exceeded budget (${budget.active}). Demoted to probation based on knowledge value.`
|
|
221
|
+
}
|
|
222
|
+
];
|
|
223
|
+
return k;
|
|
224
|
+
}
|
|
225
|
+
return k;
|
|
226
|
+
});
|
|
227
|
+
return { modifiedList, demotedCount: excessCount };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/core/knowledge.ts
|
|
231
|
+
function tierToConfidence(tier) {
|
|
232
|
+
switch (tier) {
|
|
233
|
+
case "low":
|
|
234
|
+
return 0.5;
|
|
235
|
+
case "medium":
|
|
236
|
+
return 0.75;
|
|
237
|
+
case "high":
|
|
238
|
+
return 0.95;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function confidenceToTier(score) {
|
|
242
|
+
if (score >= 0.85) return "high";
|
|
243
|
+
if (score >= 0.65) return "medium";
|
|
244
|
+
return "low";
|
|
245
|
+
}
|
|
246
|
+
function hashString(input) {
|
|
247
|
+
return createHash("sha256").update(input.trim().toLowerCase()).digest("hex").slice(0, 16);
|
|
248
|
+
}
|
|
249
|
+
function createObservation(params) {
|
|
250
|
+
const provenance = params.provenance ?? {
|
|
251
|
+
type: "file",
|
|
252
|
+
path: params.source,
|
|
253
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
254
|
+
};
|
|
255
|
+
const contentForHash = `${params.source}:${params.claim}:${provenance.commit || provenance.timestamp || Date.now()}`;
|
|
256
|
+
const id = `obs_${hashString(contentForHash)}`;
|
|
257
|
+
return {
|
|
258
|
+
id,
|
|
259
|
+
source: params.source,
|
|
260
|
+
claim: params.claim.trim(),
|
|
261
|
+
provenance,
|
|
262
|
+
confidence: Math.min(1, Math.max(0, params.confidence ?? 0.8)),
|
|
263
|
+
positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),
|
|
264
|
+
negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),
|
|
265
|
+
details: params.details,
|
|
266
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function createEvidence(observation, type = "supports") {
|
|
270
|
+
const sourceHash = hashString(observation.source);
|
|
271
|
+
const id = `evi_${sourceHash}`;
|
|
272
|
+
return {
|
|
273
|
+
id,
|
|
274
|
+
source: observation.source,
|
|
275
|
+
sourceHash,
|
|
276
|
+
observationIds: [observation.id],
|
|
277
|
+
type,
|
|
278
|
+
claim: observation.claim,
|
|
279
|
+
provenance: observation.provenance,
|
|
280
|
+
weight: 1,
|
|
281
|
+
firstObservedAt: observation.recordedAt,
|
|
282
|
+
lastObservedAt: observation.recordedAt
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function createKnowledge(params) {
|
|
286
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
287
|
+
const confidenceTier = params.confidenceTier ?? (params.confidence !== void 0 ? confidenceToTier(params.confidence) : void 0);
|
|
288
|
+
const confidence = params.confidence !== void 0 ? params.confidence : confidenceTier ? tierToConfidence(confidenceTier) : 0.85;
|
|
289
|
+
const dependsOn = new Set(params.depends_on ?? []);
|
|
290
|
+
if (params.evidence) {
|
|
291
|
+
for (const ev of params.evidence) {
|
|
292
|
+
if (ev.startsWith("file:") || ev.startsWith("test:") || ev.startsWith("code:")) {
|
|
293
|
+
dependsOn.add(ev);
|
|
294
|
+
} else if (ev.includes("/") || ev.endsWith(".json") || ev.endsWith(".yaml") || ev.endsWith(".yml")) {
|
|
295
|
+
dependsOn.add(`file:${ev}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const initialStage = params.stage ?? determineInitialStage({
|
|
300
|
+
type: params.type ?? "lesson",
|
|
301
|
+
evidence: params.evidence ?? [],
|
|
302
|
+
confidence
|
|
303
|
+
});
|
|
304
|
+
const status = params.status ?? (initialStage === "candidate" ? "candidate" : "active");
|
|
305
|
+
const history = params.history ?? [
|
|
306
|
+
{
|
|
307
|
+
timestamp: now,
|
|
308
|
+
stage: initialStage,
|
|
309
|
+
status,
|
|
310
|
+
reason: `Initial knowledge recorded (${params.type ?? "lesson"})`,
|
|
311
|
+
source: params.evidence?.[0]
|
|
312
|
+
}
|
|
313
|
+
];
|
|
314
|
+
const knowledgeItem = {
|
|
315
|
+
id: params.id.trim(),
|
|
316
|
+
type: params.type ?? "lesson",
|
|
317
|
+
status,
|
|
318
|
+
stage: initialStage,
|
|
319
|
+
statement: params.statement.trim(),
|
|
320
|
+
positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),
|
|
321
|
+
negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),
|
|
322
|
+
relations: Array.from(new Set(params.relations?.map((r) => r.trim()) ?? [])),
|
|
323
|
+
depends_on: Array.from(dependsOn),
|
|
324
|
+
resolves: params.resolves ? Array.from(new Set(params.resolves.map((r) => r.trim()))) : void 0,
|
|
325
|
+
question: params.question?.trim(),
|
|
326
|
+
confidence: Math.min(1, Math.max(0, confidence)),
|
|
327
|
+
confidenceTier,
|
|
328
|
+
evidence: Array.from(new Set(params.evidence ?? [])),
|
|
329
|
+
scope: params.scope ?? "project",
|
|
330
|
+
tags: Array.from(new Set(params.tags?.map((t) => t.trim().toLowerCase()) ?? [])),
|
|
331
|
+
temporal: {
|
|
332
|
+
revision: 1
|
|
333
|
+
},
|
|
334
|
+
metrics: {
|
|
335
|
+
reuse_count: 0,
|
|
336
|
+
support_count: 1,
|
|
337
|
+
contradiction_count: 0,
|
|
338
|
+
successful_outcomes: 0,
|
|
339
|
+
failed_outcomes: 0
|
|
340
|
+
},
|
|
341
|
+
outcomesSummary: params.outcomesSummary ?? {
|
|
342
|
+
applied: 0,
|
|
343
|
+
successful: 0,
|
|
344
|
+
misleading: 0,
|
|
345
|
+
neutral: 0
|
|
346
|
+
},
|
|
347
|
+
history,
|
|
348
|
+
value: 0,
|
|
349
|
+
createdAt: now,
|
|
350
|
+
updatedAt: now
|
|
351
|
+
};
|
|
352
|
+
knowledgeItem.value = calculateKnowledgeValue(knowledgeItem);
|
|
353
|
+
return knowledgeItem;
|
|
354
|
+
}
|
|
355
|
+
function calculateEffectiveConfidence(knowledge) {
|
|
356
|
+
const m = knowledge.metrics;
|
|
357
|
+
const base = knowledge.confidence;
|
|
358
|
+
const evRatio = (m.support_count + 1) / (m.support_count + 3 * m.contradiction_count + 1);
|
|
359
|
+
const totalOutcomes = m.successful_outcomes + m.failed_outcomes;
|
|
360
|
+
let outRatio = 1;
|
|
361
|
+
let evWeight = 0.6;
|
|
362
|
+
let outWeight = 0.4;
|
|
363
|
+
if (totalOutcomes === 0) {
|
|
364
|
+
evWeight = 0.85;
|
|
365
|
+
outWeight = 0.15;
|
|
366
|
+
} else {
|
|
367
|
+
outRatio = (m.successful_outcomes + 1) / (totalOutcomes + 1);
|
|
368
|
+
}
|
|
369
|
+
const effective = base * (evWeight * evRatio + outWeight * outRatio);
|
|
370
|
+
return Math.round(Math.min(1, Math.max(0.05, effective)) * 100) / 100;
|
|
371
|
+
}
|
|
372
|
+
var knowledgeTokenCache = /* @__PURE__ */ new WeakMap();
|
|
373
|
+
function tokenize(text) {
|
|
374
|
+
return new Set(
|
|
375
|
+
text.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2)
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
function getCachedTokens(k) {
|
|
379
|
+
let tokens = knowledgeTokenCache.get(k);
|
|
380
|
+
if (!tokens) {
|
|
381
|
+
tokens = tokenize(k.statement);
|
|
382
|
+
knowledgeTokenCache.set(k, tokens);
|
|
383
|
+
}
|
|
384
|
+
return tokens;
|
|
385
|
+
}
|
|
386
|
+
function jaccardSimilarity(setA, setB) {
|
|
387
|
+
if (setA.size === 0 && setB.size === 0) return 1;
|
|
388
|
+
let intersection = 0;
|
|
389
|
+
for (const item of setA) {
|
|
390
|
+
if (setB.has(item)) intersection++;
|
|
391
|
+
}
|
|
392
|
+
const union = setA.size + setB.size - intersection;
|
|
393
|
+
return union === 0 ? 0 : intersection / union;
|
|
394
|
+
}
|
|
395
|
+
var POLAR_ANTONYMS = [
|
|
396
|
+
["enabled", "disabled"],
|
|
397
|
+
["enable", "disable"],
|
|
398
|
+
["active", "inactive"],
|
|
399
|
+
["true", "false"],
|
|
400
|
+
["on", "off"],
|
|
401
|
+
["yes", "no"],
|
|
402
|
+
["allowed", "disallowed"],
|
|
403
|
+
["allowed", "prohibited"],
|
|
404
|
+
["allowed", "forbidden"],
|
|
405
|
+
["permit", "deny"],
|
|
406
|
+
["allow", "forbid"],
|
|
407
|
+
["allow", "prohibit"],
|
|
408
|
+
["always", "never"],
|
|
409
|
+
["should", "should not"],
|
|
410
|
+
["must", "must not"],
|
|
411
|
+
["required", "optional"],
|
|
412
|
+
["required", "forbidden"],
|
|
413
|
+
["valid", "invalid"],
|
|
414
|
+
["safe", "unsafe"],
|
|
415
|
+
["sync", "async"],
|
|
416
|
+
["synchronous", "asynchronous"],
|
|
417
|
+
["public", "private"]
|
|
418
|
+
];
|
|
419
|
+
var NEGATION_PATTERNS = [
|
|
420
|
+
/\bnot\b/i,
|
|
421
|
+
/\bnever\b/i,
|
|
422
|
+
/\bno\b/i,
|
|
423
|
+
/\bcannot\b/i,
|
|
424
|
+
/\bwithout\b/i,
|
|
425
|
+
/\bdisallow\b/i,
|
|
426
|
+
/\bforbid\b/i,
|
|
427
|
+
/\bprohibit\b/i,
|
|
428
|
+
/\bavoid\b/i
|
|
429
|
+
];
|
|
430
|
+
function detectPolarContradiction(stmtA, stmtB) {
|
|
431
|
+
const lowerA = stmtA.toLowerCase();
|
|
432
|
+
const lowerB = stmtB.toLowerCase();
|
|
433
|
+
for (const [pos, neg] of POLAR_ANTONYMS) {
|
|
434
|
+
const aHasPos = new RegExp(`\\b${pos}\\b`, "i").test(lowerA);
|
|
435
|
+
const aHasNeg = new RegExp(`\\b${neg}\\b`, "i").test(lowerA);
|
|
436
|
+
const bHasPos = new RegExp(`\\b${pos}\\b`, "i").test(lowerB);
|
|
437
|
+
const bHasNeg = new RegExp(`\\b${neg}\\b`, "i").test(lowerB);
|
|
438
|
+
if (aHasPos && bHasNeg || aHasNeg && bHasPos) {
|
|
439
|
+
return {
|
|
440
|
+
contradicted: true,
|
|
441
|
+
reason: `Polar opposite terms detected: '${pos}' vs '${neg}'`
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const aHasNegation = NEGATION_PATTERNS.some((p) => p.test(lowerA));
|
|
446
|
+
const bHasNegation = NEGATION_PATTERNS.some((p) => p.test(lowerB));
|
|
447
|
+
if (aHasNegation !== bHasNegation) {
|
|
448
|
+
return {
|
|
449
|
+
contradicted: true,
|
|
450
|
+
reason: "Asymmetric negation detected (one statement asserts, the other negates)"
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
return { contradicted: false };
|
|
454
|
+
}
|
|
455
|
+
function reconcileKnowledge(candidate, existingList) {
|
|
456
|
+
const candidateId = candidate.id?.trim();
|
|
457
|
+
const candidateStmt = candidate.statement.trim();
|
|
458
|
+
const candidateTokens = tokenize(candidateStmt);
|
|
459
|
+
const candidatePos = new Set((candidate.positive ?? []).map((p) => p.trim().toLowerCase()));
|
|
460
|
+
if (candidateId) {
|
|
461
|
+
const directMatch = existingList.find((k) => k.id === candidateId);
|
|
462
|
+
if (directMatch) {
|
|
463
|
+
if (directMatch.statement.trim().toLowerCase() === candidateStmt.toLowerCase()) {
|
|
464
|
+
const hasNewEvidence = (candidate.evidence ?? []).some((e) => !directMatch.evidence.includes(e));
|
|
465
|
+
if (!hasNewEvidence) {
|
|
466
|
+
return {
|
|
467
|
+
action: "IGNORE",
|
|
468
|
+
targetId: directMatch.id,
|
|
469
|
+
knowledge: directMatch,
|
|
470
|
+
reason: "Identical statement and evidence already recorded"
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
action: "SUPPORT",
|
|
475
|
+
targetId: directMatch.id,
|
|
476
|
+
knowledge: directMatch,
|
|
477
|
+
reason: "Confirms existing knowledge with new evidence source"
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const opposingPositive = (candidate.negative ?? []).some((n) => directMatch.positive.includes(n));
|
|
481
|
+
const opposingNegative = (candidate.positive ?? []).some((p) => directMatch.negative.includes(p));
|
|
482
|
+
const polarCheck = detectPolarContradiction(candidateStmt, directMatch.statement);
|
|
483
|
+
if (opposingPositive || opposingNegative || polarCheck.contradicted) {
|
|
484
|
+
const distinctCues = Array.from(candidatePos).filter((c) => !directMatch.positive.includes(c));
|
|
485
|
+
const mentionsEnvA = /(development|dev|local|staging|test)/i.test(candidateStmt);
|
|
486
|
+
const mentionsEnvB = /(production|prod)/i.test(directMatch.statement);
|
|
487
|
+
const mentionsEnvBDev = /(development|dev|local|staging|test)/i.test(directMatch.statement);
|
|
488
|
+
const mentionsEnvAProd = /(production|prod)/i.test(candidateStmt);
|
|
489
|
+
if (distinctCues.length > 0 || mentionsEnvA && mentionsEnvB || mentionsEnvBDev && mentionsEnvAProd) {
|
|
490
|
+
return {
|
|
491
|
+
action: "SPECIALIZE",
|
|
492
|
+
targetId: directMatch.id,
|
|
493
|
+
knowledge: directMatch,
|
|
494
|
+
reason: `Conflicting claim applies under specialized context cues: [${distinctCues.join(", ")}] (${polarCheck.reason || "opposing polarity"})`
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
action: "CONTRADICT",
|
|
499
|
+
targetId: directMatch.id,
|
|
500
|
+
knowledge: directMatch,
|
|
501
|
+
reason: `Contradicts existing statement (${polarCheck.reason || "opposing polarity"}) for ID "${directMatch.id}"`
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
action: "UPDATE",
|
|
506
|
+
targetId: directMatch.id,
|
|
507
|
+
knowledge: directMatch,
|
|
508
|
+
reason: "Refines or updates existing knowledge statement"
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
let bestMatch = null;
|
|
513
|
+
let bestSim = 0;
|
|
514
|
+
let bestPolarCheck = { contradicted: false };
|
|
515
|
+
for (const existing of existingList) {
|
|
516
|
+
const existingTokens = getCachedTokens(existing);
|
|
517
|
+
const sim = jaccardSimilarity(candidateTokens, existingTokens);
|
|
518
|
+
const polarCheck = detectPolarContradiction(candidateStmt, existing.statement);
|
|
519
|
+
if (polarCheck.contradicted) {
|
|
520
|
+
let sharedCount = 0;
|
|
521
|
+
for (const t of candidateTokens) {
|
|
522
|
+
if (existingTokens.has(t)) sharedCount++;
|
|
523
|
+
}
|
|
524
|
+
const sharedCues = (candidate.positive ?? []).filter((p) => existing.positive.includes(p));
|
|
525
|
+
if (sharedCount >= 2 || sharedCues.length >= 1 && sim >= 0.35 || sim >= 0.4) {
|
|
526
|
+
bestMatch = existing;
|
|
527
|
+
bestSim = Math.max(sim, 0.85);
|
|
528
|
+
bestPolarCheck = polarCheck;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (sim > bestSim) {
|
|
533
|
+
bestSim = sim;
|
|
534
|
+
bestMatch = existing;
|
|
535
|
+
bestPolarCheck = polarCheck;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (bestMatch && bestSim >= 0.7) {
|
|
539
|
+
const polarCheck = bestPolarCheck.contradicted ? bestPolarCheck : detectPolarContradiction(candidateStmt, bestMatch.statement);
|
|
540
|
+
const hasOpposingCues = (candidate.negative ?? []).some((n) => bestMatch.positive.includes(n)) || (candidate.positive ?? []).some((p) => bestMatch.negative.includes(p));
|
|
541
|
+
if (polarCheck.contradicted || hasOpposingCues) {
|
|
542
|
+
const distinctCues = Array.from(candidatePos).filter((c) => !bestMatch.positive.includes(c));
|
|
543
|
+
const mentionsEnvA = /(development|dev|local|staging|test)/i.test(candidateStmt);
|
|
544
|
+
const mentionsEnvB = /(production|prod)/i.test(bestMatch.statement);
|
|
545
|
+
const mentionsEnvBDev = /(development|dev|local|staging|test)/i.test(bestMatch.statement);
|
|
546
|
+
const mentionsEnvAProd = /(production|prod)/i.test(candidateStmt);
|
|
547
|
+
if (distinctCues.length > 0 || mentionsEnvA && mentionsEnvB || mentionsEnvBDev && mentionsEnvAProd) {
|
|
548
|
+
return {
|
|
549
|
+
action: "SPECIALIZE",
|
|
550
|
+
targetId: bestMatch.id,
|
|
551
|
+
knowledge: bestMatch,
|
|
552
|
+
reason: `High textual similarity (${Math.round(bestSim * 100)}%) with polar distinction across contexts (${polarCheck.reason || "opposing cues"})`,
|
|
553
|
+
score: bestSim
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
action: "CONTRADICT",
|
|
558
|
+
targetId: bestMatch.id,
|
|
559
|
+
knowledge: bestMatch,
|
|
560
|
+
reason: `Contradicting statement (${polarCheck.reason || "opposing cues"}) despite high textual similarity (${Math.round(bestSim * 100)}%)`,
|
|
561
|
+
score: bestSim
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
if (bestSim >= 0.9) {
|
|
565
|
+
return {
|
|
566
|
+
action: "MERGE",
|
|
567
|
+
targetId: bestMatch.id,
|
|
568
|
+
knowledge: bestMatch,
|
|
569
|
+
reason: `Nearly identical statement (${Math.round(bestSim * 100)}% match) with compatible polarity; merging cues and evidence`,
|
|
570
|
+
score: bestSim
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
return {
|
|
574
|
+
action: "SUPPORT",
|
|
575
|
+
targetId: bestMatch.id,
|
|
576
|
+
knowledge: bestMatch,
|
|
577
|
+
reason: `Supports existing knowledge (${Math.round(bestSim * 100)}% similarity) with compatible polarity`,
|
|
578
|
+
score: bestSim
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
action: "NEW",
|
|
583
|
+
reason: "No conflicting or matching knowledge found; creating new knowledge entry"
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
function applyReconciliation(reconciliation, candidate, existingList, storage) {
|
|
587
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
588
|
+
switch (reconciliation.action) {
|
|
589
|
+
case "SUPPORT": {
|
|
590
|
+
const target = existingList.find((k) => k.id === reconciliation.targetId);
|
|
591
|
+
target.metrics.support_count++;
|
|
592
|
+
target.metrics.last_used = now;
|
|
593
|
+
storage?.appendMetricEvent({
|
|
594
|
+
type: "knowledge_strengthened",
|
|
595
|
+
knowledgeId: target.id,
|
|
596
|
+
timestamp: Date.now()
|
|
597
|
+
});
|
|
598
|
+
if (candidate.evidence) {
|
|
599
|
+
for (const e of candidate.evidence) {
|
|
600
|
+
if (!target.evidence.includes(e)) {
|
|
601
|
+
target.evidence.push(e);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (candidate.positive) {
|
|
606
|
+
for (const p of candidate.positive) {
|
|
607
|
+
const lp = p.trim().toLowerCase();
|
|
608
|
+
if (!target.positive.includes(lp)) target.positive.push(lp);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (!target.history) {
|
|
612
|
+
target.history = [
|
|
613
|
+
{
|
|
614
|
+
timestamp: target.createdAt || now,
|
|
615
|
+
stage: target.stage,
|
|
616
|
+
status: target.status,
|
|
617
|
+
reason: "Initial knowledge recorded",
|
|
618
|
+
source: target.evidence?.[0]
|
|
619
|
+
}
|
|
620
|
+
];
|
|
621
|
+
}
|
|
622
|
+
const promotion = evaluatePromotion(target);
|
|
623
|
+
if (promotion.promoted && promotion.newStage) {
|
|
624
|
+
target.stage = promotion.newStage;
|
|
625
|
+
if (target.stage === "active" && target.status === "candidate") {
|
|
626
|
+
target.status = "active";
|
|
627
|
+
}
|
|
628
|
+
target.history.push({
|
|
629
|
+
timestamp: now,
|
|
630
|
+
stage: target.stage,
|
|
631
|
+
status: target.status,
|
|
632
|
+
reason: promotion.reason || `Promoted to ${target.stage}`,
|
|
633
|
+
source: candidate.evidence?.[0]
|
|
634
|
+
});
|
|
635
|
+
} else {
|
|
636
|
+
target.history.push({
|
|
637
|
+
timestamp: now,
|
|
638
|
+
stage: target.stage,
|
|
639
|
+
status: target.status,
|
|
640
|
+
reason: reconciliation.reason || "Confirmed with supporting evidence",
|
|
641
|
+
source: candidate.evidence?.[0]
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
target.confidence = calculateEffectiveConfidence(target);
|
|
645
|
+
target.value = calculateKnowledgeValue(target);
|
|
646
|
+
target.updatedAt = now;
|
|
647
|
+
return { updatedKnowledge: target, action: "SUPPORT", createdId: target.id };
|
|
648
|
+
}
|
|
649
|
+
case "MERGE": {
|
|
650
|
+
const target = existingList.find((k) => k.id === reconciliation.targetId);
|
|
651
|
+
target.metrics.support_count++;
|
|
652
|
+
target.metrics.last_used = now;
|
|
653
|
+
storage?.appendMetricEvent({
|
|
654
|
+
type: "knowledge_merged",
|
|
655
|
+
knowledgeId: target.id,
|
|
656
|
+
targetId: reconciliation.targetId,
|
|
657
|
+
timestamp: Date.now()
|
|
658
|
+
});
|
|
659
|
+
if (candidate.evidence) {
|
|
660
|
+
for (const e of candidate.evidence) {
|
|
661
|
+
if (!target.evidence.includes(e)) target.evidence.push(e);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (candidate.positive) {
|
|
665
|
+
for (const p of candidate.positive) {
|
|
666
|
+
const lp = p.trim().toLowerCase();
|
|
667
|
+
if (!target.positive.includes(lp)) target.positive.push(lp);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (candidate.negative) {
|
|
671
|
+
for (const n of candidate.negative) {
|
|
672
|
+
const ln = n.trim().toLowerCase();
|
|
673
|
+
if (!target.negative.includes(ln)) target.negative.push(ln);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (!target.history) {
|
|
677
|
+
target.history = [
|
|
678
|
+
{
|
|
679
|
+
timestamp: target.createdAt || now,
|
|
680
|
+
stage: target.stage,
|
|
681
|
+
status: target.status,
|
|
682
|
+
reason: "Initial knowledge recorded",
|
|
683
|
+
source: target.evidence?.[0]
|
|
684
|
+
}
|
|
685
|
+
];
|
|
686
|
+
}
|
|
687
|
+
const promotion = evaluatePromotion(target);
|
|
688
|
+
if (promotion.promoted && promotion.newStage) {
|
|
689
|
+
target.stage = promotion.newStage;
|
|
690
|
+
if (target.stage === "active" && target.status === "candidate") {
|
|
691
|
+
target.status = "active";
|
|
692
|
+
}
|
|
693
|
+
target.history.push({
|
|
694
|
+
timestamp: now,
|
|
695
|
+
stage: target.stage,
|
|
696
|
+
status: target.status,
|
|
697
|
+
reason: promotion.reason || `Promoted to ${target.stage}`,
|
|
698
|
+
source: candidate.evidence?.[0]
|
|
699
|
+
});
|
|
700
|
+
} else {
|
|
701
|
+
target.history.push({
|
|
702
|
+
timestamp: now,
|
|
703
|
+
stage: target.stage,
|
|
704
|
+
status: target.status,
|
|
705
|
+
reason: reconciliation.reason || "Merged with compatible knowledge candidate",
|
|
706
|
+
source: candidate.evidence?.[0]
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
target.confidence = calculateEffectiveConfidence(target);
|
|
710
|
+
target.value = calculateKnowledgeValue(target);
|
|
711
|
+
target.updatedAt = now;
|
|
712
|
+
return { updatedKnowledge: target, action: "MERGE", createdId: target.id };
|
|
713
|
+
}
|
|
714
|
+
case "UPDATE": {
|
|
715
|
+
const target = existingList.find((k) => k.id === reconciliation.targetId);
|
|
716
|
+
target.statement = candidate.statement;
|
|
717
|
+
if (candidate.type) target.type = candidate.type;
|
|
718
|
+
if (candidate.positive) {
|
|
719
|
+
target.positive = Array.from(/* @__PURE__ */ new Set([...target.positive, ...candidate.positive.map((p) => p.toLowerCase())]));
|
|
720
|
+
}
|
|
721
|
+
if (candidate.negative) {
|
|
722
|
+
target.negative = Array.from(/* @__PURE__ */ new Set([...target.negative, ...candidate.negative.map((n) => n.toLowerCase())]));
|
|
723
|
+
}
|
|
724
|
+
if (candidate.evidence) {
|
|
725
|
+
target.evidence = Array.from(/* @__PURE__ */ new Set([...target.evidence, ...candidate.evidence]));
|
|
726
|
+
}
|
|
727
|
+
target.temporal = {
|
|
728
|
+
revision: (target.temporal?.revision ?? 1) + 1,
|
|
729
|
+
valid_from: { timestamp: now }
|
|
730
|
+
};
|
|
731
|
+
if (!target.history) {
|
|
732
|
+
target.history = [
|
|
733
|
+
{
|
|
734
|
+
timestamp: target.createdAt || now,
|
|
735
|
+
stage: target.stage,
|
|
736
|
+
status: target.status,
|
|
737
|
+
reason: "Initial knowledge recorded",
|
|
738
|
+
source: target.evidence?.[0]
|
|
739
|
+
}
|
|
740
|
+
];
|
|
741
|
+
}
|
|
742
|
+
target.history.push({
|
|
743
|
+
timestamp: now,
|
|
744
|
+
stage: target.stage,
|
|
745
|
+
status: target.status,
|
|
746
|
+
reason: reconciliation.reason || "Updated statement and context",
|
|
747
|
+
source: candidate.evidence?.[0]
|
|
748
|
+
});
|
|
749
|
+
target.updatedAt = now;
|
|
750
|
+
target.confidence = calculateEffectiveConfidence(target);
|
|
751
|
+
target.value = calculateKnowledgeValue(target);
|
|
752
|
+
storage?.appendMetricEvent({
|
|
753
|
+
type: "knowledge_strengthened",
|
|
754
|
+
knowledgeId: target.id,
|
|
755
|
+
timestamp: Date.now()
|
|
756
|
+
});
|
|
757
|
+
return { updatedKnowledge: target, action: "UPDATE", createdId: target.id };
|
|
758
|
+
}
|
|
759
|
+
case "CONTRADICT": {
|
|
760
|
+
const target = existingList.find((k) => k.id === reconciliation.targetId);
|
|
761
|
+
target.metrics.contradiction_count++;
|
|
762
|
+
if (candidate.evidence) {
|
|
763
|
+
for (const e of candidate.evidence) {
|
|
764
|
+
if (!target.evidence.includes(e)) target.evidence.push(e);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (target.metrics.contradiction_count > target.metrics.support_count) {
|
|
768
|
+
target.status = "contradicted";
|
|
769
|
+
}
|
|
770
|
+
if (!target.history) {
|
|
771
|
+
target.history = [
|
|
772
|
+
{
|
|
773
|
+
timestamp: target.createdAt || now,
|
|
774
|
+
stage: target.stage,
|
|
775
|
+
status: target.status,
|
|
776
|
+
reason: "Initial knowledge recorded",
|
|
777
|
+
source: target.evidence?.[0]
|
|
778
|
+
}
|
|
779
|
+
];
|
|
780
|
+
}
|
|
781
|
+
target.history.push({
|
|
782
|
+
timestamp: now,
|
|
783
|
+
stage: target.stage,
|
|
784
|
+
status: target.status,
|
|
785
|
+
reason: reconciliation.reason || "Contradicted by opposing claim",
|
|
786
|
+
source: candidate.evidence?.[0]
|
|
787
|
+
});
|
|
788
|
+
target.confidence = calculateEffectiveConfidence(target);
|
|
789
|
+
target.value = calculateKnowledgeValue(target);
|
|
790
|
+
target.updatedAt = now;
|
|
791
|
+
storage?.appendMetricEvent({
|
|
792
|
+
type: "contradiction_detected",
|
|
793
|
+
knowledgeId: target.id,
|
|
794
|
+
timestamp: Date.now()
|
|
795
|
+
});
|
|
796
|
+
return { updatedKnowledge: target, action: "CONTRADICT", createdId: target.id };
|
|
797
|
+
}
|
|
798
|
+
case "SPECIALIZE": {
|
|
799
|
+
const parent = existingList.find((k) => k.id === reconciliation.targetId);
|
|
800
|
+
const subContext = candidate.positive?.[0] || "specialized";
|
|
801
|
+
const newId = candidate.id && candidate.id !== parent?.id ? candidate.id : `${parent ? parent.id : "knowledge"}.${subContext}`;
|
|
802
|
+
const created = createKnowledge({
|
|
803
|
+
id: newId,
|
|
804
|
+
type: candidate.type ?? parent?.type ?? "lesson",
|
|
805
|
+
statement: candidate.statement,
|
|
806
|
+
positive: candidate.positive ?? [],
|
|
807
|
+
negative: candidate.negative ?? [],
|
|
808
|
+
relations: parent ? [parent.id, ...candidate.relations ?? []] : candidate.relations ?? [],
|
|
809
|
+
confidence: candidate.confidence ?? 0.85,
|
|
810
|
+
evidence: candidate.evidence ?? [],
|
|
811
|
+
scope: candidate.scope ?? parent?.scope ?? "project",
|
|
812
|
+
tags: candidate.tags ?? parent?.tags ?? []
|
|
813
|
+
});
|
|
814
|
+
if (parent) {
|
|
815
|
+
if (!parent.relations.includes(newId)) {
|
|
816
|
+
parent.relations.push(newId);
|
|
817
|
+
}
|
|
818
|
+
if (!parent.history) {
|
|
819
|
+
parent.history = [
|
|
820
|
+
{
|
|
821
|
+
timestamp: parent.createdAt || now,
|
|
822
|
+
stage: parent.stage,
|
|
823
|
+
status: parent.status,
|
|
824
|
+
reason: "Initial knowledge recorded"
|
|
825
|
+
}
|
|
826
|
+
];
|
|
827
|
+
}
|
|
828
|
+
parent.history.push({
|
|
829
|
+
timestamp: now,
|
|
830
|
+
stage: parent.stage,
|
|
831
|
+
status: parent.status,
|
|
832
|
+
reason: `Specialized into contextual branch ${newId}`
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
storage?.appendMetricEvent({
|
|
836
|
+
type: "knowledge_specialized",
|
|
837
|
+
knowledgeId: created.id,
|
|
838
|
+
timestamp: Date.now()
|
|
839
|
+
});
|
|
840
|
+
return { updatedKnowledge: created, action: "SPECIALIZE", createdId: created.id };
|
|
841
|
+
}
|
|
842
|
+
case "IGNORE": {
|
|
843
|
+
const target = existingList.find((k) => k.id === reconciliation.targetId);
|
|
844
|
+
storage?.appendMetricEvent({
|
|
845
|
+
type: "duplicate_ignored",
|
|
846
|
+
timestamp: Date.now()
|
|
847
|
+
});
|
|
848
|
+
return { updatedKnowledge: target, action: "IGNORE", createdId: target.id };
|
|
849
|
+
}
|
|
850
|
+
case "NEW":
|
|
851
|
+
default: {
|
|
852
|
+
const newId = candidate.id || `kn_${hashString(candidate.statement)}`;
|
|
853
|
+
const created = createKnowledge({
|
|
854
|
+
id: newId,
|
|
855
|
+
type: candidate.type,
|
|
856
|
+
statement: candidate.statement,
|
|
857
|
+
positive: candidate.positive,
|
|
858
|
+
negative: candidate.negative,
|
|
859
|
+
relations: candidate.relations,
|
|
860
|
+
confidence: candidate.confidence,
|
|
861
|
+
evidence: candidate.evidence,
|
|
862
|
+
scope: candidate.scope,
|
|
863
|
+
tags: candidate.tags
|
|
864
|
+
});
|
|
865
|
+
storage?.appendMetricEvent({
|
|
866
|
+
type: "knowledge_created",
|
|
867
|
+
knowledgeId: created.id,
|
|
868
|
+
timestamp: Date.now()
|
|
869
|
+
});
|
|
870
|
+
return { updatedKnowledge: created, action: "NEW", createdId: created.id };
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/core/unknown.ts
|
|
876
|
+
function createUnknown(params) {
|
|
877
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
878
|
+
return {
|
|
879
|
+
id: params.id.trim(),
|
|
880
|
+
state: params.state ?? "UNKNOWN",
|
|
881
|
+
reason: params.reason ?? "NOT_OBSERVED",
|
|
882
|
+
statement: params.statement?.trim(),
|
|
883
|
+
checked: Array.from(new Set(params.checked?.map((c) => c.trim()) ?? [])),
|
|
884
|
+
next: params.next,
|
|
885
|
+
scope: params.scope ?? "project",
|
|
886
|
+
createdAt: now,
|
|
887
|
+
updatedAt: now
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function hasBeenInvestigated(unknowns, target) {
|
|
891
|
+
const normalizedTarget = target.trim().toLowerCase();
|
|
892
|
+
for (const record of unknowns) {
|
|
893
|
+
if (record.id.toLowerCase() === normalizedTarget) {
|
|
894
|
+
return { investigated: true, record, reason: record.reason };
|
|
895
|
+
}
|
|
896
|
+
const checkedMatch = record.checked.some((c) => {
|
|
897
|
+
const nc = c.toLowerCase();
|
|
898
|
+
return nc === normalizedTarget || normalizedTarget.startsWith(nc.replace("/**", ""));
|
|
899
|
+
});
|
|
900
|
+
if (checkedMatch) {
|
|
901
|
+
return { investigated: true, record, reason: record.reason };
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return { investigated: false };
|
|
905
|
+
}
|
|
906
|
+
function updateUnknown(record, updates) {
|
|
907
|
+
const checked = new Set(record.checked);
|
|
908
|
+
if (updates.addChecked) {
|
|
909
|
+
for (const c of updates.addChecked) {
|
|
910
|
+
checked.add(c.trim());
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return {
|
|
914
|
+
...record,
|
|
915
|
+
state: updates.state ?? record.state,
|
|
916
|
+
reason: updates.reason ?? record.reason,
|
|
917
|
+
statement: updates.statement !== void 0 ? updates.statement : record.statement,
|
|
918
|
+
checked: Array.from(checked),
|
|
919
|
+
next: updates.next !== void 0 ? updates.next : record.next,
|
|
920
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/core/outcome.ts
|
|
925
|
+
function createOutcome(params) {
|
|
926
|
+
const content = `${params.knowledgeId || "none"}:${params.result}:${Date.now()}`;
|
|
927
|
+
return {
|
|
928
|
+
id: `out_${hashString(content)}`,
|
|
929
|
+
knowledgeId: params.knowledgeId,
|
|
930
|
+
taskId: params.taskId,
|
|
931
|
+
result: params.result,
|
|
932
|
+
context: params.context,
|
|
933
|
+
feedback: params.feedback,
|
|
934
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
function applyOutcomeToKnowledge(knowledge, outcome) {
|
|
938
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
939
|
+
knowledge.metrics.reuse_count++;
|
|
940
|
+
knowledge.metrics.last_used = now;
|
|
941
|
+
if (outcome.result === "success") {
|
|
942
|
+
knowledge.metrics.successful_outcomes++;
|
|
943
|
+
} else if (outcome.result === "failure" || outcome.result === "reverted") {
|
|
944
|
+
knowledge.metrics.failed_outcomes++;
|
|
945
|
+
}
|
|
946
|
+
knowledge.confidence = calculateEffectiveConfidence(knowledge);
|
|
947
|
+
knowledge.updatedAt = now;
|
|
948
|
+
return knowledge;
|
|
949
|
+
}
|
|
950
|
+
var MetricsTracker = class {
|
|
951
|
+
metrics = {
|
|
952
|
+
knowledgeHits: 0,
|
|
953
|
+
knowledgeMisses: 0,
|
|
954
|
+
knowledgeReused: 0,
|
|
955
|
+
unknownsResolved: 0,
|
|
956
|
+
repeatedInvestigationsPrevented: 0,
|
|
957
|
+
candidateKnowledgeMerged: 0,
|
|
958
|
+
staleKnowledgeDetected: 0,
|
|
959
|
+
contradictionsDetected: 0,
|
|
960
|
+
knowledgePromoted: 0,
|
|
961
|
+
knowledgeSuperseded: 0
|
|
962
|
+
};
|
|
963
|
+
constructor(initial) {
|
|
964
|
+
if (initial) {
|
|
965
|
+
this.metrics = { ...this.metrics, ...initial };
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
recordHit() {
|
|
969
|
+
this.metrics.knowledgeHits++;
|
|
970
|
+
}
|
|
971
|
+
recordMiss() {
|
|
972
|
+
this.metrics.knowledgeMisses++;
|
|
973
|
+
}
|
|
974
|
+
recordReused() {
|
|
975
|
+
this.metrics.knowledgeReused++;
|
|
976
|
+
}
|
|
977
|
+
recordUnknownResolved() {
|
|
978
|
+
this.metrics.unknownsResolved++;
|
|
979
|
+
}
|
|
980
|
+
recordInvestigationPrevented() {
|
|
981
|
+
this.metrics.repeatedInvestigationsPrevented++;
|
|
982
|
+
}
|
|
983
|
+
recordCandidateMerged() {
|
|
984
|
+
this.metrics.candidateKnowledgeMerged++;
|
|
985
|
+
}
|
|
986
|
+
recordStaleDetected() {
|
|
987
|
+
this.metrics.staleKnowledgeDetected++;
|
|
988
|
+
}
|
|
989
|
+
recordContradiction() {
|
|
990
|
+
this.metrics.contradictionsDetected++;
|
|
991
|
+
}
|
|
992
|
+
recordPromoted() {
|
|
993
|
+
this.metrics.knowledgePromoted++;
|
|
994
|
+
}
|
|
995
|
+
recordSuperseded() {
|
|
996
|
+
this.metrics.knowledgeSuperseded++;
|
|
997
|
+
}
|
|
998
|
+
getMetrics() {
|
|
999
|
+
return { ...this.metrics };
|
|
1000
|
+
}
|
|
1001
|
+
toJSON() {
|
|
1002
|
+
return this.metrics;
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
// src/core/learn.ts
|
|
1007
|
+
import { createHash as createHash2 } from "crypto";
|
|
1008
|
+
function distillTrajectory(trajectory) {
|
|
1009
|
+
const events = trajectory.events || [];
|
|
1010
|
+
if (events.length === 0) return null;
|
|
1011
|
+
const failures = events.filter((e) => e.type === "failure" || e.type === "revert");
|
|
1012
|
+
const successes = events.filter((e) => e.type === "success");
|
|
1013
|
+
const corrections = events.filter((e) => e.type === "user_correction");
|
|
1014
|
+
if (successes.length === 0 && corrections.length === 0 && failures.length === 0) {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
const positiveSignals = /* @__PURE__ */ new Set();
|
|
1018
|
+
const negativeSignals = /* @__PURE__ */ new Set();
|
|
1019
|
+
const tags = /* @__PURE__ */ new Set(["lesson", "trajectory"]);
|
|
1020
|
+
for (const f of failures) {
|
|
1021
|
+
negativeSignals.add(f.description.toLowerCase().replace(/[^a-z0-9_-]/g, "-"));
|
|
1022
|
+
if (f.path) negativeSignals.add(f.path.toLowerCase());
|
|
1023
|
+
}
|
|
1024
|
+
for (const s of successes) {
|
|
1025
|
+
positiveSignals.add(s.description.toLowerCase().replace(/[^a-z0-9_-]/g, "-"));
|
|
1026
|
+
if (s.path) positiveSignals.add(s.path.toLowerCase());
|
|
1027
|
+
}
|
|
1028
|
+
for (const c of corrections) {
|
|
1029
|
+
positiveSignals.add(c.description.toLowerCase().replace(/[^a-z0-9_-]/g, "-"));
|
|
1030
|
+
tags.add("user-correction");
|
|
1031
|
+
}
|
|
1032
|
+
const goalTokens = trajectory.goal.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((w) => w.length > 3);
|
|
1033
|
+
for (const t of goalTokens) {
|
|
1034
|
+
positiveSignals.add(t);
|
|
1035
|
+
}
|
|
1036
|
+
let statement = "";
|
|
1037
|
+
let reasoning = "";
|
|
1038
|
+
if (corrections.length > 0) {
|
|
1039
|
+
const lastCorr = corrections[corrections.length - 1];
|
|
1040
|
+
statement = `Corrected approach for "${trajectory.goal}": ${lastCorr.description}`;
|
|
1041
|
+
reasoning = `Based on user correction during task ${trajectory.taskId}`;
|
|
1042
|
+
} else if (successes.length > 0) {
|
|
1043
|
+
const lastSuccess = successes[successes.length - 1];
|
|
1044
|
+
if (failures.length > 0) {
|
|
1045
|
+
const firstFailure = failures[0];
|
|
1046
|
+
statement = `When addressing "${trajectory.goal}": prefer ${lastSuccess.description} over ${firstFailure.description}`;
|
|
1047
|
+
reasoning = `Initial attempt (${firstFailure.description}) failed/reverted; resolved by ${lastSuccess.description}`;
|
|
1048
|
+
} else {
|
|
1049
|
+
statement = `Effective pattern for "${trajectory.goal}": ${lastSuccess.description}`;
|
|
1050
|
+
reasoning = `Directly succeeded in task ${trajectory.taskId}`;
|
|
1051
|
+
}
|
|
1052
|
+
} else if (failures.length > 0) {
|
|
1053
|
+
const failureList = failures.map((f) => f.description).join(", ");
|
|
1054
|
+
statement = `Avoid for "${trajectory.goal}": approaches [${failureList}] failed`;
|
|
1055
|
+
reasoning = `Recorded ${failures.length} failed attempts in task ${trajectory.taskId}`;
|
|
1056
|
+
}
|
|
1057
|
+
const hash = createHash2("sha256").update(`${trajectory.goal}:${statement}`).digest("hex").slice(0, 12);
|
|
1058
|
+
const id = `lesson.${trajectory.taskId || hash}`;
|
|
1059
|
+
return {
|
|
1060
|
+
id,
|
|
1061
|
+
statement,
|
|
1062
|
+
positive: Array.from(positiveSignals).slice(0, 8),
|
|
1063
|
+
negative: Array.from(negativeSignals).slice(0, 8),
|
|
1064
|
+
confidence: corrections.length > 0 ? 0.95 : successes.length > 0 ? 0.85 : 0.65,
|
|
1065
|
+
evidence: [`trajectory:${trajectory.taskId}`],
|
|
1066
|
+
tags: Array.from(tags),
|
|
1067
|
+
reasoning
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
function learnFromTrajectory(trajectory, existingKnowledge = [], options = {}) {
|
|
1071
|
+
const dryRun = options.dryRun ?? false;
|
|
1072
|
+
const storage = options.storage;
|
|
1073
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1074
|
+
const taskId = trajectory.taskId || `task_${Date.now()}`;
|
|
1075
|
+
const events = trajectory.events || [];
|
|
1076
|
+
const observations = [];
|
|
1077
|
+
events.forEach((event, idx) => {
|
|
1078
|
+
let obsType = "agent";
|
|
1079
|
+
let confidence = 0.75;
|
|
1080
|
+
const positive = [taskId, event.type];
|
|
1081
|
+
const negative = [];
|
|
1082
|
+
if (event.type === "user_correction") {
|
|
1083
|
+
obsType = "user";
|
|
1084
|
+
confidence = 0.98;
|
|
1085
|
+
} else if (event.type === "failure" || event.type === "revert") {
|
|
1086
|
+
obsType = "test";
|
|
1087
|
+
confidence = 0.95;
|
|
1088
|
+
negative.push(event.description.toLowerCase().replace(/[^a-z0-9_-]/g, "-"));
|
|
1089
|
+
} else if (event.type === "success") {
|
|
1090
|
+
obsType = "agent";
|
|
1091
|
+
confidence = 0.95;
|
|
1092
|
+
positive.push(event.description.toLowerCase().replace(/[^a-z0-9_-]/g, "-"));
|
|
1093
|
+
} else if (event.type === "hypothesis") {
|
|
1094
|
+
confidence = 0.5;
|
|
1095
|
+
}
|
|
1096
|
+
if (event.path) positive.push(event.path.toLowerCase());
|
|
1097
|
+
observations.push(
|
|
1098
|
+
createObservation({
|
|
1099
|
+
source: `trajectory:${taskId}:${event.type}:${idx}`,
|
|
1100
|
+
claim: `[${event.type.toUpperCase()}] ${event.description}`,
|
|
1101
|
+
provenance: {
|
|
1102
|
+
type: obsType,
|
|
1103
|
+
path: event.path,
|
|
1104
|
+
timestamp: event.timestamp || now
|
|
1105
|
+
},
|
|
1106
|
+
confidence,
|
|
1107
|
+
positive,
|
|
1108
|
+
negative: negative.length > 0 ? negative : void 0,
|
|
1109
|
+
details: { taskId, eventIndex: idx, eventType: event.type, ...event.data || {} }
|
|
1110
|
+
})
|
|
1111
|
+
);
|
|
1112
|
+
});
|
|
1113
|
+
const evidence = observations.map((obs) => {
|
|
1114
|
+
const isFailure = obs.claim.startsWith("[FAILURE]") || obs.claim.startsWith("[REVERT]");
|
|
1115
|
+
return createEvidence(obs, isFailure ? "contradicts" : "supports");
|
|
1116
|
+
});
|
|
1117
|
+
const distilled = distillTrajectory(trajectory);
|
|
1118
|
+
const candidateLessons = [];
|
|
1119
|
+
if (distilled) {
|
|
1120
|
+
candidateLessons.push({
|
|
1121
|
+
id: distilled.id,
|
|
1122
|
+
type: "lesson",
|
|
1123
|
+
statement: distilled.statement,
|
|
1124
|
+
positive: distilled.positive,
|
|
1125
|
+
negative: distilled.negative,
|
|
1126
|
+
confidence: distilled.confidence,
|
|
1127
|
+
evidence: evidence.map((e) => e.source),
|
|
1128
|
+
tags: distilled.tags,
|
|
1129
|
+
scope: "project"
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
const reconciliations = [];
|
|
1133
|
+
const currentKnowledgeList = [...existingKnowledge];
|
|
1134
|
+
for (const candidate of candidateLessons) {
|
|
1135
|
+
const recResult = reconcileKnowledge(candidate, currentKnowledgeList);
|
|
1136
|
+
if (dryRun) {
|
|
1137
|
+
reconciliations.push({
|
|
1138
|
+
candidate,
|
|
1139
|
+
result: recResult,
|
|
1140
|
+
action: recResult.action
|
|
1141
|
+
});
|
|
1142
|
+
} else {
|
|
1143
|
+
const { updatedKnowledge, action } = applyReconciliation(
|
|
1144
|
+
recResult,
|
|
1145
|
+
candidate,
|
|
1146
|
+
currentKnowledgeList,
|
|
1147
|
+
storage
|
|
1148
|
+
);
|
|
1149
|
+
reconciliations.push({
|
|
1150
|
+
candidate,
|
|
1151
|
+
result: recResult,
|
|
1152
|
+
appliedKnowledge: updatedKnowledge,
|
|
1153
|
+
action
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
return {
|
|
1158
|
+
taskId,
|
|
1159
|
+
observations,
|
|
1160
|
+
evidence,
|
|
1161
|
+
candidateLessons,
|
|
1162
|
+
reconciliations,
|
|
1163
|
+
dryRun
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
function processUserCorrection(params) {
|
|
1167
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1168
|
+
const prov = {
|
|
1169
|
+
type: "user",
|
|
1170
|
+
timestamp: now,
|
|
1171
|
+
...params.author ? { author: params.author } : {}
|
|
1172
|
+
};
|
|
1173
|
+
const claim = `User correction on ${params.topic}: ${params.correctFact} (supersedes assumption: ${params.incorrectAssumption})`;
|
|
1174
|
+
const observation = createObservation({
|
|
1175
|
+
source: `user:correction:${params.topic.toLowerCase()}`,
|
|
1176
|
+
claim,
|
|
1177
|
+
provenance: prov,
|
|
1178
|
+
confidence: 1,
|
|
1179
|
+
positive: [params.topic.toLowerCase(), ...params.context ?? []],
|
|
1180
|
+
negative: [params.incorrectAssumption.toLowerCase()]
|
|
1181
|
+
});
|
|
1182
|
+
const evidence = createEvidence(observation, "supports");
|
|
1183
|
+
const knowledge = createKnowledge({
|
|
1184
|
+
id: `correction.${params.topic.toLowerCase().replace(/[^a-z0-9_-]/g, ".")}`,
|
|
1185
|
+
type: "rule",
|
|
1186
|
+
status: "active",
|
|
1187
|
+
statement: params.correctFact,
|
|
1188
|
+
positive: [params.topic.toLowerCase(), ...params.context ?? []],
|
|
1189
|
+
negative: [params.incorrectAssumption.toLowerCase()],
|
|
1190
|
+
confidence: 1,
|
|
1191
|
+
confidenceTier: "high",
|
|
1192
|
+
evidence: [evidence.id, observation.source],
|
|
1193
|
+
tags: ["user-correction", params.topic.toLowerCase()]
|
|
1194
|
+
});
|
|
1195
|
+
return { observation, knowledge };
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/core/dependencies.ts
|
|
1199
|
+
function normalizeSourceKey(source) {
|
|
1200
|
+
return source.replace(/^(file|code|test|doc):/, "").trim().toLowerCase();
|
|
1201
|
+
}
|
|
1202
|
+
function buildDependencyIndex(knowledgeList) {
|
|
1203
|
+
const sourceToKnowledge = /* @__PURE__ */ new Map();
|
|
1204
|
+
for (const k of knowledgeList) {
|
|
1205
|
+
const allDependencies = /* @__PURE__ */ new Set();
|
|
1206
|
+
if (k.depends_on) {
|
|
1207
|
+
for (const d of k.depends_on) {
|
|
1208
|
+
allDependencies.add(normalizeSourceKey(d));
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (k.evidence) {
|
|
1212
|
+
for (const ev of k.evidence) {
|
|
1213
|
+
allDependencies.add(normalizeSourceKey(ev));
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
for (const dep of allDependencies) {
|
|
1217
|
+
if (!dep) continue;
|
|
1218
|
+
let set = sourceToKnowledge.get(dep);
|
|
1219
|
+
if (!set) {
|
|
1220
|
+
set = /* @__PURE__ */ new Set();
|
|
1221
|
+
sourceToKnowledge.set(dep, set);
|
|
1222
|
+
}
|
|
1223
|
+
set.add(k.id);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
return { sourceToKnowledge };
|
|
1227
|
+
}
|
|
1228
|
+
function checkKnowledgeStaleness(knowledge, changedSources) {
|
|
1229
|
+
const normChanged = new Set(changedSources.map(normalizeSourceKey));
|
|
1230
|
+
const allDeps = [
|
|
1231
|
+
...(knowledge.depends_on ?? []).map(normalizeSourceKey),
|
|
1232
|
+
...knowledge.evidence.map(normalizeSourceKey)
|
|
1233
|
+
];
|
|
1234
|
+
const changed = [];
|
|
1235
|
+
const unchanged = [];
|
|
1236
|
+
for (const dep of allDeps) {
|
|
1237
|
+
if (!dep) continue;
|
|
1238
|
+
const isChanged = normChanged.has(dep) || Array.from(normChanged).some((c) => dep.includes(c) || c.includes(dep));
|
|
1239
|
+
if (isChanged) {
|
|
1240
|
+
changed.push(dep);
|
|
1241
|
+
} else {
|
|
1242
|
+
unchanged.push(dep);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (changed.length > 0) {
|
|
1246
|
+
const reason = `Supporting dependency changed: ${changed.join(", ")}. Unchanged evidence: ${unchanged.join(", ") || "none"}`;
|
|
1247
|
+
return {
|
|
1248
|
+
knowledgeId: knowledge.id,
|
|
1249
|
+
affected: true,
|
|
1250
|
+
status: "needs_revalidation",
|
|
1251
|
+
changedSources: changed,
|
|
1252
|
+
unchangedSources: unchanged,
|
|
1253
|
+
reason
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
return {
|
|
1257
|
+
knowledgeId: knowledge.id,
|
|
1258
|
+
affected: false,
|
|
1259
|
+
status: knowledge.status,
|
|
1260
|
+
changedSources: [],
|
|
1261
|
+
unchangedSources: allDeps
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function markStaleKnowledge(knowledgeList, changedSources) {
|
|
1265
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1266
|
+
let affectedCount = 0;
|
|
1267
|
+
const updatedKnowledge = [];
|
|
1268
|
+
for (const k of knowledgeList) {
|
|
1269
|
+
if (k.status === "superseded" || k.status === "invalid" || k.status === "invalidated") {
|
|
1270
|
+
updatedKnowledge.push(k);
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
const res = checkKnowledgeStaleness(k, changedSources);
|
|
1274
|
+
if (res.affected) {
|
|
1275
|
+
affectedCount++;
|
|
1276
|
+
const history = k.history ?? [];
|
|
1277
|
+
const updated = {
|
|
1278
|
+
...k,
|
|
1279
|
+
status: "needs_revalidation",
|
|
1280
|
+
updatedAt: now,
|
|
1281
|
+
history: [
|
|
1282
|
+
...history,
|
|
1283
|
+
{
|
|
1284
|
+
timestamp: now,
|
|
1285
|
+
status: "needs_revalidation",
|
|
1286
|
+
action: "source_changed",
|
|
1287
|
+
reason: res.reason
|
|
1288
|
+
}
|
|
1289
|
+
]
|
|
1290
|
+
};
|
|
1291
|
+
updatedKnowledge.push(updated);
|
|
1292
|
+
} else {
|
|
1293
|
+
updatedKnowledge.push(k);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return { updatedKnowledge, affectedCount };
|
|
1297
|
+
}
|
|
1298
|
+
function checkKnowledgeListStaleness(knowledgeList, changedSources) {
|
|
1299
|
+
const { updatedKnowledge, affectedCount } = markStaleKnowledge(knowledgeList, changedSources);
|
|
1300
|
+
const affectedKnowledge = updatedKnowledge.filter((k) => k.status === "needs_revalidation");
|
|
1301
|
+
return { affectedKnowledge, updatedKnowledge, affectedCount };
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// src/jev/scorer.ts
|
|
1305
|
+
var statementTokenCache = /* @__PURE__ */ new WeakMap();
|
|
1306
|
+
function extractTokens(text) {
|
|
1307
|
+
if (!text) return /* @__PURE__ */ new Set();
|
|
1308
|
+
return new Set(
|
|
1309
|
+
text.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2)
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
function getKnowledgeStatementTokens(k) {
|
|
1313
|
+
let tokens = statementTokenCache.get(k);
|
|
1314
|
+
if (!tokens) {
|
|
1315
|
+
tokens = extractTokens(k.statement);
|
|
1316
|
+
statementTokenCache.set(k, tokens);
|
|
1317
|
+
}
|
|
1318
|
+
return tokens;
|
|
1319
|
+
}
|
|
1320
|
+
function evaluateJev(knowledge, context) {
|
|
1321
|
+
const queryTokens = extractTokens(context.query);
|
|
1322
|
+
const contextPos = new Set((context.positive ?? []).map((p) => p.trim().toLowerCase()));
|
|
1323
|
+
const contextNeg = new Set((context.negative ?? []).map((n) => n.trim().toLowerCase()));
|
|
1324
|
+
const knowPos = new Set((knowledge.positive ?? []).map((p) => p.trim().toLowerCase()));
|
|
1325
|
+
const knowNeg = new Set((knowledge.negative ?? []).map((n) => n.trim().toLowerCase()));
|
|
1326
|
+
const negativeConflicts = [];
|
|
1327
|
+
for (const n of knowNeg) {
|
|
1328
|
+
if (contextPos.has(n) || queryTokens.has(n)) {
|
|
1329
|
+
negativeConflicts.push(`Knowledge forbids "${n}", which was present in context`);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
for (const p of knowPos) {
|
|
1333
|
+
if (contextNeg.has(p)) {
|
|
1334
|
+
negativeConflicts.push(`Context forbids "${p}", which is required by knowledge`);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
const positiveMatches = [];
|
|
1338
|
+
for (const p of knowPos) {
|
|
1339
|
+
if (contextPos.has(p)) {
|
|
1340
|
+
positiveMatches.push(`Context cue: ${p}`);
|
|
1341
|
+
} else {
|
|
1342
|
+
for (const q of queryTokens) {
|
|
1343
|
+
if (p === q || p.startsWith(q) || q.startsWith(p)) {
|
|
1344
|
+
positiveMatches.push(`Query term match: ${p}`);
|
|
1345
|
+
break;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
const stmtTokens = getKnowledgeStatementTokens(knowledge);
|
|
1351
|
+
let queryOverlapCount = 0;
|
|
1352
|
+
for (const q of queryTokens) {
|
|
1353
|
+
let matched = false;
|
|
1354
|
+
for (const st of stmtTokens) {
|
|
1355
|
+
if (q === st || q.startsWith(st) || st.startsWith(q)) {
|
|
1356
|
+
matched = true;
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
if (matched) {
|
|
1361
|
+
queryOverlapCount++;
|
|
1362
|
+
positiveMatches.push(`Statement term: ${q}`);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
const totalCues = Math.max(1, knowPos.size);
|
|
1366
|
+
const matchedCuesCount = positiveMatches.filter((m2) => m2.startsWith("Context cue") || m2.startsWith("Query term")).length;
|
|
1367
|
+
const cueMatchRatio = matchedCuesCount / totalCues;
|
|
1368
|
+
const tokenOverlapScore = queryTokens.size > 0 ? queryOverlapCount / Math.max(1, queryTokens.size) : 0;
|
|
1369
|
+
const rawPositiveScore = 0.65 * cueMatchRatio + 0.35 * tokenOverlapScore;
|
|
1370
|
+
let scopeMultiplier = 1;
|
|
1371
|
+
const targetScope = (context.scope ?? "project").toLowerCase();
|
|
1372
|
+
const knowScope = (knowledge.scope ?? "project").toLowerCase();
|
|
1373
|
+
if (targetScope === knowScope) {
|
|
1374
|
+
scopeMultiplier = 1;
|
|
1375
|
+
} else if (knowScope === "global" || targetScope.startsWith(knowScope)) {
|
|
1376
|
+
scopeMultiplier = 0.85;
|
|
1377
|
+
} else if (knowScope === "project" && targetScope.startsWith("branch:")) {
|
|
1378
|
+
scopeMultiplier = 0.9;
|
|
1379
|
+
} else {
|
|
1380
|
+
scopeMultiplier = 0.3;
|
|
1381
|
+
}
|
|
1382
|
+
const m = knowledge.metrics;
|
|
1383
|
+
const evRatio = (m.support_count + 1) / (m.support_count + 2 * m.contradiction_count + 1);
|
|
1384
|
+
const confidenceFactor = knowledge.confidence * evRatio;
|
|
1385
|
+
let outcomeAdjustment = 0;
|
|
1386
|
+
if (m.successful_outcomes > 0) {
|
|
1387
|
+
outcomeAdjustment += Math.min(0.1, m.successful_outcomes * 0.03);
|
|
1388
|
+
}
|
|
1389
|
+
if (m.failed_outcomes > 0) {
|
|
1390
|
+
outcomeAdjustment -= Math.min(0.2, m.failed_outcomes * 0.05);
|
|
1391
|
+
}
|
|
1392
|
+
let score = (rawPositiveScore * 0.45 + confidenceFactor * 0.4 + outcomeAdjustment) * scopeMultiplier;
|
|
1393
|
+
if (knowledge.status === "stale") {
|
|
1394
|
+
score *= 0.6;
|
|
1395
|
+
} else if (knowledge.status === "contradicted") {
|
|
1396
|
+
score *= 0.3;
|
|
1397
|
+
} else if (knowledge.status === "invalid" || knowledge.status === "superseded") {
|
|
1398
|
+
score *= 0.1;
|
|
1399
|
+
}
|
|
1400
|
+
let status = "POSSIBLE";
|
|
1401
|
+
const reasons = [];
|
|
1402
|
+
if (negativeConflicts.length > 0) {
|
|
1403
|
+
score = Math.max(0.01, score * 0.2);
|
|
1404
|
+
status = "REJECT";
|
|
1405
|
+
reasons.push(`Rejected due to ${negativeConflicts.length} negative contextual conflicts.`);
|
|
1406
|
+
} else if (score >= 0.45 && positiveMatches.length > 0) {
|
|
1407
|
+
status = "MATCH";
|
|
1408
|
+
reasons.push(`Strong contextual match with ${positiveMatches.length} matching signals.`);
|
|
1409
|
+
} else if (score >= 0.25) {
|
|
1410
|
+
status = "POSSIBLE";
|
|
1411
|
+
reasons.push(`Potential match; partial contextual overlap.`);
|
|
1412
|
+
} else {
|
|
1413
|
+
status = "REJECT";
|
|
1414
|
+
reasons.push(`Insufficient contextual match score (${score.toFixed(2)} < 0.25).`);
|
|
1415
|
+
}
|
|
1416
|
+
const finalScore = Math.round(Math.min(1, Math.max(0, score)) * 100) / 100;
|
|
1417
|
+
const explanation = {
|
|
1418
|
+
positiveMatches,
|
|
1419
|
+
negativeConflicts,
|
|
1420
|
+
score: finalScore,
|
|
1421
|
+
confidence: knowledge.confidence,
|
|
1422
|
+
status,
|
|
1423
|
+
reasons
|
|
1424
|
+
};
|
|
1425
|
+
return {
|
|
1426
|
+
knowledge,
|
|
1427
|
+
status,
|
|
1428
|
+
score: finalScore,
|
|
1429
|
+
explanation
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// src/jev/matcher.ts
|
|
1434
|
+
function rerankCandidates(candidates, context) {
|
|
1435
|
+
const results = [];
|
|
1436
|
+
for (const candidate of candidates) {
|
|
1437
|
+
const res = evaluateJev(candidate, context);
|
|
1438
|
+
results.push(res);
|
|
1439
|
+
}
|
|
1440
|
+
const statusPriority = {
|
|
1441
|
+
MATCH: 3,
|
|
1442
|
+
POSSIBLE: 2,
|
|
1443
|
+
REJECT: 1
|
|
1444
|
+
};
|
|
1445
|
+
results.sort((a, b) => {
|
|
1446
|
+
const prioDiff = (statusPriority[b.status] ?? 0) - (statusPriority[a.status] ?? 0);
|
|
1447
|
+
if (prioDiff !== 0) return prioDiff;
|
|
1448
|
+
const scoreDiff = b.score - a.score;
|
|
1449
|
+
if (Math.abs(scoreDiff) > 1e-3) return scoreDiff;
|
|
1450
|
+
return b.knowledge.confidence - a.knowledge.confidence;
|
|
1451
|
+
});
|
|
1452
|
+
return results;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// src/search/indexer.ts
|
|
1456
|
+
import { create, insert, remove, search } from "@orama/orama";
|
|
1457
|
+
var SearchIndex = class {
|
|
1458
|
+
db = null;
|
|
1459
|
+
idToDocId = /* @__PURE__ */ new Map();
|
|
1460
|
+
async init() {
|
|
1461
|
+
this.db = await create({
|
|
1462
|
+
schema: {
|
|
1463
|
+
id: "string",
|
|
1464
|
+
statement: "string",
|
|
1465
|
+
type: "string",
|
|
1466
|
+
scope: "string",
|
|
1467
|
+
status: "string",
|
|
1468
|
+
positive: "string[]",
|
|
1469
|
+
negative: "string[]",
|
|
1470
|
+
tags: "string[]"
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
this.idToDocId.clear();
|
|
1474
|
+
}
|
|
1475
|
+
async buildIndex(knowledgeList) {
|
|
1476
|
+
await this.init();
|
|
1477
|
+
for (const item of knowledgeList) {
|
|
1478
|
+
await this.upsert(item);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
async upsert(item) {
|
|
1482
|
+
if (!this.db) {
|
|
1483
|
+
await this.init();
|
|
1484
|
+
}
|
|
1485
|
+
const existingDocId = this.idToDocId.get(item.id);
|
|
1486
|
+
if (existingDocId) {
|
|
1487
|
+
try {
|
|
1488
|
+
await remove(this.db, existingDocId);
|
|
1489
|
+
} catch {
|
|
1490
|
+
}
|
|
1491
|
+
this.idToDocId.delete(item.id);
|
|
1492
|
+
}
|
|
1493
|
+
const docId = await insert(this.db, {
|
|
1494
|
+
id: item.id,
|
|
1495
|
+
statement: item.statement,
|
|
1496
|
+
type: item.type,
|
|
1497
|
+
scope: item.scope,
|
|
1498
|
+
status: item.status,
|
|
1499
|
+
positive: item.positive,
|
|
1500
|
+
negative: item.negative,
|
|
1501
|
+
tags: item.tags ?? []
|
|
1502
|
+
});
|
|
1503
|
+
this.idToDocId.set(item.id, docId);
|
|
1504
|
+
}
|
|
1505
|
+
async delete(id) {
|
|
1506
|
+
if (!this.db) return;
|
|
1507
|
+
const docId = this.idToDocId.get(id);
|
|
1508
|
+
if (docId) {
|
|
1509
|
+
try {
|
|
1510
|
+
await remove(this.db, docId);
|
|
1511
|
+
} catch {
|
|
1512
|
+
}
|
|
1513
|
+
this.idToDocId.delete(id);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
async searchCandidates(term, limit = 25) {
|
|
1517
|
+
if (!this.db) {
|
|
1518
|
+
return [];
|
|
1519
|
+
}
|
|
1520
|
+
const cleanTerm = term.trim();
|
|
1521
|
+
if (!cleanTerm) {
|
|
1522
|
+
return Array.from(this.idToDocId.keys()).slice(0, limit);
|
|
1523
|
+
}
|
|
1524
|
+
try {
|
|
1525
|
+
const results = await search(this.db, {
|
|
1526
|
+
term: cleanTerm,
|
|
1527
|
+
limit,
|
|
1528
|
+
threshold: 0.2
|
|
1529
|
+
});
|
|
1530
|
+
return results.hits.map((h) => h.document.id);
|
|
1531
|
+
} catch {
|
|
1532
|
+
return Array.from(this.idToDocId.keys()).slice(0, limit);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
size() {
|
|
1536
|
+
return this.idToDocId.size;
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
// src/search/retriever.ts
|
|
1541
|
+
var Retriever = class {
|
|
1542
|
+
constructor(index, store) {
|
|
1543
|
+
this.index = index;
|
|
1544
|
+
this.store = store;
|
|
1545
|
+
}
|
|
1546
|
+
index;
|
|
1547
|
+
store;
|
|
1548
|
+
/**
|
|
1549
|
+
* Executes the full Bilmem retrieval pipeline:
|
|
1550
|
+
* 1. Query / Context
|
|
1551
|
+
* 2. Candidate retrieval (small candidate set)
|
|
1552
|
+
* 3. JEV contextual reranking
|
|
1553
|
+
* 4. MATCH / POSSIBLE / REJECT classification with explanations
|
|
1554
|
+
*/
|
|
1555
|
+
async lookup(options = {}) {
|
|
1556
|
+
const limit = options.limit ?? 5;
|
|
1557
|
+
const query = options.query?.trim() ?? "";
|
|
1558
|
+
const positive = options.positive ?? [];
|
|
1559
|
+
const negative = options.negative ?? [];
|
|
1560
|
+
const scope = options.scope ?? "project";
|
|
1561
|
+
const searchTerms = [];
|
|
1562
|
+
if (query) searchTerms.push(query);
|
|
1563
|
+
if (positive.length > 0) searchTerms.push(...positive.slice(0, 3));
|
|
1564
|
+
const searchTerm = searchTerms.join(" ").trim();
|
|
1565
|
+
let candidateIds = await this.index.searchCandidates(searchTerm, 30);
|
|
1566
|
+
let candidates = [];
|
|
1567
|
+
if (candidateIds.length === 0) {
|
|
1568
|
+
const all = await this.store.getAllKnowledge();
|
|
1569
|
+
candidates = all.filter((k) => k.status !== "invalid" && k.status !== "superseded");
|
|
1570
|
+
} else {
|
|
1571
|
+
for (const id of candidateIds) {
|
|
1572
|
+
const item = await this.store.getKnowledge(id);
|
|
1573
|
+
if (item && item.status !== "invalid" && item.status !== "superseded") {
|
|
1574
|
+
candidates.push(item);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
if (candidates.length < 5) {
|
|
1578
|
+
const all = await this.store.getAllKnowledge();
|
|
1579
|
+
for (const k of all) {
|
|
1580
|
+
if (!candidateIds.includes(k.id) && k.status !== "invalid" && k.status !== "superseded") {
|
|
1581
|
+
candidates.push(k);
|
|
1582
|
+
if (candidates.length >= 20) break;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const queryContext = {
|
|
1588
|
+
query,
|
|
1589
|
+
positive,
|
|
1590
|
+
negative,
|
|
1591
|
+
scope
|
|
1592
|
+
};
|
|
1593
|
+
const reranked = rerankCandidates(candidates, queryContext);
|
|
1594
|
+
const filtered = options.includeRejected ? reranked : reranked.filter((r) => r.status !== "REJECT");
|
|
1595
|
+
return filtered.slice(0, limit);
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
|
|
1599
|
+
// src/core/resolver.ts
|
|
1600
|
+
var RELATIVE_COSTS = {
|
|
1601
|
+
USE_KNOWLEDGE: 1,
|
|
1602
|
+
INSPECT_TEST: 2,
|
|
1603
|
+
INSPECT_FILE: 2,
|
|
1604
|
+
INSPECT_SYMBOL: 2.5,
|
|
1605
|
+
CHECK_DOCUMENTATION: 2.5,
|
|
1606
|
+
CHECK_GIT: 3,
|
|
1607
|
+
SEARCH_REPOSITORY: 4,
|
|
1608
|
+
ASK_USER: 3.5,
|
|
1609
|
+
// Context-dependent; cheaper than broad scan when specifically targeted
|
|
1610
|
+
STOP: 1
|
|
1611
|
+
};
|
|
1612
|
+
function tokenize2(text) {
|
|
1613
|
+
return text.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2);
|
|
1614
|
+
}
|
|
1615
|
+
async function resolveEpistemicQuery(options, storage, retriever) {
|
|
1616
|
+
const question = options.question.trim();
|
|
1617
|
+
const positive = (options.positive ?? []).map((p) => p.trim().toLowerCase());
|
|
1618
|
+
const negative = (options.negative ?? []).map((n) => n.trim().toLowerCase());
|
|
1619
|
+
const scope = options.scope ?? "project";
|
|
1620
|
+
const queryTokens = tokenize2(question);
|
|
1621
|
+
const contextualCues = Array.from(/* @__PURE__ */ new Set([...positive, ...queryTokens]));
|
|
1622
|
+
const allKnowledge = await storage.getAllKnowledge();
|
|
1623
|
+
let jevResults;
|
|
1624
|
+
if (retriever instanceof Retriever) {
|
|
1625
|
+
jevResults = await retriever.lookup({
|
|
1626
|
+
query: question,
|
|
1627
|
+
positive: options.positive,
|
|
1628
|
+
negative: options.negative,
|
|
1629
|
+
scope,
|
|
1630
|
+
limit: options.limit ?? 25,
|
|
1631
|
+
includeRejected: true
|
|
1632
|
+
});
|
|
1633
|
+
} else {
|
|
1634
|
+
let candidatePool = [];
|
|
1635
|
+
if (retriever instanceof SearchIndex) {
|
|
1636
|
+
const queryTerm = queryTokens.slice(0, 5).join(" ");
|
|
1637
|
+
const ids = await retriever.searchCandidates(queryTerm, 30);
|
|
1638
|
+
for (const id of ids) {
|
|
1639
|
+
const item = await storage.getKnowledge(id);
|
|
1640
|
+
if (item) candidatePool.push(item);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
if (candidatePool.length === 0) {
|
|
1644
|
+
candidatePool = allKnowledge;
|
|
1645
|
+
}
|
|
1646
|
+
for (const k of allKnowledge) {
|
|
1647
|
+
if (!candidatePool.some((c) => c.id === k.id) && k.positive.some((p) => contextualCues.includes(p))) {
|
|
1648
|
+
candidatePool.push(k);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
jevResults = rerankCandidates(candidatePool, {
|
|
1652
|
+
positive: options.positive,
|
|
1653
|
+
negative: options.negative,
|
|
1654
|
+
scope
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
const matchingItems = [];
|
|
1658
|
+
const rejectedItems = [];
|
|
1659
|
+
for (const res of jevResults) {
|
|
1660
|
+
let jevReason = "INSUFFICIENT_CONTEXT";
|
|
1661
|
+
if (res.explanation.negativeConflicts.length > 0) {
|
|
1662
|
+
jevReason = "HARD_NEGATIVE";
|
|
1663
|
+
} else if (res.explanation.reasons.some((r) => r.toLowerCase().includes("scope"))) {
|
|
1664
|
+
jevReason = "SCOPE_MISMATCH";
|
|
1665
|
+
} else if (res.knowledge.status === "stale" || res.knowledge.status === "needs_revalidation") {
|
|
1666
|
+
jevReason = "STALE_KNOWLEDGE";
|
|
1667
|
+
}
|
|
1668
|
+
await storage.appendMetricEvent({
|
|
1669
|
+
type: "jev_result",
|
|
1670
|
+
knowledgeId: res.knowledge.id,
|
|
1671
|
+
result: res.status,
|
|
1672
|
+
reason: jevReason,
|
|
1673
|
+
timestamp: Date.now()
|
|
1674
|
+
});
|
|
1675
|
+
if (jevReason === "HARD_NEGATIVE") {
|
|
1676
|
+
await storage.appendMetricEvent({
|
|
1677
|
+
type: "jev_hard_negative",
|
|
1678
|
+
knowledgeId: res.knowledge.id,
|
|
1679
|
+
timestamp: Date.now()
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
if (res.status === "REJECT") {
|
|
1683
|
+
const conflictMsg = res.explanation.negativeConflicts.length > 0 ? `${res.explanation.negativeConflicts.join(", ")} conflicts with current context` : res.explanation.reasons[0] || "Context mismatch";
|
|
1684
|
+
rejectedItems.push({
|
|
1685
|
+
id: res.knowledge.id,
|
|
1686
|
+
reason: conflictMsg
|
|
1687
|
+
});
|
|
1688
|
+
} else if (res.status === "MATCH" || res.status === "POSSIBLE") {
|
|
1689
|
+
matchingItems.push(res.knowledge);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
const receipts = await storage.getAllReceipts();
|
|
1693
|
+
const previouslyChecked = /* @__PURE__ */ new Set();
|
|
1694
|
+
const previouslyRejectedTargets = /* @__PURE__ */ new Map();
|
|
1695
|
+
for (const r of receipts) {
|
|
1696
|
+
const qOverlap = tokenize2(r.question).filter((t) => queryTokens.includes(t));
|
|
1697
|
+
if (qOverlap.length >= 2 || r.positive && r.positive.some((p) => contextualCues.includes(p))) {
|
|
1698
|
+
await storage.appendMetricEvent({
|
|
1699
|
+
type: "investigation_reused",
|
|
1700
|
+
investigationId: r.id,
|
|
1701
|
+
timestamp: Date.now()
|
|
1702
|
+
});
|
|
1703
|
+
for (const c of r.checked) {
|
|
1704
|
+
previouslyChecked.add(c);
|
|
1705
|
+
}
|
|
1706
|
+
for (const rej of r.rejected) {
|
|
1707
|
+
previouslyRejectedTargets.set(rej.target, rej.reason);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
for (const m of matchingItems) {
|
|
1712
|
+
for (const ev of m.evidence) {
|
|
1713
|
+
previouslyChecked.add(ev.replace(/^(code|file|test|doc):/, ""));
|
|
1714
|
+
}
|
|
1715
|
+
if (m.depends_on) {
|
|
1716
|
+
for (const dep of m.depends_on) {
|
|
1717
|
+
previouslyChecked.add(dep.replace(/^(code|file|test|doc):/, ""));
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
const staleCandidates = matchingItems.filter((k) => k.status === "needs_revalidation" || k.status === "stale");
|
|
1722
|
+
const contradictedCandidates = matchingItems.filter((k) => k.status === "contradicted");
|
|
1723
|
+
const activeCandidates = matchingItems.filter(
|
|
1724
|
+
(k) => (k.status === "active" || k.stage === "active" || k.stage === "probation") && k.status !== "needs_revalidation" && k.status !== "stale" && k.status !== "contradicted" && k.status !== "invalid" && k.status !== "superseded"
|
|
1725
|
+
);
|
|
1726
|
+
const questionKnowledge = allKnowledge.filter((k) => k.type === "question");
|
|
1727
|
+
let learnedQuestionToAsk;
|
|
1728
|
+
for (const qk of questionKnowledge) {
|
|
1729
|
+
const matchesCues = qk.positive.some((p) => contextualCues.includes(p));
|
|
1730
|
+
const conflicts = qk.negative.some((n) => negative.includes(n));
|
|
1731
|
+
if (matchesCues && !conflicts && qk.question) {
|
|
1732
|
+
learnedQuestionToAsk = {
|
|
1733
|
+
question: qk.question,
|
|
1734
|
+
targetTopic: qk.resolves?.[0] || "uncertainty",
|
|
1735
|
+
reason: `Learned question for [${qk.positive.join(", ")}] uncertainty`
|
|
1736
|
+
};
|
|
1737
|
+
break;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
const coveredTokens = /* @__PURE__ */ new Set();
|
|
1741
|
+
for (const k of activeCandidates) {
|
|
1742
|
+
for (const t of tokenize2(k.statement)) coveredTokens.add(t);
|
|
1743
|
+
for (const p of k.positive) coveredTokens.add(p);
|
|
1744
|
+
}
|
|
1745
|
+
const missingAspects = [];
|
|
1746
|
+
for (const qt of queryTokens) {
|
|
1747
|
+
if (!["how", "what", "why", "when", "where", "are", "the", "for", "with", "does", "from"].includes(qt)) {
|
|
1748
|
+
if (!coveredTokens.has(qt)) {
|
|
1749
|
+
missingAspects.push(qt);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
let state = "UNKNOWN";
|
|
1754
|
+
let reason;
|
|
1755
|
+
if (activeCandidates.length === 0 && staleCandidates.length > 0) {
|
|
1756
|
+
state = "STALE";
|
|
1757
|
+
reason = "STALE_EVIDENCE";
|
|
1758
|
+
} else if (activeCandidates.length === 0 && contradictedCandidates.length > 0) {
|
|
1759
|
+
state = "CONFLICTED";
|
|
1760
|
+
reason = "CONFLICTING_EVIDENCE";
|
|
1761
|
+
} else if (activeCandidates.length === 0) {
|
|
1762
|
+
state = "UNKNOWN";
|
|
1763
|
+
reason = rejectedItems.length > 0 && matchingItems.length === 0 ? "CONTEXT_MISMATCH" : "NOT_OBSERVED";
|
|
1764
|
+
} else if (missingAspects.length === 0 && activeCandidates.some((k) => k.confidence >= 0.85)) {
|
|
1765
|
+
state = "KNOWN";
|
|
1766
|
+
} else {
|
|
1767
|
+
state = "PARTIALLY_KNOWN";
|
|
1768
|
+
reason = "INSUFFICIENT_EVIDENCE";
|
|
1769
|
+
}
|
|
1770
|
+
const checkedArray = Array.from(previouslyChecked);
|
|
1771
|
+
let nextAction;
|
|
1772
|
+
let fallback;
|
|
1773
|
+
if (state === "KNOWN") {
|
|
1774
|
+
nextAction = {
|
|
1775
|
+
type: "USE_KNOWLEDGE",
|
|
1776
|
+
target: activeCandidates[0].id,
|
|
1777
|
+
reason: `Sufficient high-confidence knowledge available (${activeCandidates[0].id})`,
|
|
1778
|
+
relativeCost: RELATIVE_COSTS.USE_KNOWLEDGE,
|
|
1779
|
+
expectedInfoGain: 0.95
|
|
1780
|
+
};
|
|
1781
|
+
} else if (state === "CONFLICTED") {
|
|
1782
|
+
nextAction = {
|
|
1783
|
+
type: "ASK_USER",
|
|
1784
|
+
reason: "Opposing credible knowledge requires contextual disambiguation",
|
|
1785
|
+
relativeCost: RELATIVE_COSTS.ASK_USER,
|
|
1786
|
+
expectedInfoGain: 0.9
|
|
1787
|
+
};
|
|
1788
|
+
fallback = {
|
|
1789
|
+
type: "ask",
|
|
1790
|
+
question: `Conflicting conventions exist for "${question}". Which context applies?`
|
|
1791
|
+
};
|
|
1792
|
+
} else if (state === "STALE") {
|
|
1793
|
+
const staleItem = staleCandidates[0];
|
|
1794
|
+
const targetSource = staleItem.depends_on?.[0] || staleItem.evidence[0] || "source";
|
|
1795
|
+
nextAction = {
|
|
1796
|
+
type: "INSPECT_FILE",
|
|
1797
|
+
target: targetSource.replace(/^(code|file|test|doc):/, ""),
|
|
1798
|
+
reason: `Revalidate stale knowledge (${staleItem.id}) whose dependencies changed`,
|
|
1799
|
+
relativeCost: RELATIVE_COSTS.INSPECT_FILE,
|
|
1800
|
+
expectedInfoGain: 0.85
|
|
1801
|
+
};
|
|
1802
|
+
} else {
|
|
1803
|
+
if (learnedQuestionToAsk) {
|
|
1804
|
+
nextAction = {
|
|
1805
|
+
type: "ASK_USER",
|
|
1806
|
+
reason: learnedQuestionToAsk.reason,
|
|
1807
|
+
relativeCost: RELATIVE_COSTS.ASK_USER,
|
|
1808
|
+
expectedInfoGain: 0.9
|
|
1809
|
+
};
|
|
1810
|
+
fallback = {
|
|
1811
|
+
type: "ask",
|
|
1812
|
+
question: learnedQuestionToAsk.question
|
|
1813
|
+
};
|
|
1814
|
+
} else {
|
|
1815
|
+
const testCandidates = [
|
|
1816
|
+
`tests/${queryTokens[0] || "module"}.test.ts`,
|
|
1817
|
+
`test/${queryTokens[0] || "module"}.test.ts`,
|
|
1818
|
+
`tests/session-refresh.test.ts`
|
|
1819
|
+
];
|
|
1820
|
+
const uninspectedTest = testCandidates.find(
|
|
1821
|
+
(t) => !previouslyChecked.has(t) && !previouslyRejectedTargets.has(t)
|
|
1822
|
+
);
|
|
1823
|
+
const fileCandidates = [
|
|
1824
|
+
`src/${queryTokens[0] || "auth"}/${queryTokens[1] || "index"}.ts`,
|
|
1825
|
+
`src/${queryTokens[0] || "index"}.ts`
|
|
1826
|
+
];
|
|
1827
|
+
const uninspectedFile = fileCandidates.find(
|
|
1828
|
+
(f) => !previouslyChecked.has(f) && !previouslyRejectedTargets.has(f)
|
|
1829
|
+
);
|
|
1830
|
+
for (const t of testCandidates) {
|
|
1831
|
+
if (previouslyChecked.has(t)) {
|
|
1832
|
+
await storage.appendMetricEvent({
|
|
1833
|
+
type: "source_read_avoided",
|
|
1834
|
+
source: t,
|
|
1835
|
+
timestamp: Date.now()
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
for (const f of fileCandidates) {
|
|
1840
|
+
if (previouslyChecked.has(f)) {
|
|
1841
|
+
await storage.appendMetricEvent({
|
|
1842
|
+
type: "source_read_avoided",
|
|
1843
|
+
source: f,
|
|
1844
|
+
timestamp: Date.now()
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
if (uninspectedTest && missingAspects.some((m) => m.includes("order") || m.includes("test") || m.includes("behavior"))) {
|
|
1849
|
+
nextAction = {
|
|
1850
|
+
type: "INSPECT_TEST",
|
|
1851
|
+
target: uninspectedTest,
|
|
1852
|
+
reason: `Targeted test inspection for unconfirmed aspect: ${missingAspects.join(", ")}`,
|
|
1853
|
+
relativeCost: RELATIVE_COSTS.INSPECT_TEST,
|
|
1854
|
+
expectedInfoGain: 0.8
|
|
1855
|
+
};
|
|
1856
|
+
} else if (uninspectedFile) {
|
|
1857
|
+
nextAction = {
|
|
1858
|
+
type: "INSPECT_FILE",
|
|
1859
|
+
target: uninspectedFile,
|
|
1860
|
+
reason: `Direct source inspection for missing context: ${missingAspects.join(", ")}`,
|
|
1861
|
+
relativeCost: RELATIVE_COSTS.INSPECT_FILE,
|
|
1862
|
+
expectedInfoGain: 0.75
|
|
1863
|
+
};
|
|
1864
|
+
} else {
|
|
1865
|
+
nextAction = {
|
|
1866
|
+
type: "SEARCH_REPOSITORY",
|
|
1867
|
+
target: missingAspects.slice(0, 3).join(" ") || question,
|
|
1868
|
+
reason: `Targeted search for unobserved aspects: ${missingAspects.join(", ")}`,
|
|
1869
|
+
relativeCost: RELATIVE_COSTS.SEARCH_REPOSITORY,
|
|
1870
|
+
expectedInfoGain: 0.6
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
fallback = {
|
|
1874
|
+
type: "ask",
|
|
1875
|
+
question: `Could you clarify: ${question}? (Missing: ${missingAspects.join(", ") || "specifics"})`
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
if (state === "KNOWN" || nextAction && nextAction.type !== "SEARCH_REPOSITORY" && nextAction.type !== "STOP") {
|
|
1880
|
+
await storage.appendMetricEvent({
|
|
1881
|
+
type: "broad_search_avoided",
|
|
1882
|
+
targetAction: nextAction?.type,
|
|
1883
|
+
timestamp: Date.now()
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
for (const k of activeCandidates) {
|
|
1887
|
+
await storage.appendMetricEvent({
|
|
1888
|
+
type: "knowledge_reused",
|
|
1889
|
+
knowledgeId: k.id,
|
|
1890
|
+
timestamp: Date.now()
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
if (learnedQuestionToAsk || fallback?.type === "ask") {
|
|
1894
|
+
await storage.appendMetricEvent({
|
|
1895
|
+
type: "question_suggested",
|
|
1896
|
+
question: learnedQuestionToAsk?.question || fallback?.question,
|
|
1897
|
+
timestamp: Date.now()
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
await storage.appendMetricEvent({
|
|
1901
|
+
type: "resolve",
|
|
1902
|
+
state,
|
|
1903
|
+
action: nextAction?.type,
|
|
1904
|
+
question,
|
|
1905
|
+
timestamp: Date.now()
|
|
1906
|
+
});
|
|
1907
|
+
const receiptId = `receipt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1908
|
+
const receipt = {
|
|
1909
|
+
id: receiptId,
|
|
1910
|
+
question,
|
|
1911
|
+
checked: checkedArray,
|
|
1912
|
+
findings: activeCandidates.map((c) => c.statement),
|
|
1913
|
+
rejected: rejectedItems.map((r) => ({ target: r.id, reason: r.reason })),
|
|
1914
|
+
missing: missingAspects,
|
|
1915
|
+
next: nextAction,
|
|
1916
|
+
status: state === "KNOWN" ? "resolved" : "unresolved",
|
|
1917
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1918
|
+
positive,
|
|
1919
|
+
negative
|
|
1920
|
+
};
|
|
1921
|
+
await storage.saveReceipt(receipt);
|
|
1922
|
+
return {
|
|
1923
|
+
state,
|
|
1924
|
+
reason,
|
|
1925
|
+
known: activeCandidates.map((k) => ({
|
|
1926
|
+
id: k.id,
|
|
1927
|
+
statement: k.statement,
|
|
1928
|
+
confidence: k.confidenceTier ?? k.confidence,
|
|
1929
|
+
confidenceTier: k.confidenceTier,
|
|
1930
|
+
stage: k.stage
|
|
1931
|
+
})),
|
|
1932
|
+
applicable: positive.length > 0 ? positive : contextualCues.slice(0, 5),
|
|
1933
|
+
rejected: rejectedItems,
|
|
1934
|
+
missing: missingAspects,
|
|
1935
|
+
checked: checkedArray,
|
|
1936
|
+
next: nextAction,
|
|
1937
|
+
fallback,
|
|
1938
|
+
receiptId
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
export {
|
|
1942
|
+
EvidenceGraph,
|
|
1943
|
+
MetricsTracker,
|
|
1944
|
+
RELATIVE_COSTS,
|
|
1945
|
+
applyOutcomeToKnowledge,
|
|
1946
|
+
applyReconciliation,
|
|
1947
|
+
buildDependencyIndex,
|
|
1948
|
+
calculateEffectiveConfidence,
|
|
1949
|
+
calculateKnowledgeValue,
|
|
1950
|
+
checkKnowledgeListStaleness,
|
|
1951
|
+
checkKnowledgeStaleness,
|
|
1952
|
+
confidenceToTier,
|
|
1953
|
+
createEvidence,
|
|
1954
|
+
createKnowledge,
|
|
1955
|
+
createObservation,
|
|
1956
|
+
createOutcome,
|
|
1957
|
+
createUnknown,
|
|
1958
|
+
detectPolarContradiction,
|
|
1959
|
+
determineInitialStage,
|
|
1960
|
+
distillTrajectory,
|
|
1961
|
+
enforceKnowledgeBudget,
|
|
1962
|
+
evaluatePromotion,
|
|
1963
|
+
hasBeenInvestigated,
|
|
1964
|
+
hashString,
|
|
1965
|
+
learnFromTrajectory,
|
|
1966
|
+
markStaleKnowledge,
|
|
1967
|
+
normalizeSourceKey,
|
|
1968
|
+
processUserCorrection,
|
|
1969
|
+
reconcileKnowledge,
|
|
1970
|
+
recordOutcomeOnKnowledge,
|
|
1971
|
+
resolveEpistemicQuery,
|
|
1972
|
+
tierToConfidence,
|
|
1973
|
+
updateUnknown
|
|
1974
|
+
};
|
|
1975
|
+
//# sourceMappingURL=index.js.map
|