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,1748 @@
1
+ // src/storage/canonical.ts
2
+ import * as fs3 from "fs/promises";
3
+ import * as path3 from "path";
4
+
5
+ // src/rules/loader.ts
6
+ import YAML from "yaml";
7
+ import * as fs from "fs/promises";
8
+ import * as path from "path";
9
+ function validateRuleDefinition(rule, index = 0) {
10
+ const errors = [];
11
+ if (!rule || typeof rule !== "object") {
12
+ errors.push(`Rule at index ${index} must be an object.`);
13
+ return errors;
14
+ }
15
+ const r = rule;
16
+ if (!r.id || typeof r.id !== "string" || !r.id.trim()) {
17
+ errors.push(`Rule at index ${index} requires a non-empty string 'id'.`);
18
+ }
19
+ if (!r.statement || typeof r.statement !== "string" || !r.statement.trim()) {
20
+ errors.push(`Rule '${r.id || index}' requires a non-empty string 'statement'.`);
21
+ }
22
+ if (r.type) {
23
+ const validTypes = [
24
+ "rule",
25
+ "convention",
26
+ "decision",
27
+ "lesson",
28
+ "architecture",
29
+ "fact"
30
+ ];
31
+ if (!validTypes.includes(r.type)) {
32
+ errors.push(`Rule '${r.id || index}' has invalid type '${r.type}'. Allowed: ${validTypes.join(", ")}.`);
33
+ }
34
+ }
35
+ if (r.confidence !== void 0) {
36
+ if (typeof r.confidence !== "number" || r.confidence < 0 || r.confidence > 1) {
37
+ errors.push(`Rule '${r.id || index}' confidence must be a number between 0.0 and 1.0.`);
38
+ }
39
+ }
40
+ if (r.positive !== void 0 && !Array.isArray(r.positive)) {
41
+ errors.push(`Rule '${r.id || index}' positive cues must be an array of strings.`);
42
+ }
43
+ if (r.negative !== void 0 && !Array.isArray(r.negative)) {
44
+ errors.push(`Rule '${r.id || index}' negative cues must be an array of strings.`);
45
+ }
46
+ return errors;
47
+ }
48
+ function validateRuleSet(data) {
49
+ const errors = [];
50
+ if (!data || typeof data !== "object") {
51
+ return { valid: false, errors: ["RuleSet content must be an object or an array of rules."] };
52
+ }
53
+ let rules = [];
54
+ if (Array.isArray(data)) {
55
+ rules = data;
56
+ } else if ("rules" in data && Array.isArray(data.rules)) {
57
+ rules = data.rules;
58
+ } else if ("id" in data && "statement" in data) {
59
+ rules = [data];
60
+ } else {
61
+ return { valid: false, errors: ['RuleSet must contain a "rules" array or be a list of rule definitions.'] };
62
+ }
63
+ if (rules.length === 0) {
64
+ errors.push("RuleSet contains no rules.");
65
+ }
66
+ rules.forEach((rule, idx) => {
67
+ errors.push(...validateRuleDefinition(rule, idx));
68
+ });
69
+ return {
70
+ valid: errors.length === 0,
71
+ errors
72
+ };
73
+ }
74
+ function parseRuleSetYaml(content, sourcePath = "inline.yml") {
75
+ let parsed;
76
+ try {
77
+ parsed = YAML.parse(content);
78
+ } catch (err) {
79
+ throw new Error(`YAML syntax error in ${sourcePath}: ${err.message}`);
80
+ }
81
+ const validation = validateRuleSet(parsed);
82
+ if (!validation.valid) {
83
+ throw new Error(`Invalid RuleSet in ${sourcePath}:
84
+ - ${validation.errors.join("\n- ")}`);
85
+ }
86
+ if (Array.isArray(parsed)) {
87
+ return {
88
+ name: path.basename(sourcePath, path.extname(sourcePath)),
89
+ rules: parsed
90
+ };
91
+ }
92
+ if ("id" in parsed && "statement" in parsed && !("rules" in parsed)) {
93
+ return {
94
+ name: parsed.id,
95
+ rules: [parsed]
96
+ };
97
+ }
98
+ const set = parsed;
99
+ return {
100
+ name: set.name ?? path.basename(sourcePath, path.extname(sourcePath)),
101
+ version: set.version ?? 1,
102
+ scope: set.scope ?? "project",
103
+ description: set.description,
104
+ rules: set.rules
105
+ };
106
+ }
107
+ function stringifyRuleSetYaml(ruleSet) {
108
+ const payload = {};
109
+ if (ruleSet.name) payload.name = ruleSet.name;
110
+ if (ruleSet.version !== void 0) payload.version = ruleSet.version;
111
+ if (ruleSet.scope) payload.scope = ruleSet.scope;
112
+ if (ruleSet.description) payload.description = ruleSet.description;
113
+ payload.rules = ruleSet.rules.map((r) => {
114
+ const ruleObj = {
115
+ id: r.id
116
+ };
117
+ if (r.name) ruleObj.name = r.name;
118
+ ruleObj.statement = r.statement;
119
+ if (r.type && r.type !== "rule") ruleObj.type = r.type;
120
+ if (r.confidence !== void 0 && r.confidence !== 0.95) ruleObj.confidence = r.confidence;
121
+ if (r.scope && r.scope !== "project") ruleObj.scope = r.scope;
122
+ if (r.positive && r.positive.length > 0) ruleObj.positive = r.positive;
123
+ if (r.negative && r.negative.length > 0) ruleObj.negative = r.negative;
124
+ if (r.tags && r.tags.length > 0) ruleObj.tags = r.tags;
125
+ if (r.when && Object.keys(r.when).length > 0) ruleObj.when = r.when;
126
+ if (r.details && Object.keys(r.details).length > 0) ruleObj.details = r.details;
127
+ return ruleObj;
128
+ });
129
+ return YAML.stringify(payload, { indent: 2, lineWidth: 100 });
130
+ }
131
+ async function loadRuleSetFile(filePath) {
132
+ const content = await fs.readFile(filePath, "utf-8");
133
+ return parseRuleSetYaml(content, filePath);
134
+ }
135
+ async function findRuleFiles(rootDir, storageRulesDir) {
136
+ const candidates = [];
137
+ const rulesDir = storageRulesDir || path.resolve(rootDir, ".bilmem", "rules");
138
+ try {
139
+ const entries = await fs.readdir(rulesDir, { withFileTypes: true });
140
+ for (const entry of entries) {
141
+ if (entry.isFile() && (entry.name.endsWith(".yml") || entry.name.endsWith(".yaml"))) {
142
+ candidates.push(path.join(rulesDir, entry.name));
143
+ }
144
+ }
145
+ } catch {
146
+ }
147
+ for (const name of ["rules.yml", "rules.yaml"]) {
148
+ const p = path.resolve(rootDir, ".bilmem", name);
149
+ try {
150
+ const st = await fs.stat(p);
151
+ if (st.isFile()) candidates.push(p);
152
+ } catch {
153
+ }
154
+ }
155
+ for (const name of ["bilmem.rules.yml", "bilmem.rules.yaml", "rules.yml", "rules.yaml"]) {
156
+ const p = path.resolve(rootDir, name);
157
+ try {
158
+ const st = await fs.stat(p);
159
+ if (st.isFile()) candidates.push(p);
160
+ } catch {
161
+ }
162
+ }
163
+ return Array.from(new Set(candidates));
164
+ }
165
+
166
+ // src/stats/events.ts
167
+ import * as fs2 from "fs/promises";
168
+ import * as path2 from "path";
169
+ function getEventsFilePath(rootDir) {
170
+ return path2.join(rootDir, ".bilmem", "events.jsonl");
171
+ }
172
+ async function readMetricEvents(rootDir, filter) {
173
+ const filePath = getEventsFilePath(rootDir);
174
+ let raw;
175
+ try {
176
+ raw = await fs2.readFile(filePath, "utf-8");
177
+ } catch (err) {
178
+ if (err.code === "ENOENT") {
179
+ return [];
180
+ }
181
+ return [];
182
+ }
183
+ const lines = raw.split("\n");
184
+ const events = [];
185
+ for (const line of lines) {
186
+ const trimmed = line.trim();
187
+ if (!trimmed) continue;
188
+ try {
189
+ const parsed = JSON.parse(trimmed);
190
+ if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
191
+ continue;
192
+ }
193
+ if (typeof parsed.timestamp !== "number") {
194
+ continue;
195
+ }
196
+ if (filter?.sinceTimestamp !== void 0 && parsed.timestamp < filter.sinceTimestamp) {
197
+ continue;
198
+ }
199
+ if (filter?.knowledgeId !== void 0) {
200
+ const kid = parsed.knowledgeId;
201
+ if (kid !== filter.knowledgeId) {
202
+ continue;
203
+ }
204
+ }
205
+ events.push(parsed);
206
+ } catch {
207
+ continue;
208
+ }
209
+ }
210
+ return events;
211
+ }
212
+ async function resetMetricEvents(rootDir) {
213
+ const filePath = getEventsFilePath(rootDir);
214
+ try {
215
+ await fs2.mkdir(path2.dirname(filePath), { recursive: true });
216
+ await fs2.writeFile(filePath, "", "utf-8");
217
+ } catch (err) {
218
+ if (err.code !== "ENOENT") throw err;
219
+ }
220
+ }
221
+
222
+ // src/storage/canonical.ts
223
+ function sanitizeFileName(id) {
224
+ return encodeURIComponent(id).replace(/%/g, "_");
225
+ }
226
+ function desanitizeFileName(name) {
227
+ try {
228
+ return decodeURIComponent(name.replace(/_/g, "%"));
229
+ } catch {
230
+ return name;
231
+ }
232
+ }
233
+ var Storage = class {
234
+ baseDir;
235
+ knowledgeDir;
236
+ evidenceDir;
237
+ observationDir;
238
+ unknownDir;
239
+ rulesDir;
240
+ configFile;
241
+ relationsFile;
242
+ outcomesFile;
243
+ metricsFile;
244
+ revisionsFile;
245
+ knowledgeJsonlFile;
246
+ evidenceJsonlFile;
247
+ observationJsonlFile;
248
+ unknownsJsonlFile;
249
+ receiptsDir;
250
+ receiptsJsonlFile;
251
+ eventsFile;
252
+ constructor(rootDir = process.cwd()) {
253
+ this.baseDir = path3.resolve(rootDir, ".bilmem");
254
+ this.knowledgeDir = path3.join(this.baseDir, "knowledge");
255
+ this.evidenceDir = path3.join(this.baseDir, "evidence");
256
+ this.observationDir = path3.join(this.baseDir, "observations");
257
+ this.unknownDir = path3.join(this.baseDir, "unknowns");
258
+ this.rulesDir = path3.join(this.baseDir, "rules");
259
+ this.receiptsDir = path3.join(this.baseDir, "receipts");
260
+ this.configFile = path3.join(this.baseDir, "config.json");
261
+ this.relationsFile = path3.join(this.baseDir, "relations.json");
262
+ this.outcomesFile = path3.join(this.baseDir, "outcomes.jsonl");
263
+ this.metricsFile = path3.join(this.baseDir, "metrics.json");
264
+ this.revisionsFile = path3.join(this.baseDir, "revisions.jsonl");
265
+ this.knowledgeJsonlFile = path3.join(this.baseDir, "knowledge.jsonl");
266
+ this.evidenceJsonlFile = path3.join(this.baseDir, "evidence.jsonl");
267
+ this.observationJsonlFile = path3.join(this.baseDir, "observations.jsonl");
268
+ this.unknownsJsonlFile = path3.join(this.baseDir, "unknowns.jsonl");
269
+ this.receiptsJsonlFile = path3.join(this.baseDir, "receipts.jsonl");
270
+ this.eventsFile = path3.join(this.baseDir, "events.jsonl");
271
+ }
272
+ getBaseDir() {
273
+ return this.baseDir;
274
+ }
275
+ async isInitialized() {
276
+ try {
277
+ await fs3.access(this.configFile);
278
+ return true;
279
+ } catch {
280
+ return false;
281
+ }
282
+ }
283
+ async init(options = {}) {
284
+ await fs3.mkdir(this.knowledgeDir, { recursive: true });
285
+ await fs3.mkdir(this.evidenceDir, { recursive: true });
286
+ await fs3.mkdir(this.observationDir, { recursive: true });
287
+ await fs3.mkdir(this.unknownDir, { recursive: true });
288
+ await fs3.mkdir(this.rulesDir, { recursive: true });
289
+ await fs3.mkdir(this.receiptsDir, { recursive: true });
290
+ let config;
291
+ if (await this.isInitialized()) {
292
+ config = await this.getConfig();
293
+ } else {
294
+ config = {
295
+ schemaVersion: 1,
296
+ projectName: options.projectName ?? path3.basename(path3.dirname(this.baseDir)) ?? "project",
297
+ defaultScope: options.defaultScope ?? "project",
298
+ dataDir: this.baseDir
299
+ };
300
+ await this.saveConfig(config);
301
+ }
302
+ try {
303
+ await fs3.access(this.relationsFile);
304
+ } catch {
305
+ await fs3.writeFile(this.relationsFile, JSON.stringify([], null, 2), "utf-8");
306
+ }
307
+ try {
308
+ await fs3.access(this.metricsFile);
309
+ } catch {
310
+ const defaultMetrics = {
311
+ knowledgeHits: 0,
312
+ knowledgeMisses: 0,
313
+ knowledgeReused: 0,
314
+ unknownsResolved: 0,
315
+ repeatedInvestigationsPrevented: 0,
316
+ candidateKnowledgeMerged: 0,
317
+ staleKnowledgeDetected: 0,
318
+ contradictionsDetected: 0,
319
+ knowledgePromoted: 0,
320
+ knowledgeSuperseded: 0
321
+ };
322
+ await fs3.writeFile(this.metricsFile, JSON.stringify(defaultMetrics, null, 2), "utf-8");
323
+ }
324
+ return config;
325
+ }
326
+ // --- Config ---
327
+ async getConfig() {
328
+ const raw = await fs3.readFile(this.configFile, "utf-8");
329
+ return JSON.parse(raw);
330
+ }
331
+ async saveConfig(config) {
332
+ await fs3.writeFile(this.configFile, JSON.stringify(config, null, 2), "utf-8");
333
+ }
334
+ async updateConfig(updates) {
335
+ const current = await this.getConfig();
336
+ const updated = { ...current, ...updates };
337
+ await this.saveConfig(updated);
338
+ return updated;
339
+ }
340
+ // --- Knowledge ---
341
+ async saveKnowledge(knowledge) {
342
+ await fs3.mkdir(this.knowledgeDir, { recursive: true });
343
+ const filename = `${sanitizeFileName(knowledge.id)}.json`;
344
+ const filePath = path3.join(this.knowledgeDir, filename);
345
+ await fs3.writeFile(filePath, JSON.stringify(knowledge, null, 2), "utf-8");
346
+ await fs3.appendFile(this.knowledgeJsonlFile, JSON.stringify(knowledge) + "\n", "utf-8");
347
+ }
348
+ async getKnowledge(id) {
349
+ const filename = `${sanitizeFileName(id)}.json`;
350
+ const filePath = path3.join(this.knowledgeDir, filename);
351
+ try {
352
+ const raw = await fs3.readFile(filePath, "utf-8");
353
+ return JSON.parse(raw);
354
+ } catch {
355
+ return void 0;
356
+ }
357
+ }
358
+ async getAllKnowledge() {
359
+ try {
360
+ const files = await fs3.readdir(this.knowledgeDir);
361
+ const items = [];
362
+ for (const file of files) {
363
+ if (!file.endsWith(".json")) continue;
364
+ const raw = await fs3.readFile(path3.join(this.knowledgeDir, file), "utf-8");
365
+ try {
366
+ items.push(JSON.parse(raw));
367
+ } catch {
368
+ }
369
+ }
370
+ return items;
371
+ } catch {
372
+ return [];
373
+ }
374
+ }
375
+ async deleteKnowledge(id) {
376
+ const filename = `${sanitizeFileName(id)}.json`;
377
+ const filePath = path3.join(this.knowledgeDir, filename);
378
+ try {
379
+ await fs3.unlink(filePath);
380
+ return true;
381
+ } catch {
382
+ return false;
383
+ }
384
+ }
385
+ // --- Evidence ---
386
+ async saveEvidence(evidence) {
387
+ await fs3.mkdir(this.evidenceDir, { recursive: true });
388
+ const filename = `${sanitizeFileName(evidence.id)}.json`;
389
+ const filePath = path3.join(this.evidenceDir, filename);
390
+ await fs3.writeFile(filePath, JSON.stringify(evidence, null, 2), "utf-8");
391
+ await fs3.appendFile(this.evidenceJsonlFile, JSON.stringify(evidence) + "\n", "utf-8");
392
+ }
393
+ async getEvidence(id) {
394
+ const filename = `${sanitizeFileName(id)}.json`;
395
+ const filePath = path3.join(this.evidenceDir, filename);
396
+ try {
397
+ const raw = await fs3.readFile(filePath, "utf-8");
398
+ return JSON.parse(raw);
399
+ } catch {
400
+ return void 0;
401
+ }
402
+ }
403
+ async getAllEvidence() {
404
+ try {
405
+ const files = await fs3.readdir(this.evidenceDir);
406
+ const items = [];
407
+ for (const file of files) {
408
+ if (!file.endsWith(".json")) continue;
409
+ const raw = await fs3.readFile(path3.join(this.evidenceDir, file), "utf-8");
410
+ try {
411
+ items.push(JSON.parse(raw));
412
+ } catch {
413
+ }
414
+ }
415
+ return items;
416
+ } catch {
417
+ return [];
418
+ }
419
+ }
420
+ // --- Observations ---
421
+ async saveObservation(obs) {
422
+ await fs3.mkdir(this.observationDir, { recursive: true });
423
+ const filename = `${sanitizeFileName(obs.id)}.json`;
424
+ const filePath = path3.join(this.observationDir, filename);
425
+ await fs3.writeFile(filePath, JSON.stringify(obs, null, 2), "utf-8");
426
+ await fs3.appendFile(this.observationJsonlFile, JSON.stringify(obs) + "\n", "utf-8");
427
+ }
428
+ async getObservation(id) {
429
+ const filename = `${sanitizeFileName(id)}.json`;
430
+ const filePath = path3.join(this.observationDir, filename);
431
+ try {
432
+ const raw = await fs3.readFile(filePath, "utf-8");
433
+ return JSON.parse(raw);
434
+ } catch {
435
+ return void 0;
436
+ }
437
+ }
438
+ async getAllObservations() {
439
+ try {
440
+ const files = await fs3.readdir(this.observationDir);
441
+ const items = [];
442
+ for (const file of files) {
443
+ if (!file.endsWith(".json")) continue;
444
+ const raw = await fs3.readFile(path3.join(this.observationDir, file), "utf-8");
445
+ try {
446
+ items.push(JSON.parse(raw));
447
+ } catch {
448
+ }
449
+ }
450
+ return items;
451
+ } catch {
452
+ return [];
453
+ }
454
+ }
455
+ async deleteObservation(id) {
456
+ const filename = `${sanitizeFileName(id)}.json`;
457
+ const filePath = path3.join(this.observationDir, filename);
458
+ try {
459
+ await fs3.unlink(filePath);
460
+ return true;
461
+ } catch {
462
+ return false;
463
+ }
464
+ }
465
+ // --- Unknowns ---
466
+ async saveUnknown(record) {
467
+ await fs3.mkdir(this.unknownDir, { recursive: true });
468
+ const filename = `${sanitizeFileName(record.id)}.json`;
469
+ const filePath = path3.join(this.unknownDir, filename);
470
+ await fs3.writeFile(filePath, JSON.stringify(record, null, 2), "utf-8");
471
+ await fs3.appendFile(this.unknownsJsonlFile, JSON.stringify(record) + "\n", "utf-8");
472
+ }
473
+ async getUnknown(id) {
474
+ const filename = `${sanitizeFileName(id)}.json`;
475
+ const filePath = path3.join(this.unknownDir, filename);
476
+ try {
477
+ const raw = await fs3.readFile(filePath, "utf-8");
478
+ return JSON.parse(raw);
479
+ } catch {
480
+ return void 0;
481
+ }
482
+ }
483
+ async getAllUnknowns() {
484
+ try {
485
+ const files = await fs3.readdir(this.unknownDir);
486
+ const items = [];
487
+ for (const file of files) {
488
+ if (!file.endsWith(".json")) continue;
489
+ const raw = await fs3.readFile(path3.join(this.unknownDir, file), "utf-8");
490
+ try {
491
+ items.push(JSON.parse(raw));
492
+ } catch {
493
+ }
494
+ }
495
+ return items;
496
+ } catch {
497
+ return [];
498
+ }
499
+ }
500
+ async deleteUnknown(id) {
501
+ const filename = `${sanitizeFileName(id)}.json`;
502
+ const filePath = path3.join(this.unknownDir, filename);
503
+ try {
504
+ await fs3.unlink(filePath);
505
+ return true;
506
+ } catch {
507
+ return false;
508
+ }
509
+ }
510
+ // --- Outcomes (Append-oriented log) ---
511
+ async appendOutcome(outcome) {
512
+ const line = JSON.stringify(outcome) + "\n";
513
+ await fs3.appendFile(this.outcomesFile, line, "utf-8");
514
+ if (outcome.knowledgeId) {
515
+ await this.appendMetricEvent({
516
+ type: "outcome",
517
+ knowledgeId: outcome.knowledgeId,
518
+ result: outcome.result,
519
+ timestamp: Date.now()
520
+ });
521
+ if (outcome.result === "success") {
522
+ await this.appendMetricEvent({
523
+ type: "question_resolved",
524
+ questionId: outcome.knowledgeId,
525
+ timestamp: Date.now()
526
+ });
527
+ }
528
+ }
529
+ }
530
+ async getAllOutcomes() {
531
+ try {
532
+ const raw = await fs3.readFile(this.outcomesFile, "utf-8");
533
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
534
+ return lines.map((l) => JSON.parse(l));
535
+ } catch {
536
+ return [];
537
+ }
538
+ }
539
+ // --- Metric Events (Append-only local log) ---
540
+ async appendMetricEvent(event) {
541
+ try {
542
+ await fs3.mkdir(this.baseDir, { recursive: true });
543
+ await fs3.appendFile(this.eventsFile, JSON.stringify(event) + "\n", "utf-8");
544
+ } catch {
545
+ }
546
+ }
547
+ async getMetricEvents(filter) {
548
+ return readMetricEvents(path3.dirname(this.baseDir), filter);
549
+ }
550
+ async resetMetricEvents() {
551
+ return resetMetricEvents(path3.dirname(this.baseDir));
552
+ }
553
+ // --- Investigation Receipts ---
554
+ async saveReceipt(receipt) {
555
+ await fs3.mkdir(this.receiptsDir, { recursive: true });
556
+ const filename = `${sanitizeFileName(receipt.id)}.json`;
557
+ const filePath = path3.join(this.receiptsDir, filename);
558
+ await fs3.writeFile(filePath, JSON.stringify(receipt, null, 2), "utf-8");
559
+ await fs3.appendFile(this.receiptsJsonlFile, JSON.stringify(receipt) + "\n", "utf-8");
560
+ }
561
+ async getReceipt(id) {
562
+ const filename = `${sanitizeFileName(id)}.json`;
563
+ const filePath = path3.join(this.receiptsDir, filename);
564
+ try {
565
+ const raw = await fs3.readFile(filePath, "utf-8");
566
+ return JSON.parse(raw);
567
+ } catch {
568
+ return void 0;
569
+ }
570
+ }
571
+ async getAllReceipts() {
572
+ try {
573
+ const files = await fs3.readdir(this.receiptsDir);
574
+ const items = [];
575
+ for (const file of files) {
576
+ if (!file.endsWith(".json")) continue;
577
+ const raw = await fs3.readFile(path3.join(this.receiptsDir, file), "utf-8");
578
+ try {
579
+ items.push(JSON.parse(raw));
580
+ } catch {
581
+ }
582
+ }
583
+ return items;
584
+ } catch {
585
+ return [];
586
+ }
587
+ }
588
+ // --- Relations ---
589
+ async getRelations() {
590
+ try {
591
+ const raw = await fs3.readFile(this.relationsFile, "utf-8");
592
+ return JSON.parse(raw);
593
+ } catch {
594
+ return [];
595
+ }
596
+ }
597
+ async saveRelations(relations) {
598
+ await fs3.writeFile(this.relationsFile, JSON.stringify(relations, null, 2), "utf-8");
599
+ }
600
+ // --- Metrics ---
601
+ async getMetrics() {
602
+ try {
603
+ const raw = await fs3.readFile(this.metricsFile, "utf-8");
604
+ return JSON.parse(raw);
605
+ } catch {
606
+ return {
607
+ knowledgeHits: 0,
608
+ knowledgeMisses: 0,
609
+ knowledgeReused: 0,
610
+ unknownsResolved: 0,
611
+ repeatedInvestigationsPrevented: 0,
612
+ candidateKnowledgeMerged: 0,
613
+ staleKnowledgeDetected: 0,
614
+ contradictionsDetected: 0,
615
+ knowledgePromoted: 0,
616
+ knowledgeSuperseded: 0
617
+ };
618
+ }
619
+ }
620
+ async saveMetrics(metrics) {
621
+ await fs3.writeFile(this.metricsFile, JSON.stringify(metrics, null, 2), "utf-8");
622
+ }
623
+ // --- Rules ---
624
+ getRulesDir() {
625
+ return this.rulesDir;
626
+ }
627
+ async loadRuleSets() {
628
+ const files = await findRuleFiles(path3.dirname(this.baseDir), this.rulesDir);
629
+ const sets = [];
630
+ for (const f of files) {
631
+ try {
632
+ sets.push(await loadRuleSetFile(f));
633
+ } catch {
634
+ }
635
+ }
636
+ return sets;
637
+ }
638
+ async saveRuleSet(ruleSet, fileName = "default.yml") {
639
+ await fs3.mkdir(this.rulesDir, { recursive: true });
640
+ const cleanName = fileName.endsWith(".yml") || fileName.endsWith(".yaml") ? fileName : `${fileName}.yml`;
641
+ const targetPath = path3.join(this.rulesDir, cleanName);
642
+ const yaml = stringifyRuleSetYaml(ruleSet);
643
+ await fs3.writeFile(targetPath, yaml, "utf-8");
644
+ return targetPath;
645
+ }
646
+ async addRule(rule, fileName = "default.yml") {
647
+ await fs3.mkdir(this.rulesDir, { recursive: true });
648
+ const cleanName = fileName.endsWith(".yml") || fileName.endsWith(".yaml") ? fileName : `${fileName}.yml`;
649
+ const targetPath = path3.join(this.rulesDir, cleanName);
650
+ let existingSet;
651
+ try {
652
+ existingSet = await loadRuleSetFile(targetPath);
653
+ } catch {
654
+ existingSet = {
655
+ name: path3.basename(cleanName, path3.extname(cleanName)),
656
+ rules: []
657
+ };
658
+ }
659
+ const existingIdx = existingSet.rules.findIndex((r) => r.id === rule.id);
660
+ if (existingIdx >= 0) {
661
+ existingSet.rules[existingIdx] = rule;
662
+ } else {
663
+ existingSet.rules.push(rule);
664
+ }
665
+ const yaml = stringifyRuleSetYaml(existingSet);
666
+ await fs3.writeFile(targetPath, yaml, "utf-8");
667
+ return targetPath;
668
+ }
669
+ // --- Revisions & Incremental Learning Diff ---
670
+ async appendRevision(snapshot) {
671
+ const line = JSON.stringify(snapshot) + "\n";
672
+ await fs3.appendFile(this.revisionsFile, line, "utf-8");
673
+ }
674
+ async getRevisions() {
675
+ try {
676
+ const raw = await fs3.readFile(this.revisionsFile, "utf-8");
677
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
678
+ return lines.map((l) => JSON.parse(l));
679
+ } catch {
680
+ return [];
681
+ }
682
+ }
683
+ async getLatestRevision() {
684
+ const revs = await this.getRevisions();
685
+ return revs.length > 0 ? revs[revs.length - 1] : void 0;
686
+ }
687
+ async getDiff(sinceRef) {
688
+ const revisions = await this.getRevisions();
689
+ let baselineSnapshot;
690
+ let targetSnapshot;
691
+ if (sinceRef && sinceRef.includes("..")) {
692
+ const [startRef, endRef] = sinceRef.split("..").map((s) => s.trim().toLowerCase());
693
+ baselineSnapshot = revisions.find(
694
+ (r) => r.id.toLowerCase() === startRef || r.commit?.toLowerCase() === startRef || r.commit?.toLowerCase().startsWith(startRef) || r.id.toLowerCase().startsWith(startRef)
695
+ );
696
+ targetSnapshot = revisions.find(
697
+ (r) => r.id.toLowerCase() === endRef || r.commit?.toLowerCase() === endRef || r.commit?.toLowerCase().startsWith(endRef) || r.id.toLowerCase().startsWith(endRef)
698
+ );
699
+ } else if (sinceRef) {
700
+ const cleanRef = sinceRef.trim().toLowerCase();
701
+ baselineSnapshot = revisions.find(
702
+ (r) => r.id.toLowerCase() === cleanRef || r.commit?.toLowerCase() === cleanRef || r.commit?.toLowerCase().startsWith(cleanRef) || r.id.toLowerCase().startsWith(cleanRef)
703
+ );
704
+ } else {
705
+ if (revisions.length >= 2) {
706
+ baselineSnapshot = revisions[revisions.length - 2];
707
+ } else if (revisions.length === 1) {
708
+ baselineSnapshot = revisions[0];
709
+ }
710
+ }
711
+ const baselineTimestamp = baselineSnapshot?.timestamp ?? "1970-01-01T00:00:00.000Z";
712
+ const baselineTime = new Date(baselineTimestamp).getTime();
713
+ const targetTime = targetSnapshot ? new Date(targetSnapshot.timestamp).getTime() : Date.now();
714
+ const inWindow = (time) => time >= baselineTime && time <= targetTime;
715
+ const allObservations = await this.getAllObservations();
716
+ const allKnowledge = await this.getAllKnowledge();
717
+ const allUnknowns = await this.getAllUnknowns();
718
+ let observationsAdded = 0;
719
+ for (const obs of allObservations) {
720
+ const obsTime = new Date(obs.recordedAt || obs.provenance?.timestamp || 0).getTime();
721
+ if (inWindow(obsTime)) {
722
+ observationsAdded++;
723
+ }
724
+ }
725
+ let candidatesAdded = 0;
726
+ let strengthenedCount = 0;
727
+ let revisedCount = 0;
728
+ let contradictionCount = 0;
729
+ let invalidatedCount = 0;
730
+ let needsRevalidationCount = 0;
731
+ const strengthened = [];
732
+ const revised = [];
733
+ const contradictions = [];
734
+ const invalidated = [];
735
+ const needsRevalidation = [];
736
+ for (const k of allKnowledge) {
737
+ const createdTime = new Date(k.createdAt).getTime();
738
+ const updatedTime = new Date(k.updatedAt).getTime();
739
+ if ((k.status === "invalid" || k.status === "invalidated") && inWindow(updatedTime)) {
740
+ invalidatedCount++;
741
+ invalidated.push(k.id);
742
+ continue;
743
+ }
744
+ if (k.status === "contradicted" && inWindow(updatedTime)) {
745
+ contradictionCount++;
746
+ contradictions.push(k.id);
747
+ continue;
748
+ }
749
+ if ((k.status === "needs_revalidation" || k.status === "stale") && inWindow(updatedTime)) {
750
+ needsRevalidationCount++;
751
+ needsRevalidation.push(k.id);
752
+ continue;
753
+ }
754
+ if (inWindow(createdTime)) {
755
+ candidatesAdded++;
756
+ } else if (inWindow(updatedTime)) {
757
+ if (k.metrics.support_count > 1 || k.confidence >= 0.85 || k.metrics.successful_outcomes > 0) {
758
+ strengthenedCount++;
759
+ strengthened.push(k.id);
760
+ } else {
761
+ revisedCount++;
762
+ revised.push(k.id);
763
+ }
764
+ }
765
+ }
766
+ let unknownsCount = 0;
767
+ const unknownsList = [];
768
+ for (const u of allUnknowns) {
769
+ const uTime = new Date(u.createdAt || u.updatedAt || 0).getTime();
770
+ if (inWindow(uTime)) {
771
+ unknownsCount++;
772
+ unknownsList.push(u.id);
773
+ }
774
+ }
775
+ const refLabel = sinceRef ?? (baselineSnapshot ? baselineSnapshot.commit?.slice(0, 7) || baselineSnapshot.id.slice(0, 7) : void 0);
776
+ return {
777
+ sinceRef: refLabel,
778
+ observationsAdded,
779
+ candidatesAdded,
780
+ strengthenedCount,
781
+ revisedCount,
782
+ contradictionCount,
783
+ unknownsCount,
784
+ needsRevalidationCount,
785
+ invalidatedCount,
786
+ details: {
787
+ strengthened,
788
+ revised,
789
+ contradictions,
790
+ unknowns: unknownsList,
791
+ needsRevalidation,
792
+ invalidated
793
+ }
794
+ };
795
+ }
796
+ };
797
+ function formatKnowledgeDiff(diff) {
798
+ const header = diff.sinceRef ? `Since ${diff.sinceRef}:` : "Recent knowledge changes:";
799
+ const lines = [
800
+ header,
801
+ "",
802
+ `+ ${diff.observationsAdded} observations`,
803
+ `+ ${diff.candidatesAdded} candidates`,
804
+ `\u2191 ${diff.strengthenedCount} knowledge strengthened`,
805
+ `~ ${diff.revisedCount} knowledge revised`,
806
+ `! ${diff.contradictionCount} contradiction${diff.contradictionCount === 1 ? "" : "s"}`,
807
+ `? ${diff.unknownsCount ?? 0} unknowns`,
808
+ `\xD7 ${diff.needsRevalidationCount ?? 0} knowledge needs revalidation`,
809
+ `- ${diff.invalidatedCount} invalidated`
810
+ ];
811
+ return lines.join("\n");
812
+ }
813
+
814
+ // src/storage/portable.ts
815
+ import YAML2 from "yaml";
816
+
817
+ // src/core/knowledge.ts
818
+ import { createHash } from "crypto";
819
+
820
+ // src/core/quarantine.ts
821
+ function determineInitialStage(knowledge) {
822
+ if (knowledge.type === "fact" || knowledge.type === "architecture" || knowledge.confidenceTier === "high" || knowledge.confidence !== void 0 && knowledge.confidence >= 0.85) {
823
+ return "active";
824
+ }
825
+ if (knowledge.type === "rule" && ((knowledge.evidence?.length ?? 0) >= 2 || knowledge.confidence !== void 0 && knowledge.confidence >= 0.75)) {
826
+ return "probation";
827
+ }
828
+ if (knowledge.confidence !== void 0 && knowledge.confidence >= 0.7) {
829
+ return "probation";
830
+ }
831
+ return "candidate";
832
+ }
833
+ function evaluatePromotion(knowledge) {
834
+ const stage = knowledge.stage ?? "candidate";
835
+ const supportCount = knowledge.metrics.support_count;
836
+ const reuseCount = knowledge.metrics.reuse_count;
837
+ const successCount = Math.max(knowledge.outcomesSummary?.successful ?? 0, knowledge.metrics.successful_outcomes ?? 0);
838
+ const misleadingCount = Math.max(knowledge.outcomesSummary?.misleading ?? 0, knowledge.metrics.failed_outcomes ?? 0);
839
+ if (stage === "candidate") {
840
+ if (supportCount >= 2 || knowledge.evidence && knowledge.evidence.length >= 2 || knowledge.confidence >= 0.85) {
841
+ return {
842
+ promoted: true,
843
+ newStage: "probation",
844
+ reason: `Supported by ${supportCount} sources / ${knowledge.evidence.length} evidence nodes`
845
+ };
846
+ }
847
+ } else if (stage === "probation") {
848
+ if (successCount > 0 && misleadingCount === 0 || reuseCount >= 2 && misleadingCount === 0) {
849
+ return {
850
+ promoted: true,
851
+ newStage: "active",
852
+ reason: `Verified by ${successCount} successful task outcomes and ${reuseCount} reuses`
853
+ };
854
+ }
855
+ }
856
+ return { promoted: false, newStage: stage };
857
+ }
858
+ function calculateKnowledgeValue(knowledge) {
859
+ const reuse = knowledge.metrics.reuse_count;
860
+ const success = knowledge.outcomesSummary?.successful ?? knowledge.metrics.successful_outcomes;
861
+ const misleading = knowledge.outcomesSummary?.misleading ?? knowledge.metrics.failed_outcomes;
862
+ const evidenceCount = Math.min(knowledge.evidence.length, 6);
863
+ const confidence = knowledge.confidence;
864
+ let value = reuse * 2.5 + success * 4 + evidenceCount * 1.5 + confidence * 2 - misleading * 5;
865
+ if (knowledge.status === "active") value += 5;
866
+ if (knowledge.status === "needs_revalidation") value -= 3;
867
+ if (knowledge.status === "contradicted") value -= 8;
868
+ if (knowledge.status === "stale") value -= 4;
869
+ if (knowledge.stage === "active") value += 3;
870
+ if (knowledge.stage === "candidate") value -= 1;
871
+ return Math.round(value * 10) / 10;
872
+ }
873
+
874
+ // src/core/knowledge.ts
875
+ function tierToConfidence(tier) {
876
+ switch (tier) {
877
+ case "low":
878
+ return 0.5;
879
+ case "medium":
880
+ return 0.75;
881
+ case "high":
882
+ return 0.95;
883
+ }
884
+ }
885
+ function confidenceToTier(score) {
886
+ if (score >= 0.85) return "high";
887
+ if (score >= 0.65) return "medium";
888
+ return "low";
889
+ }
890
+ function hashString(input) {
891
+ return createHash("sha256").update(input.trim().toLowerCase()).digest("hex").slice(0, 16);
892
+ }
893
+ function createKnowledge(params) {
894
+ const now = (/* @__PURE__ */ new Date()).toISOString();
895
+ const confidenceTier = params.confidenceTier ?? (params.confidence !== void 0 ? confidenceToTier(params.confidence) : void 0);
896
+ const confidence = params.confidence !== void 0 ? params.confidence : confidenceTier ? tierToConfidence(confidenceTier) : 0.85;
897
+ const dependsOn = new Set(params.depends_on ?? []);
898
+ if (params.evidence) {
899
+ for (const ev of params.evidence) {
900
+ if (ev.startsWith("file:") || ev.startsWith("test:") || ev.startsWith("code:")) {
901
+ dependsOn.add(ev);
902
+ } else if (ev.includes("/") || ev.endsWith(".json") || ev.endsWith(".yaml") || ev.endsWith(".yml")) {
903
+ dependsOn.add(`file:${ev}`);
904
+ }
905
+ }
906
+ }
907
+ const initialStage = params.stage ?? determineInitialStage({
908
+ type: params.type ?? "lesson",
909
+ evidence: params.evidence ?? [],
910
+ confidence
911
+ });
912
+ const status = params.status ?? (initialStage === "candidate" ? "candidate" : "active");
913
+ const history = params.history ?? [
914
+ {
915
+ timestamp: now,
916
+ stage: initialStage,
917
+ status,
918
+ reason: `Initial knowledge recorded (${params.type ?? "lesson"})`,
919
+ source: params.evidence?.[0]
920
+ }
921
+ ];
922
+ const knowledgeItem = {
923
+ id: params.id.trim(),
924
+ type: params.type ?? "lesson",
925
+ status,
926
+ stage: initialStage,
927
+ statement: params.statement.trim(),
928
+ positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),
929
+ negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),
930
+ relations: Array.from(new Set(params.relations?.map((r) => r.trim()) ?? [])),
931
+ depends_on: Array.from(dependsOn),
932
+ resolves: params.resolves ? Array.from(new Set(params.resolves.map((r) => r.trim()))) : void 0,
933
+ question: params.question?.trim(),
934
+ confidence: Math.min(1, Math.max(0, confidence)),
935
+ confidenceTier,
936
+ evidence: Array.from(new Set(params.evidence ?? [])),
937
+ scope: params.scope ?? "project",
938
+ tags: Array.from(new Set(params.tags?.map((t) => t.trim().toLowerCase()) ?? [])),
939
+ temporal: {
940
+ revision: 1
941
+ },
942
+ metrics: {
943
+ reuse_count: 0,
944
+ support_count: 1,
945
+ contradiction_count: 0,
946
+ successful_outcomes: 0,
947
+ failed_outcomes: 0
948
+ },
949
+ outcomesSummary: params.outcomesSummary ?? {
950
+ applied: 0,
951
+ successful: 0,
952
+ misleading: 0,
953
+ neutral: 0
954
+ },
955
+ history,
956
+ value: 0,
957
+ createdAt: now,
958
+ updatedAt: now
959
+ };
960
+ knowledgeItem.value = calculateKnowledgeValue(knowledgeItem);
961
+ return knowledgeItem;
962
+ }
963
+ function calculateEffectiveConfidence(knowledge) {
964
+ const m = knowledge.metrics;
965
+ const base = knowledge.confidence;
966
+ const evRatio = (m.support_count + 1) / (m.support_count + 3 * m.contradiction_count + 1);
967
+ const totalOutcomes = m.successful_outcomes + m.failed_outcomes;
968
+ let outRatio = 1;
969
+ let evWeight = 0.6;
970
+ let outWeight = 0.4;
971
+ if (totalOutcomes === 0) {
972
+ evWeight = 0.85;
973
+ outWeight = 0.15;
974
+ } else {
975
+ outRatio = (m.successful_outcomes + 1) / (totalOutcomes + 1);
976
+ }
977
+ const effective = base * (evWeight * evRatio + outWeight * outRatio);
978
+ return Math.round(Math.min(1, Math.max(0.05, effective)) * 100) / 100;
979
+ }
980
+ var knowledgeTokenCache = /* @__PURE__ */ new WeakMap();
981
+ function tokenize(text) {
982
+ return new Set(
983
+ text.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2)
984
+ );
985
+ }
986
+ function getCachedTokens(k) {
987
+ let tokens = knowledgeTokenCache.get(k);
988
+ if (!tokens) {
989
+ tokens = tokenize(k.statement);
990
+ knowledgeTokenCache.set(k, tokens);
991
+ }
992
+ return tokens;
993
+ }
994
+ function jaccardSimilarity(setA, setB) {
995
+ if (setA.size === 0 && setB.size === 0) return 1;
996
+ let intersection = 0;
997
+ for (const item of setA) {
998
+ if (setB.has(item)) intersection++;
999
+ }
1000
+ const union = setA.size + setB.size - intersection;
1001
+ return union === 0 ? 0 : intersection / union;
1002
+ }
1003
+ var POLAR_ANTONYMS = [
1004
+ ["enabled", "disabled"],
1005
+ ["enable", "disable"],
1006
+ ["active", "inactive"],
1007
+ ["true", "false"],
1008
+ ["on", "off"],
1009
+ ["yes", "no"],
1010
+ ["allowed", "disallowed"],
1011
+ ["allowed", "prohibited"],
1012
+ ["allowed", "forbidden"],
1013
+ ["permit", "deny"],
1014
+ ["allow", "forbid"],
1015
+ ["allow", "prohibit"],
1016
+ ["always", "never"],
1017
+ ["should", "should not"],
1018
+ ["must", "must not"],
1019
+ ["required", "optional"],
1020
+ ["required", "forbidden"],
1021
+ ["valid", "invalid"],
1022
+ ["safe", "unsafe"],
1023
+ ["sync", "async"],
1024
+ ["synchronous", "asynchronous"],
1025
+ ["public", "private"]
1026
+ ];
1027
+ var NEGATION_PATTERNS = [
1028
+ /\bnot\b/i,
1029
+ /\bnever\b/i,
1030
+ /\bno\b/i,
1031
+ /\bcannot\b/i,
1032
+ /\bwithout\b/i,
1033
+ /\bdisallow\b/i,
1034
+ /\bforbid\b/i,
1035
+ /\bprohibit\b/i,
1036
+ /\bavoid\b/i
1037
+ ];
1038
+ function detectPolarContradiction(stmtA, stmtB) {
1039
+ const lowerA = stmtA.toLowerCase();
1040
+ const lowerB = stmtB.toLowerCase();
1041
+ for (const [pos, neg] of POLAR_ANTONYMS) {
1042
+ const aHasPos = new RegExp(`\\b${pos}\\b`, "i").test(lowerA);
1043
+ const aHasNeg = new RegExp(`\\b${neg}\\b`, "i").test(lowerA);
1044
+ const bHasPos = new RegExp(`\\b${pos}\\b`, "i").test(lowerB);
1045
+ const bHasNeg = new RegExp(`\\b${neg}\\b`, "i").test(lowerB);
1046
+ if (aHasPos && bHasNeg || aHasNeg && bHasPos) {
1047
+ return {
1048
+ contradicted: true,
1049
+ reason: `Polar opposite terms detected: '${pos}' vs '${neg}'`
1050
+ };
1051
+ }
1052
+ }
1053
+ const aHasNegation = NEGATION_PATTERNS.some((p) => p.test(lowerA));
1054
+ const bHasNegation = NEGATION_PATTERNS.some((p) => p.test(lowerB));
1055
+ if (aHasNegation !== bHasNegation) {
1056
+ return {
1057
+ contradicted: true,
1058
+ reason: "Asymmetric negation detected (one statement asserts, the other negates)"
1059
+ };
1060
+ }
1061
+ return { contradicted: false };
1062
+ }
1063
+ function reconcileKnowledge(candidate, existingList) {
1064
+ const candidateId = candidate.id?.trim();
1065
+ const candidateStmt = candidate.statement.trim();
1066
+ const candidateTokens = tokenize(candidateStmt);
1067
+ const candidatePos = new Set((candidate.positive ?? []).map((p) => p.trim().toLowerCase()));
1068
+ if (candidateId) {
1069
+ const directMatch = existingList.find((k) => k.id === candidateId);
1070
+ if (directMatch) {
1071
+ if (directMatch.statement.trim().toLowerCase() === candidateStmt.toLowerCase()) {
1072
+ const hasNewEvidence = (candidate.evidence ?? []).some((e) => !directMatch.evidence.includes(e));
1073
+ if (!hasNewEvidence) {
1074
+ return {
1075
+ action: "IGNORE",
1076
+ targetId: directMatch.id,
1077
+ knowledge: directMatch,
1078
+ reason: "Identical statement and evidence already recorded"
1079
+ };
1080
+ }
1081
+ return {
1082
+ action: "SUPPORT",
1083
+ targetId: directMatch.id,
1084
+ knowledge: directMatch,
1085
+ reason: "Confirms existing knowledge with new evidence source"
1086
+ };
1087
+ }
1088
+ const opposingPositive = (candidate.negative ?? []).some((n) => directMatch.positive.includes(n));
1089
+ const opposingNegative = (candidate.positive ?? []).some((p) => directMatch.negative.includes(p));
1090
+ const polarCheck = detectPolarContradiction(candidateStmt, directMatch.statement);
1091
+ if (opposingPositive || opposingNegative || polarCheck.contradicted) {
1092
+ const distinctCues = Array.from(candidatePos).filter((c) => !directMatch.positive.includes(c));
1093
+ const mentionsEnvA = /(development|dev|local|staging|test)/i.test(candidateStmt);
1094
+ const mentionsEnvB = /(production|prod)/i.test(directMatch.statement);
1095
+ const mentionsEnvBDev = /(development|dev|local|staging|test)/i.test(directMatch.statement);
1096
+ const mentionsEnvAProd = /(production|prod)/i.test(candidateStmt);
1097
+ if (distinctCues.length > 0 || mentionsEnvA && mentionsEnvB || mentionsEnvBDev && mentionsEnvAProd) {
1098
+ return {
1099
+ action: "SPECIALIZE",
1100
+ targetId: directMatch.id,
1101
+ knowledge: directMatch,
1102
+ reason: `Conflicting claim applies under specialized context cues: [${distinctCues.join(", ")}] (${polarCheck.reason || "opposing polarity"})`
1103
+ };
1104
+ }
1105
+ return {
1106
+ action: "CONTRADICT",
1107
+ targetId: directMatch.id,
1108
+ knowledge: directMatch,
1109
+ reason: `Contradicts existing statement (${polarCheck.reason || "opposing polarity"}) for ID "${directMatch.id}"`
1110
+ };
1111
+ }
1112
+ return {
1113
+ action: "UPDATE",
1114
+ targetId: directMatch.id,
1115
+ knowledge: directMatch,
1116
+ reason: "Refines or updates existing knowledge statement"
1117
+ };
1118
+ }
1119
+ }
1120
+ let bestMatch = null;
1121
+ let bestSim = 0;
1122
+ let bestPolarCheck = { contradicted: false };
1123
+ for (const existing of existingList) {
1124
+ const existingTokens = getCachedTokens(existing);
1125
+ const sim = jaccardSimilarity(candidateTokens, existingTokens);
1126
+ const polarCheck = detectPolarContradiction(candidateStmt, existing.statement);
1127
+ if (polarCheck.contradicted) {
1128
+ let sharedCount = 0;
1129
+ for (const t of candidateTokens) {
1130
+ if (existingTokens.has(t)) sharedCount++;
1131
+ }
1132
+ const sharedCues = (candidate.positive ?? []).filter((p) => existing.positive.includes(p));
1133
+ if (sharedCount >= 2 || sharedCues.length >= 1 && sim >= 0.35 || sim >= 0.4) {
1134
+ bestMatch = existing;
1135
+ bestSim = Math.max(sim, 0.85);
1136
+ bestPolarCheck = polarCheck;
1137
+ break;
1138
+ }
1139
+ }
1140
+ if (sim > bestSim) {
1141
+ bestSim = sim;
1142
+ bestMatch = existing;
1143
+ bestPolarCheck = polarCheck;
1144
+ }
1145
+ }
1146
+ if (bestMatch && bestSim >= 0.7) {
1147
+ const polarCheck = bestPolarCheck.contradicted ? bestPolarCheck : detectPolarContradiction(candidateStmt, bestMatch.statement);
1148
+ const hasOpposingCues = (candidate.negative ?? []).some((n) => bestMatch.positive.includes(n)) || (candidate.positive ?? []).some((p) => bestMatch.negative.includes(p));
1149
+ if (polarCheck.contradicted || hasOpposingCues) {
1150
+ const distinctCues = Array.from(candidatePos).filter((c) => !bestMatch.positive.includes(c));
1151
+ const mentionsEnvA = /(development|dev|local|staging|test)/i.test(candidateStmt);
1152
+ const mentionsEnvB = /(production|prod)/i.test(bestMatch.statement);
1153
+ const mentionsEnvBDev = /(development|dev|local|staging|test)/i.test(bestMatch.statement);
1154
+ const mentionsEnvAProd = /(production|prod)/i.test(candidateStmt);
1155
+ if (distinctCues.length > 0 || mentionsEnvA && mentionsEnvB || mentionsEnvBDev && mentionsEnvAProd) {
1156
+ return {
1157
+ action: "SPECIALIZE",
1158
+ targetId: bestMatch.id,
1159
+ knowledge: bestMatch,
1160
+ reason: `High textual similarity (${Math.round(bestSim * 100)}%) with polar distinction across contexts (${polarCheck.reason || "opposing cues"})`,
1161
+ score: bestSim
1162
+ };
1163
+ }
1164
+ return {
1165
+ action: "CONTRADICT",
1166
+ targetId: bestMatch.id,
1167
+ knowledge: bestMatch,
1168
+ reason: `Contradicting statement (${polarCheck.reason || "opposing cues"}) despite high textual similarity (${Math.round(bestSim * 100)}%)`,
1169
+ score: bestSim
1170
+ };
1171
+ }
1172
+ if (bestSim >= 0.9) {
1173
+ return {
1174
+ action: "MERGE",
1175
+ targetId: bestMatch.id,
1176
+ knowledge: bestMatch,
1177
+ reason: `Nearly identical statement (${Math.round(bestSim * 100)}% match) with compatible polarity; merging cues and evidence`,
1178
+ score: bestSim
1179
+ };
1180
+ }
1181
+ return {
1182
+ action: "SUPPORT",
1183
+ targetId: bestMatch.id,
1184
+ knowledge: bestMatch,
1185
+ reason: `Supports existing knowledge (${Math.round(bestSim * 100)}% similarity) with compatible polarity`,
1186
+ score: bestSim
1187
+ };
1188
+ }
1189
+ return {
1190
+ action: "NEW",
1191
+ reason: "No conflicting or matching knowledge found; creating new knowledge entry"
1192
+ };
1193
+ }
1194
+ function applyReconciliation(reconciliation, candidate, existingList, storage) {
1195
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1196
+ switch (reconciliation.action) {
1197
+ case "SUPPORT": {
1198
+ const target = existingList.find((k) => k.id === reconciliation.targetId);
1199
+ target.metrics.support_count++;
1200
+ target.metrics.last_used = now;
1201
+ storage?.appendMetricEvent({
1202
+ type: "knowledge_strengthened",
1203
+ knowledgeId: target.id,
1204
+ timestamp: Date.now()
1205
+ });
1206
+ if (candidate.evidence) {
1207
+ for (const e of candidate.evidence) {
1208
+ if (!target.evidence.includes(e)) {
1209
+ target.evidence.push(e);
1210
+ }
1211
+ }
1212
+ }
1213
+ if (candidate.positive) {
1214
+ for (const p of candidate.positive) {
1215
+ const lp = p.trim().toLowerCase();
1216
+ if (!target.positive.includes(lp)) target.positive.push(lp);
1217
+ }
1218
+ }
1219
+ if (!target.history) {
1220
+ target.history = [
1221
+ {
1222
+ timestamp: target.createdAt || now,
1223
+ stage: target.stage,
1224
+ status: target.status,
1225
+ reason: "Initial knowledge recorded",
1226
+ source: target.evidence?.[0]
1227
+ }
1228
+ ];
1229
+ }
1230
+ const promotion = evaluatePromotion(target);
1231
+ if (promotion.promoted && promotion.newStage) {
1232
+ target.stage = promotion.newStage;
1233
+ if (target.stage === "active" && target.status === "candidate") {
1234
+ target.status = "active";
1235
+ }
1236
+ target.history.push({
1237
+ timestamp: now,
1238
+ stage: target.stage,
1239
+ status: target.status,
1240
+ reason: promotion.reason || `Promoted to ${target.stage}`,
1241
+ source: candidate.evidence?.[0]
1242
+ });
1243
+ } else {
1244
+ target.history.push({
1245
+ timestamp: now,
1246
+ stage: target.stage,
1247
+ status: target.status,
1248
+ reason: reconciliation.reason || "Confirmed with supporting evidence",
1249
+ source: candidate.evidence?.[0]
1250
+ });
1251
+ }
1252
+ target.confidence = calculateEffectiveConfidence(target);
1253
+ target.value = calculateKnowledgeValue(target);
1254
+ target.updatedAt = now;
1255
+ return { updatedKnowledge: target, action: "SUPPORT", createdId: target.id };
1256
+ }
1257
+ case "MERGE": {
1258
+ const target = existingList.find((k) => k.id === reconciliation.targetId);
1259
+ target.metrics.support_count++;
1260
+ target.metrics.last_used = now;
1261
+ storage?.appendMetricEvent({
1262
+ type: "knowledge_merged",
1263
+ knowledgeId: target.id,
1264
+ targetId: reconciliation.targetId,
1265
+ timestamp: Date.now()
1266
+ });
1267
+ if (candidate.evidence) {
1268
+ for (const e of candidate.evidence) {
1269
+ if (!target.evidence.includes(e)) target.evidence.push(e);
1270
+ }
1271
+ }
1272
+ if (candidate.positive) {
1273
+ for (const p of candidate.positive) {
1274
+ const lp = p.trim().toLowerCase();
1275
+ if (!target.positive.includes(lp)) target.positive.push(lp);
1276
+ }
1277
+ }
1278
+ if (candidate.negative) {
1279
+ for (const n of candidate.negative) {
1280
+ const ln = n.trim().toLowerCase();
1281
+ if (!target.negative.includes(ln)) target.negative.push(ln);
1282
+ }
1283
+ }
1284
+ if (!target.history) {
1285
+ target.history = [
1286
+ {
1287
+ timestamp: target.createdAt || now,
1288
+ stage: target.stage,
1289
+ status: target.status,
1290
+ reason: "Initial knowledge recorded",
1291
+ source: target.evidence?.[0]
1292
+ }
1293
+ ];
1294
+ }
1295
+ const promotion = evaluatePromotion(target);
1296
+ if (promotion.promoted && promotion.newStage) {
1297
+ target.stage = promotion.newStage;
1298
+ if (target.stage === "active" && target.status === "candidate") {
1299
+ target.status = "active";
1300
+ }
1301
+ target.history.push({
1302
+ timestamp: now,
1303
+ stage: target.stage,
1304
+ status: target.status,
1305
+ reason: promotion.reason || `Promoted to ${target.stage}`,
1306
+ source: candidate.evidence?.[0]
1307
+ });
1308
+ } else {
1309
+ target.history.push({
1310
+ timestamp: now,
1311
+ stage: target.stage,
1312
+ status: target.status,
1313
+ reason: reconciliation.reason || "Merged with compatible knowledge candidate",
1314
+ source: candidate.evidence?.[0]
1315
+ });
1316
+ }
1317
+ target.confidence = calculateEffectiveConfidence(target);
1318
+ target.value = calculateKnowledgeValue(target);
1319
+ target.updatedAt = now;
1320
+ return { updatedKnowledge: target, action: "MERGE", createdId: target.id };
1321
+ }
1322
+ case "UPDATE": {
1323
+ const target = existingList.find((k) => k.id === reconciliation.targetId);
1324
+ target.statement = candidate.statement;
1325
+ if (candidate.type) target.type = candidate.type;
1326
+ if (candidate.positive) {
1327
+ target.positive = Array.from(/* @__PURE__ */ new Set([...target.positive, ...candidate.positive.map((p) => p.toLowerCase())]));
1328
+ }
1329
+ if (candidate.negative) {
1330
+ target.negative = Array.from(/* @__PURE__ */ new Set([...target.negative, ...candidate.negative.map((n) => n.toLowerCase())]));
1331
+ }
1332
+ if (candidate.evidence) {
1333
+ target.evidence = Array.from(/* @__PURE__ */ new Set([...target.evidence, ...candidate.evidence]));
1334
+ }
1335
+ target.temporal = {
1336
+ revision: (target.temporal?.revision ?? 1) + 1,
1337
+ valid_from: { timestamp: now }
1338
+ };
1339
+ if (!target.history) {
1340
+ target.history = [
1341
+ {
1342
+ timestamp: target.createdAt || now,
1343
+ stage: target.stage,
1344
+ status: target.status,
1345
+ reason: "Initial knowledge recorded",
1346
+ source: target.evidence?.[0]
1347
+ }
1348
+ ];
1349
+ }
1350
+ target.history.push({
1351
+ timestamp: now,
1352
+ stage: target.stage,
1353
+ status: target.status,
1354
+ reason: reconciliation.reason || "Updated statement and context",
1355
+ source: candidate.evidence?.[0]
1356
+ });
1357
+ target.updatedAt = now;
1358
+ target.confidence = calculateEffectiveConfidence(target);
1359
+ target.value = calculateKnowledgeValue(target);
1360
+ storage?.appendMetricEvent({
1361
+ type: "knowledge_strengthened",
1362
+ knowledgeId: target.id,
1363
+ timestamp: Date.now()
1364
+ });
1365
+ return { updatedKnowledge: target, action: "UPDATE", createdId: target.id };
1366
+ }
1367
+ case "CONTRADICT": {
1368
+ const target = existingList.find((k) => k.id === reconciliation.targetId);
1369
+ target.metrics.contradiction_count++;
1370
+ if (candidate.evidence) {
1371
+ for (const e of candidate.evidence) {
1372
+ if (!target.evidence.includes(e)) target.evidence.push(e);
1373
+ }
1374
+ }
1375
+ if (target.metrics.contradiction_count > target.metrics.support_count) {
1376
+ target.status = "contradicted";
1377
+ }
1378
+ if (!target.history) {
1379
+ target.history = [
1380
+ {
1381
+ timestamp: target.createdAt || now,
1382
+ stage: target.stage,
1383
+ status: target.status,
1384
+ reason: "Initial knowledge recorded",
1385
+ source: target.evidence?.[0]
1386
+ }
1387
+ ];
1388
+ }
1389
+ target.history.push({
1390
+ timestamp: now,
1391
+ stage: target.stage,
1392
+ status: target.status,
1393
+ reason: reconciliation.reason || "Contradicted by opposing claim",
1394
+ source: candidate.evidence?.[0]
1395
+ });
1396
+ target.confidence = calculateEffectiveConfidence(target);
1397
+ target.value = calculateKnowledgeValue(target);
1398
+ target.updatedAt = now;
1399
+ storage?.appendMetricEvent({
1400
+ type: "contradiction_detected",
1401
+ knowledgeId: target.id,
1402
+ timestamp: Date.now()
1403
+ });
1404
+ return { updatedKnowledge: target, action: "CONTRADICT", createdId: target.id };
1405
+ }
1406
+ case "SPECIALIZE": {
1407
+ const parent = existingList.find((k) => k.id === reconciliation.targetId);
1408
+ const subContext = candidate.positive?.[0] || "specialized";
1409
+ const newId = candidate.id && candidate.id !== parent?.id ? candidate.id : `${parent ? parent.id : "knowledge"}.${subContext}`;
1410
+ const created = createKnowledge({
1411
+ id: newId,
1412
+ type: candidate.type ?? parent?.type ?? "lesson",
1413
+ statement: candidate.statement,
1414
+ positive: candidate.positive ?? [],
1415
+ negative: candidate.negative ?? [],
1416
+ relations: parent ? [parent.id, ...candidate.relations ?? []] : candidate.relations ?? [],
1417
+ confidence: candidate.confidence ?? 0.85,
1418
+ evidence: candidate.evidence ?? [],
1419
+ scope: candidate.scope ?? parent?.scope ?? "project",
1420
+ tags: candidate.tags ?? parent?.tags ?? []
1421
+ });
1422
+ if (parent) {
1423
+ if (!parent.relations.includes(newId)) {
1424
+ parent.relations.push(newId);
1425
+ }
1426
+ if (!parent.history) {
1427
+ parent.history = [
1428
+ {
1429
+ timestamp: parent.createdAt || now,
1430
+ stage: parent.stage,
1431
+ status: parent.status,
1432
+ reason: "Initial knowledge recorded"
1433
+ }
1434
+ ];
1435
+ }
1436
+ parent.history.push({
1437
+ timestamp: now,
1438
+ stage: parent.stage,
1439
+ status: parent.status,
1440
+ reason: `Specialized into contextual branch ${newId}`
1441
+ });
1442
+ }
1443
+ storage?.appendMetricEvent({
1444
+ type: "knowledge_specialized",
1445
+ knowledgeId: created.id,
1446
+ timestamp: Date.now()
1447
+ });
1448
+ return { updatedKnowledge: created, action: "SPECIALIZE", createdId: created.id };
1449
+ }
1450
+ case "IGNORE": {
1451
+ const target = existingList.find((k) => k.id === reconciliation.targetId);
1452
+ storage?.appendMetricEvent({
1453
+ type: "duplicate_ignored",
1454
+ timestamp: Date.now()
1455
+ });
1456
+ return { updatedKnowledge: target, action: "IGNORE", createdId: target.id };
1457
+ }
1458
+ case "NEW":
1459
+ default: {
1460
+ const newId = candidate.id || `kn_${hashString(candidate.statement)}`;
1461
+ const created = createKnowledge({
1462
+ id: newId,
1463
+ type: candidate.type,
1464
+ statement: candidate.statement,
1465
+ positive: candidate.positive,
1466
+ negative: candidate.negative,
1467
+ relations: candidate.relations,
1468
+ confidence: candidate.confidence,
1469
+ evidence: candidate.evidence,
1470
+ scope: candidate.scope,
1471
+ tags: candidate.tags
1472
+ });
1473
+ storage?.appendMetricEvent({
1474
+ type: "knowledge_created",
1475
+ knowledgeId: created.id,
1476
+ timestamp: Date.now()
1477
+ });
1478
+ return { updatedKnowledge: created, action: "NEW", createdId: created.id };
1479
+ }
1480
+ }
1481
+ }
1482
+
1483
+ // src/storage/portable.ts
1484
+ function exportKnowledgeToYaml(items, options = {}) {
1485
+ let filtered = items;
1486
+ if (options.scope) {
1487
+ const s = options.scope.toLowerCase();
1488
+ filtered = items.filter((k) => k.scope.toLowerCase() === s);
1489
+ }
1490
+ const payload = {
1491
+ schemaVersion: 1,
1492
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1493
+ count: filtered.length,
1494
+ knowledge: filtered.map((k) => ({
1495
+ id: k.id,
1496
+ type: k.type,
1497
+ status: k.status,
1498
+ statement: k.statement,
1499
+ positive: k.positive,
1500
+ negative: k.negative,
1501
+ relations: k.relations.length > 0 ? k.relations : void 0,
1502
+ confidence: k.confidence,
1503
+ evidence: k.evidence.length > 0 ? k.evidence : void 0,
1504
+ scope: k.scope,
1505
+ tags: k.tags && k.tags.length > 0 ? k.tags : void 0,
1506
+ temporal: k.temporal,
1507
+ metrics: {
1508
+ reuse_count: k.metrics.reuse_count,
1509
+ support_count: k.metrics.support_count,
1510
+ contradiction_count: k.metrics.contradiction_count
1511
+ }
1512
+ }))
1513
+ };
1514
+ return YAML2.stringify(payload, { indent: 2, lineWidth: 100 });
1515
+ }
1516
+ async function importKnowledgeFromYaml(yamlContent, storage) {
1517
+ const parsed = YAML2.parse(yamlContent);
1518
+ if (!parsed || typeof parsed !== "object") {
1519
+ throw new Error("Invalid YAML: root must be an object");
1520
+ }
1521
+ let candidates = [];
1522
+ if (Array.isArray(parsed)) {
1523
+ candidates = parsed;
1524
+ } else if (Array.isArray(parsed.knowledge)) {
1525
+ candidates = parsed.knowledge;
1526
+ } else if (parsed.id && parsed.statement) {
1527
+ candidates = [parsed];
1528
+ } else {
1529
+ throw new Error("Invalid knowledge YAML format: no knowledge entries found");
1530
+ }
1531
+ const existing = await storage.getAllKnowledge();
1532
+ const results = [];
1533
+ const created = [];
1534
+ const updated = [];
1535
+ for (const raw of candidates) {
1536
+ if (!raw.statement || typeof raw.statement !== "string") {
1537
+ continue;
1538
+ }
1539
+ const rec = reconcileKnowledge(raw, existing);
1540
+ results.push(rec);
1541
+ const { updatedKnowledge, action } = applyReconciliation(rec, raw, existing, storage);
1542
+ await storage.saveKnowledge(updatedKnowledge);
1543
+ if (action === "NEW" || action === "SPECIALIZE") {
1544
+ created.push(updatedKnowledge);
1545
+ existing.push(updatedKnowledge);
1546
+ } else if (action !== "IGNORE") {
1547
+ updated.push(updatedKnowledge);
1548
+ }
1549
+ }
1550
+ return {
1551
+ totalParsed: candidates.length,
1552
+ results,
1553
+ created,
1554
+ updated
1555
+ };
1556
+ }
1557
+ async function dumpToYaml(storage) {
1558
+ const config = await storage.getConfig();
1559
+ const knowledge = await storage.getAllKnowledge();
1560
+ const unknowns = await storage.getAllUnknowns();
1561
+ const evidence = await storage.getAllEvidence();
1562
+ const observations = await storage.getAllObservations();
1563
+ const relations = await storage.getRelations();
1564
+ const metrics = await storage.getMetrics();
1565
+ const dump = {
1566
+ schemaVersion: 1,
1567
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1568
+ config,
1569
+ knowledge,
1570
+ unknowns,
1571
+ evidence,
1572
+ observations,
1573
+ relations,
1574
+ metrics
1575
+ };
1576
+ return YAML2.stringify(dump, { indent: 2, lineWidth: 120 });
1577
+ }
1578
+ async function restoreFromYaml(yamlContent, storage) {
1579
+ const parsed = YAML2.parse(yamlContent);
1580
+ if (!parsed || parsed.schemaVersion !== 1) {
1581
+ throw new Error(`Unsupported snapshot schema version: ${parsed?.schemaVersion}`);
1582
+ }
1583
+ if (parsed.config) {
1584
+ await storage.saveConfig(parsed.config);
1585
+ }
1586
+ let kCount = 0;
1587
+ if (Array.isArray(parsed.knowledge)) {
1588
+ for (const k of parsed.knowledge) {
1589
+ await storage.saveKnowledge(k);
1590
+ kCount++;
1591
+ }
1592
+ }
1593
+ let uCount = 0;
1594
+ if (Array.isArray(parsed.unknowns)) {
1595
+ for (const u of parsed.unknowns) {
1596
+ await storage.saveUnknown(u);
1597
+ uCount++;
1598
+ }
1599
+ }
1600
+ if (Array.isArray(parsed.evidence)) {
1601
+ for (const e of parsed.evidence) {
1602
+ await storage.saveEvidence(e);
1603
+ }
1604
+ }
1605
+ if (Array.isArray(parsed.observations)) {
1606
+ for (const o of parsed.observations) {
1607
+ await storage.saveObservation(o);
1608
+ }
1609
+ }
1610
+ if (Array.isArray(parsed.relations)) {
1611
+ await storage.saveRelations(parsed.relations);
1612
+ }
1613
+ if (parsed.metrics) {
1614
+ await storage.saveMetrics(parsed.metrics);
1615
+ }
1616
+ return {
1617
+ restoredKnowledgeCount: kCount,
1618
+ restoredUnknownsCount: uCount
1619
+ };
1620
+ }
1621
+
1622
+ // src/storage/compaction.ts
1623
+ function tokenize2(text) {
1624
+ return new Set(
1625
+ text.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2)
1626
+ );
1627
+ }
1628
+ function jaccard(a, b) {
1629
+ if (a.size === 0 && b.size === 0) return 1;
1630
+ let inter = 0;
1631
+ for (const item of a) {
1632
+ if (b.has(item)) inter++;
1633
+ }
1634
+ const union = a.size + b.size - inter;
1635
+ return union === 0 ? 0 : inter / union;
1636
+ }
1637
+ async function compact(storage, options = {}) {
1638
+ const dryRun = options.dryRun ?? false;
1639
+ const report = {
1640
+ dryRun,
1641
+ mergedDuplicatesCount: 0,
1642
+ markedStaleCount: 0,
1643
+ markedContradictedCount: 0,
1644
+ prunedObservationsCount: 0,
1645
+ details: []
1646
+ };
1647
+ const allKnowledge = await storage.getAllKnowledge();
1648
+ const allEvidence = await storage.getAllEvidence();
1649
+ const allObservations = await storage.getAllObservations();
1650
+ const activeReferencedEvidence = /* @__PURE__ */ new Set();
1651
+ for (const k of allKnowledge) {
1652
+ for (const e of k.evidence) {
1653
+ activeReferencedEvidence.add(e);
1654
+ }
1655
+ }
1656
+ const activeReferencedObservations = /* @__PURE__ */ new Set();
1657
+ for (const ev of allEvidence) {
1658
+ for (const oId of ev.observationIds) {
1659
+ activeReferencedObservations.add(oId);
1660
+ }
1661
+ }
1662
+ const processed = /* @__PURE__ */ new Set();
1663
+ const toDelete = /* @__PURE__ */ new Set();
1664
+ for (let i = 0; i < allKnowledge.length; i++) {
1665
+ const primary = allKnowledge[i];
1666
+ if (processed.has(primary.id) || toDelete.has(primary.id)) continue;
1667
+ processed.add(primary.id);
1668
+ const primaryTokens = tokenize2(primary.statement);
1669
+ for (let j = i + 1; j < allKnowledge.length; j++) {
1670
+ const secondary = allKnowledge[j];
1671
+ if (processed.has(secondary.id) || toDelete.has(secondary.id)) continue;
1672
+ if (primary.scope !== secondary.scope) continue;
1673
+ const polarCheck = detectPolarContradiction(primary.statement, secondary.statement);
1674
+ if (polarCheck.contradicted) {
1675
+ primary.metrics.contradiction_count++;
1676
+ secondary.metrics.contradiction_count++;
1677
+ report.markedContradictedCount++;
1678
+ report.details.push(
1679
+ `Detected contradiction: "${secondary.id}" vs "${primary.id}" (${polarCheck.reason}). Skipping merge.`
1680
+ );
1681
+ continue;
1682
+ }
1683
+ const secondaryTokens = tokenize2(secondary.statement);
1684
+ const sim = jaccard(primaryTokens, secondaryTokens);
1685
+ if (sim >= 0.85) {
1686
+ report.mergedDuplicatesCount++;
1687
+ report.details.push(`Merged duplicate: "${secondary.id}" into "${primary.id}" (${Math.round(sim * 100)}% match)`);
1688
+ primary.metrics.support_count += secondary.metrics.support_count;
1689
+ primary.metrics.reuse_count += secondary.metrics.reuse_count;
1690
+ primary.metrics.successful_outcomes += secondary.metrics.successful_outcomes;
1691
+ primary.metrics.failed_outcomes += secondary.metrics.failed_outcomes;
1692
+ for (const e of secondary.evidence) {
1693
+ if (!primary.evidence.includes(e)) primary.evidence.push(e);
1694
+ }
1695
+ for (const p of secondary.positive) {
1696
+ if (!primary.positive.includes(p)) primary.positive.push(p);
1697
+ }
1698
+ for (const n of secondary.negative) {
1699
+ if (!primary.negative.includes(n)) primary.negative.push(n);
1700
+ }
1701
+ primary.confidence = calculateEffectiveConfidence(primary);
1702
+ toDelete.add(secondary.id);
1703
+ }
1704
+ }
1705
+ if (primary.metrics.contradiction_count > 2 * primary.metrics.support_count && primary.status === "active") {
1706
+ primary.status = "contradicted";
1707
+ report.markedContradictedCount++;
1708
+ report.details.push(`Marked "${primary.id}" as contradicted (${primary.metrics.contradiction_count} vs ${primary.metrics.support_count})`);
1709
+ }
1710
+ if (!dryRun) {
1711
+ await storage.saveKnowledge(primary);
1712
+ }
1713
+ }
1714
+ if (!dryRun) {
1715
+ for (const delId of toDelete) {
1716
+ await storage.deleteKnowledge(delId);
1717
+ }
1718
+ }
1719
+ const now = Date.now();
1720
+ const FOURTEEN_DAYS_MS = 14 * 24 * 60 * 60 * 1e3;
1721
+ for (const obs of allObservations) {
1722
+ const isReferenced = activeReferencedObservations.has(obs.id) || activeReferencedEvidence.has(obs.id) || activeReferencedEvidence.has(obs.source);
1723
+ if (!isReferenced) {
1724
+ const age = now - new Date(obs.recordedAt).getTime();
1725
+ if (age > FOURTEEN_DAYS_MS) {
1726
+ report.prunedObservationsCount++;
1727
+ report.details.push(`Pruned unreferenced stale observation: "${obs.id}"`);
1728
+ if (!dryRun) {
1729
+ await storage.deleteObservation(obs.id);
1730
+ }
1731
+ }
1732
+ }
1733
+ }
1734
+ return report;
1735
+ }
1736
+ export {
1737
+ Storage as CanonicalStorage,
1738
+ Storage,
1739
+ compact,
1740
+ desanitizeFileName,
1741
+ dumpToYaml,
1742
+ exportKnowledgeToYaml,
1743
+ formatKnowledgeDiff,
1744
+ importKnowledgeFromYaml,
1745
+ restoreFromYaml,
1746
+ sanitizeFileName
1747
+ };
1748
+ //# sourceMappingURL=index.js.map