@plur-ai/core 0.7.7 → 0.8.2

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/dist/index.js CHANGED
@@ -1,19 +1,42 @@
1
1
  import {
2
- atomicWrite,
2
+ computeIdf,
3
3
  embeddingSearch,
4
4
  engramSearchText,
5
5
  ftsScore,
6
6
  ftsTokenize,
7
+ searchEngrams
8
+ } from "./chunk-UETCDULF.js";
9
+ import {
10
+ EngramSchemaPassthrough,
11
+ appendHistory,
12
+ buildBatchDedupPrompt,
13
+ buildDedupPrompt,
14
+ computeContentHash,
15
+ generateEngramId,
16
+ generateEventId,
17
+ listHistoryMonths,
18
+ loadAllPacks,
19
+ loadEngrams,
20
+ loadPack,
21
+ logger,
22
+ normalizeStatement,
23
+ parseDedupResponse,
24
+ readHistory,
25
+ readHistoryForEngram,
26
+ saveEngrams,
27
+ storePrefix
28
+ } from "./chunk-GRDNBUIJ.js";
29
+ import {
30
+ atomicWrite,
7
31
  getSyncStatus,
8
- searchEngrams,
9
32
  sync,
10
33
  withLock
11
- } from "./chunk-KMVQYBNP.js";
34
+ } from "./chunk-MY4XVDCE.js";
12
35
  import "./chunk-2ZDO52B4.js";
13
36
 
14
37
  // src/index.ts
15
- import * as fs3 from "fs";
16
- import yaml5 from "js-yaml";
38
+ import * as fs4 from "fs";
39
+ import yaml6 from "js-yaml";
17
40
 
18
41
  // src/storage.ts
19
42
  import { existsSync, mkdirSync } from "fs";
@@ -37,284 +60,8 @@ function detectPlurStorage(explicitPath) {
37
60
  }
38
61
 
39
62
  // src/storage-indexed.ts
40
- import { existsSync as existsSync3 } from "fs";
63
+ import { existsSync as existsSync2 } from "fs";
41
64
  import { createRequire } from "module";
42
-
43
- // src/engrams.ts
44
- import * as fs from "fs";
45
- import * as yaml from "js-yaml";
46
-
47
- // src/schemas/engram.ts
48
- import { z } from "zod";
49
- var ActivationSchema = z.object({
50
- retrieval_strength: z.number().min(0).max(1),
51
- storage_strength: z.number().min(0).max(1),
52
- frequency: z.number().int().min(0),
53
- last_accessed: z.string()
54
- });
55
- var KnowledgeTypeSchema = z.object({
56
- memory_class: z.enum(["semantic", "episodic", "procedural", "metacognitive"]),
57
- cognitive_level: z.enum(["remember", "understand", "apply", "analyze", "evaluate", "create"])
58
- });
59
- var KnowledgeAnchorSchema = z.object({
60
- path: z.string(),
61
- relevance: z.enum(["primary", "supporting", "example"]).default("supporting"),
62
- snippet: z.string().max(200).optional(),
63
- snippet_extracted_at: z.string().optional()
64
- });
65
- var AssociationSchema = z.object({
66
- target_type: z.enum(["engram", "document"]),
67
- target: z.string(),
68
- strength: z.number().min(0).max(0.95),
69
- type: z.enum(["semantic", "temporal", "causal", "co_accessed"]),
70
- updated_at: z.string().optional()
71
- });
72
- var DualCodingSchema = z.object({
73
- example: z.string().optional(),
74
- analogy: z.string().optional()
75
- }).refine(
76
- (d) => d.example || d.analogy,
77
- "At least one of example or analogy must be provided"
78
- );
79
- var RelationsSchema = z.object({
80
- broader: z.array(z.string()).default([]),
81
- narrower: z.array(z.string()).default([]),
82
- related: z.array(z.string()).default([]),
83
- conflicts: z.array(z.string()).default([])
84
- });
85
- var ProvenanceSchema = z.object({
86
- origin: z.string(),
87
- chain: z.array(z.string()).default([]),
88
- signature: z.string().nullable().default(null),
89
- license: z.string().default("cc-by-sa-4.0")
90
- });
91
- var FeedbackSignalsSchema = z.object({
92
- positive: z.number().int().default(0),
93
- negative: z.number().int().default(0),
94
- neutral: z.number().int().default(0)
95
- });
96
- var EntityRefSchema = z.object({
97
- name: z.string(),
98
- type: z.enum([
99
- "person",
100
- "organization",
101
- "technology",
102
- "concept",
103
- "project",
104
- "tool",
105
- "place",
106
- "event",
107
- "standard",
108
- "other"
109
- ]),
110
- uri: z.string().url().optional()
111
- });
112
- var TemporalSchema = z.object({
113
- learned_at: z.string(),
114
- valid_from: z.string().optional(),
115
- valid_until: z.string().optional(),
116
- ingested_at: z.string().optional()
117
- });
118
- var UsageStatsSchema = z.object({
119
- injections: z.number().int().default(0),
120
- hits: z.number().int().default(0),
121
- misses: z.number().int().default(0),
122
- last_hit_at: z.string().optional()
123
- });
124
- var EpisodicFieldsSchema = z.object({
125
- emotional_weight: z.number().int().min(1).max(10).default(5),
126
- confidence: z.number().int().min(1).max(10).default(5),
127
- trigger_context: z.string().optional(),
128
- journal_ref: z.string().optional()
129
- });
130
- var ExchangeMetadataSchema = z.object({
131
- fitness_score: z.number().min(0).max(1).optional(),
132
- environmental_diversity: z.number().int().default(0),
133
- adoption_count: z.number().int().default(0),
134
- contradiction_rate: z.number().min(0).max(1).default(0)
135
- });
136
- var EngramSchema = z.object({
137
- // Identity
138
- id: z.string().regex(/^(ENG|ABS|META)-[A-Za-z0-9-]+$/),
139
- version: z.number().int().min(1).default(2),
140
- status: z.enum(["active", "dormant", "retired", "candidate"]),
141
- consolidated: z.boolean().default(false),
142
- type: z.enum(["behavioral", "terminological", "procedural", "architectural"]),
143
- scope: z.string(),
144
- visibility: z.enum(["private", "public", "template"]).default("private"),
145
- // Content
146
- statement: z.string().min(1),
147
- rationale: z.string().optional(),
148
- contraindications: z.array(z.string()).optional(),
149
- // Lineage
150
- source: z.string().optional(),
151
- source_patterns: z.array(z.string()).optional(),
152
- derivation_count: z.number().int().min(0).default(1),
153
- pack: z.string().nullable().default(null),
154
- abstract: z.string().nullable().default(null),
155
- derived_from: z.string().nullable().default(null),
156
- // Classification
157
- knowledge_type: KnowledgeTypeSchema.optional(),
158
- domain: z.string().optional(),
159
- tags: z.array(z.string()).default([]),
160
- // Activation (ACT-R model)
161
- activation: ActivationSchema.default({
162
- retrieval_strength: 0.7,
163
- storage_strength: 1,
164
- frequency: 0,
165
- last_accessed: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
166
- }),
167
- // Relations & grounding
168
- relations: RelationsSchema.optional(),
169
- associations: z.array(AssociationSchema).default([]),
170
- knowledge_anchors: z.array(KnowledgeAnchorSchema).default([]),
171
- dual_coding: DualCodingSchema.optional(),
172
- // Provenance
173
- provenance: ProvenanceSchema.optional(),
174
- // Feedback
175
- feedback_signals: FeedbackSignalsSchema.default({ positive: 0, negative: 0, neutral: 0 }),
176
- // === NEW OPTIONAL FIELDS (v2.1) ===
177
- /** Typed entity references extracted from statement. Enables graph queries. */
178
- entities: z.array(EntityRefSchema).optional(),
179
- /** Temporal validity window. When is this knowledge true? */
180
- temporal: TemporalSchema.optional(),
181
- /** Automatic usage tracking. Injections, hits, misses. */
182
- usage: UsageStatsSchema.optional(),
183
- /** Episodic context: emotional weight, confidence, trigger. */
184
- episodic: EpisodicFieldsSchema.optional(),
185
- /** Exchange marketplace metadata: fitness, adoption, diversity. */
186
- exchange: ExchangeMetadataSchema.optional(),
187
- /** Extensible key-value data for domain-specific fields. */
188
- structured_data: z.record(z.string(), z.unknown()).optional(),
189
- /** Polarity classification: 'do' for directives, 'dont' for prohibitions, null for unclassified. */
190
- polarity: z.enum(["do", "dont"]).nullable().default(null)
191
- });
192
-
193
- // src/schemas/pack.ts
194
- import { z as z2 } from "zod";
195
- var PackManifestSchema = z2.object({
196
- name: z2.string(),
197
- version: z2.string(),
198
- description: z2.string().optional(),
199
- creator: z2.string().optional(),
200
- license: z2.string().default("cc-by-sa-4.0"),
201
- tags: z2.array(z2.string()).default([]),
202
- metadata: z2.object({
203
- id: z2.string().optional(),
204
- injection_policy: z2.enum(["on_match", "on_request", "always"]).default("on_match"),
205
- match_terms: z2.array(z2.string()).default([]),
206
- domain: z2.string().optional(),
207
- engram_count: z2.number().optional()
208
- }).optional(),
209
- "x-datacore": z2.object({
210
- id: z2.string(),
211
- injection_policy: z2.enum(["on_match", "on_request"]),
212
- match_terms: z2.array(z2.string()).default([]),
213
- domain: z2.string().optional(),
214
- engram_count: z2.number().int().min(0)
215
- }).optional()
216
- });
217
-
218
- // src/logger.ts
219
- var level = process.env.PLUR_LOG_LEVEL || "warning";
220
- var levels = { debug: 0, info: 1, warning: 2, error: 3 };
221
- var threshold = levels[level] ?? 2;
222
- var logger = {
223
- debug: (...args) => {
224
- if (threshold <= 0) console.error("[plur:debug]", ...args);
225
- },
226
- info: (...args) => {
227
- if (threshold <= 1) console.error("[plur:info]", ...args);
228
- },
229
- warning: (...args) => {
230
- if (threshold <= 2) console.error("[plur:warning]", ...args);
231
- },
232
- error: (...args) => {
233
- if (threshold <= 3) console.error("[plur:error]", ...args);
234
- }
235
- };
236
-
237
- // src/engrams.ts
238
- function loadEngrams(filePath) {
239
- if (!fs.existsSync(filePath)) return [];
240
- try {
241
- const raw = yaml.load(fs.readFileSync(filePath, "utf8"));
242
- if (!raw?.engrams || !Array.isArray(raw.engrams)) return [];
243
- const valid = [];
244
- let skipped = 0;
245
- for (const entry of raw.engrams) {
246
- const result = EngramSchema.safeParse(entry);
247
- if (result.success) valid.push(result.data);
248
- else skipped++;
249
- }
250
- if (skipped > 0) logger.warning(`Skipped ${skipped} invalid engram(s) in ${filePath}`);
251
- return valid;
252
- } catch (err) {
253
- logger.error(`Failed to parse engrams file ${filePath}: ${err}`);
254
- return [];
255
- }
256
- }
257
- function saveEngrams(filePath, engrams) {
258
- const content = yaml.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
259
- atomicWrite(filePath, content);
260
- }
261
- function parseSkillMdFrontmatter(filePath) {
262
- const content = fs.readFileSync(filePath, "utf8");
263
- const match = content.match(/^---\n([\s\S]*?)\n---/);
264
- if (!match) throw new Error(`No frontmatter found in ${filePath}`);
265
- return yaml.load(match[1]);
266
- }
267
- function loadPack(packDir) {
268
- const skillMdPath = `${packDir}/SKILL.md`;
269
- const manifestYamlPath = `${packDir}/manifest.yaml`;
270
- const engramsPath = `${packDir}/engrams.yaml`;
271
- let rawManifest;
272
- if (fs.existsSync(skillMdPath)) {
273
- rawManifest = parseSkillMdFrontmatter(skillMdPath);
274
- } else if (fs.existsSync(manifestYamlPath)) {
275
- rawManifest = yaml.load(fs.readFileSync(manifestYamlPath, "utf8"));
276
- } else {
277
- throw new Error(`No SKILL.md or manifest.yaml found in ${packDir}`);
278
- }
279
- const manifest = PackManifestSchema.parse(rawManifest);
280
- const engrams = loadEngrams(engramsPath);
281
- return { manifest, engrams };
282
- }
283
- function loadAllPacks(packsDir) {
284
- if (!fs.existsSync(packsDir)) return [];
285
- const packs = [];
286
- for (const entry of fs.readdirSync(packsDir)) {
287
- const packDir = `${packsDir}/${entry}`;
288
- if (!fs.statSync(packDir).isDirectory()) continue;
289
- if (!fs.existsSync(`${packDir}/SKILL.md`) && !fs.existsSync(`${packDir}/manifest.yaml`)) continue;
290
- try {
291
- packs.push(loadPack(packDir));
292
- } catch (err) {
293
- logger.warning(`Failed to load pack ${entry}: ${err}`);
294
- }
295
- }
296
- return packs;
297
- }
298
- function storePrefix(scope) {
299
- const parts = scope.split(/[:\-_./]/).filter(Boolean);
300
- if (parts.length >= 2) {
301
- const p2 = parts[1];
302
- return (parts[0][0] + p2[0] + (p2[1] || p2[0])).toUpperCase();
303
- }
304
- const w = parts[0] || scope;
305
- if (w.length >= 3) return (w[0] + w[Math.floor(w.length / 2)] + w[w.length - 1]).toUpperCase();
306
- return (w[0] + (w[1] || w[0]) + (w[2] || w[0])).toUpperCase();
307
- }
308
- function generateEngramId(existing) {
309
- const now = /* @__PURE__ */ new Date();
310
- const date = now.toISOString().slice(0, 10).replace(/-/g, "");
311
- const prefix = `ENG-${date.slice(0, 4)}-${date.slice(4)}-`;
312
- const existingNums = existing.filter((e) => e.id.startsWith(prefix)).map((e) => parseInt(e.id.slice(prefix.length), 10)).filter((n) => !isNaN(n));
313
- const next = existingNums.length > 0 ? Math.max(...existingNums) + 1 : 1;
314
- return `${prefix}${String(next).padStart(3, "0")}`;
315
- }
316
-
317
- // src/storage-indexed.ts
318
65
  var require2 = createRequire(import.meta.url);
319
66
  var Database = null;
320
67
  function getDatabase() {
@@ -367,7 +114,7 @@ var IndexedStorage = class {
367
114
  }
368
115
  /** Load all engrams from SQLite index. Auto-rebuilds if db missing. */
369
116
  loadAll() {
370
- if (!existsSync3(this.dbPath)) {
117
+ if (!existsSync2(this.dbPath)) {
371
118
  this.reindex();
372
119
  }
373
120
  const db = this.getDb();
@@ -376,7 +123,7 @@ var IndexedStorage = class {
376
123
  }
377
124
  /** Load engrams with SQL-level filtering. */
378
125
  loadFiltered(filter) {
379
- if (!existsSync3(this.dbPath)) {
126
+ if (!existsSync2(this.dbPath)) {
380
127
  this.reindex();
381
128
  }
382
129
  const db = this.getDb();
@@ -400,7 +147,7 @@ var IndexedStorage = class {
400
147
  }
401
148
  /** Count engrams with optional status filter. */
402
149
  count(filter) {
403
- if (!existsSync3(this.dbPath)) {
150
+ if (!existsSync2(this.dbPath)) {
404
151
  this.reindex();
405
152
  }
406
153
  const db = this.getDb();
@@ -465,39 +212,63 @@ var IndexedStorage = class {
465
212
  };
466
213
 
467
214
  // src/config.ts
468
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
469
- import yaml2 from "js-yaml";
215
+ import { existsSync as existsSync3, readFileSync } from "fs";
216
+ import yaml from "js-yaml";
470
217
 
471
218
  // src/schemas/config.ts
472
- import { z as z3 } from "zod";
473
- var StoreEntrySchema = z3.object({
474
- path: z3.string(),
475
- scope: z3.string(),
476
- shared: z3.boolean().default(false),
477
- readonly: z3.boolean().default(false)
219
+ import { z } from "zod";
220
+ var StoreEntrySchema = z.object({
221
+ path: z.string(),
222
+ scope: z.string(),
223
+ shared: z.boolean().default(false),
224
+ readonly: z.boolean().default(false)
478
225
  });
479
- var PlurConfigSchema = z3.object({
480
- auto_learn: z3.boolean().default(true),
481
- auto_capture: z3.boolean().default(true),
482
- injection_budget: z3.number().default(2e3),
483
- decay_enabled: z3.boolean().default(true),
484
- decay_threshold: z3.number().default(0.15),
485
- packs: z3.array(z3.string()).default([]),
486
- injection: z3.object({
487
- spread_cap: z3.number().default(3),
488
- spread_budget: z3.number().default(480),
489
- co_access: z3.boolean().default(true)
226
+ var LlmTierConfigSchema = z.object({
227
+ dedup_tier: z.enum(["fast", "balanced", "thorough"]).default("fast"),
228
+ profile_tier: z.enum(["fast", "balanced", "thorough"]).default("balanced"),
229
+ meta_tier: z.enum(["fast", "balanced", "thorough"]).default("thorough")
230
+ }).partial();
231
+ var ProfileConfigSchema = z.object({
232
+ enabled: z.boolean().default(true),
233
+ cache_ttl_hours: z.number().default(24)
234
+ }).partial();
235
+ var DedupConfigSchema = z.object({
236
+ enabled: z.boolean().default(true),
237
+ threshold: z.number().min(0).max(1).default(0.85),
238
+ mode: z.enum(["llm", "cosine", "off"]).default("llm")
239
+ }).partial();
240
+ var StorageConfigSchema = z.object({
241
+ backend: z.enum(["yaml", "sqlite"]).default("yaml"),
242
+ path: z.string().optional()
243
+ }).partial();
244
+ var PlurConfigSchema = z.object({
245
+ auto_learn: z.boolean().default(true),
246
+ auto_capture: z.boolean().default(true),
247
+ injection_budget: z.number().default(2e3),
248
+ decay_enabled: z.boolean().default(true),
249
+ decay_threshold: z.number().default(0.15),
250
+ packs: z.array(z.string()).default([]),
251
+ injection: z.object({
252
+ spread_cap: z.number().default(3),
253
+ spread_budget: z.number().default(480),
254
+ co_access: z.boolean().default(true)
490
255
  }).default({}),
491
- allow_secrets: z3.boolean().default(false),
492
- index: z3.boolean().default(true),
493
- stores: z3.array(StoreEntrySchema).default([])
256
+ dedup: DedupConfigSchema.default({}),
257
+ decay_baseline: z.string().optional(),
258
+ allow_secrets: z.boolean().default(false),
259
+ index: z.boolean().default(true),
260
+ storage: StorageConfigSchema.default({}),
261
+ stores: z.array(StoreEntrySchema).default([]),
262
+ llm: LlmTierConfigSchema.default({}),
263
+ profile: ProfileConfigSchema.default({}),
264
+ registry_url: z.string().url().optional()
494
265
  }).partial();
495
266
 
496
267
  // src/config.ts
497
268
  function loadConfig(configPath) {
498
- if (!existsSync4(configPath)) return PlurConfigSchema.parse({});
269
+ if (!existsSync3(configPath)) return PlurConfigSchema.parse({});
499
270
  try {
500
- const raw = yaml2.load(readFileSync2(configPath, "utf8"));
271
+ const raw = yaml.load(readFileSync(configPath, "utf8"));
501
272
  return PlurConfigSchema.parse(raw ?? {});
502
273
  } catch {
503
274
  return PlurConfigSchema.parse({});
@@ -523,6 +294,28 @@ function decayedCoAccessStrength(strength, daysSinceUpdate, lambda = 0.01) {
523
294
  const floor = 0.02;
524
295
  return floor + (strength - floor) * Math.exp(-lambda * daysSinceUpdate);
525
296
  }
297
+ function confidenceDecay(retrievalStrength, lastPositiveFeedbackDate, commitment, decayBaseline, now) {
298
+ if (commitment === "locked") return retrievalStrength;
299
+ const CONFIDENCE_DECAY_FLOOR = 0.1;
300
+ const GRACE_PERIOD_DAYS = 90;
301
+ const MONTHLY_MULTIPLIER = 0.95;
302
+ const current = now || /* @__PURE__ */ new Date();
303
+ let referenceDate;
304
+ if (lastPositiveFeedbackDate) {
305
+ referenceDate = new Date(lastPositiveFeedbackDate);
306
+ } else if (decayBaseline) {
307
+ referenceDate = new Date(decayBaseline);
308
+ } else {
309
+ return retrievalStrength;
310
+ }
311
+ const daysSinceRef = Math.max(0, Math.floor((current.getTime() - referenceDate.getTime()) / MS_PER_DAY));
312
+ if (daysSinceRef <= GRACE_PERIOD_DAYS) return retrievalStrength;
313
+ const daysOverGrace = daysSinceRef - GRACE_PERIOD_DAYS;
314
+ const monthsOverGrace = daysOverGrace / 30;
315
+ const multiplier = Math.pow(MONTHLY_MULTIPLIER, monthsOverGrace);
316
+ const decayed = retrievalStrength * multiplier;
317
+ return Math.max(CONFIDENCE_DECAY_FLOOR, decayed);
318
+ }
526
319
 
527
320
  // src/polarity.ts
528
321
  var DONT_PATTERNS = [
@@ -571,6 +364,18 @@ function confidenceBand(score) {
571
364
  return "low";
572
365
  }
573
366
 
367
+ // src/fresh-tail.ts
368
+ var FRESH_TAIL_DAYS = 7;
369
+ var FRESH_TAIL_MAX_BOOST = 0.2;
370
+ function freshTailBoost(createdAt, commitment, now) {
371
+ if (commitment && !["exploring", "leaning"].includes(commitment)) return 0;
372
+ const created = new Date(createdAt);
373
+ const today = now ?? /* @__PURE__ */ new Date();
374
+ const daysSinceCreation = (today.getTime() - created.getTime()) / (1e3 * 60 * 60 * 24);
375
+ if (daysSinceCreation < 0 || daysSinceCreation > FRESH_TAIL_DAYS) return 0;
376
+ return FRESH_TAIL_MAX_BOOST * (1 - daysSinceCreation / FRESH_TAIL_DAYS);
377
+ }
378
+
574
379
  // src/inject.ts
575
380
  var DEFAULT_MAX_TOKENS = 8e3;
576
381
  var DEFAULT_MIN_RELEVANCE = 0.3;
@@ -595,7 +400,7 @@ function tokenize(text) {
595
400
  }
596
401
  function anchorBoost(engram, taskWords) {
597
402
  if (!engram.knowledge_anchors?.length) return 0;
598
- const threshold2 = taskWords.size <= 1 ? 1 : 2;
403
+ const threshold = taskWords.size <= 1 ? 1 : 2;
599
404
  let boost = 0;
600
405
  for (const anchor of engram.knowledge_anchors) {
601
406
  if (!anchor.snippet) continue;
@@ -604,7 +409,7 @@ function anchorBoost(engram, taskWords) {
604
409
  for (const word of taskWords) {
605
410
  if (snippetWords.has(word)) overlap++;
606
411
  }
607
- if (overlap >= threshold2) boost += 0.5;
412
+ if (overlap >= threshold) boost += 0.5;
608
413
  }
609
414
  return Math.min(boost, 2);
610
415
  }
@@ -655,7 +460,12 @@ function scoreEngram(engram, promptLower, promptWords, packMatchTerms, scopeFilt
655
460
  if (statementWords.has(word)) termHits += 0.5;
656
461
  }
657
462
  if (termHits === 0) return 0;
658
- const rs = isPack ? engram.activation.retrieval_strength : decayedStrength(engram.activation.retrieval_strength, daysSince(engram.activation.last_accessed));
463
+ let rs = isPack ? engram.activation.retrieval_strength : decayedStrength(engram.activation.retrieval_strength, daysSince(engram.activation.last_accessed));
464
+ if (!isPack) {
465
+ const fb = engram.feedback_signals;
466
+ const lastPositive = fb && fb.positive > 0 ? engram.activation.last_accessed : null;
467
+ rs = confidenceDecay(rs, lastPositive, engram.commitment, void 0);
468
+ }
659
469
  let score = termHits * rs;
660
470
  const feedback = engram.feedback_signals;
661
471
  if (feedback) {
@@ -712,6 +522,11 @@ function selectAndSpread(ctx, personalEngrams, packs, config, embeddingBoosts) {
712
522
  } else if (raw > 0 && embBoost > 0) {
713
523
  raw += embBoost;
714
524
  }
525
+ if (raw > 0) {
526
+ const createdAt = engram.temporal?.learned_at ?? engram.activation.last_accessed;
527
+ const ftBoost = freshTailBoost(createdAt, engram.commitment, /* @__PURE__ */ new Date());
528
+ if (ftBoost > 0) raw += ftBoost;
529
+ }
715
530
  if (raw > 0) {
716
531
  scored.push({ ...engram, keyword_match: raw, raw_score: raw, score: raw });
717
532
  }
@@ -813,33 +628,83 @@ function selectAndSpread(ctx, personalEngrams, packs, config, embeddingBoosts) {
813
628
  const wireConsider = agentConsider.map(stripScoring);
814
629
  const wireDirectives = [];
815
630
  const wireConstraints = [];
631
+ const cognitiveDemoted = [];
816
632
  for (const wire of wireAll) {
817
633
  const polarity = wire.polarity ?? classifyPolarity(wire.statement);
818
- if (polarity === "dont") {
634
+ const commitment = wire.commitment;
635
+ if (commitment) {
636
+ const mult = { locked: 1, decided: 0.9, leaning: 0.7, exploring: 0.5 };
637
+ wire.confidence_score *= mult[commitment] ?? 1;
638
+ }
639
+ const cogLevel = wire.knowledge_type?.cognitive_level;
640
+ if (cogLevel === "remember" || cogLevel === "understand") {
641
+ cognitiveDemoted.push(wire);
642
+ } else if (polarity === "dont") {
643
+ wireConstraints.push(wire);
644
+ } else if (cogLevel === "apply" || cogLevel === "analyze") {
819
645
  wireConstraints.push(wire);
820
646
  } else {
821
647
  wireDirectives.push(wire);
822
648
  }
823
649
  }
650
+ const allWireConsider = [...wireConsider, ...cognitiveDemoted];
824
651
  const considerTokens = dip19PoolTokens + spreadTokens;
825
652
  return {
826
653
  directives: wireDirectives,
827
654
  constraints: wireConstraints,
828
- consider: wireConsider,
655
+ consider: allWireConsider,
829
656
  tokens_used: { directives: directiveTokens, consider: considerTokens }
830
657
  };
831
658
  }
659
+ function formatLayer1(engram) {
660
+ const display = engram.summary ?? engram.statement.slice(0, 60);
661
+ return `[${engram.id}] ${display}`;
662
+ }
663
+ function formatLayer2(engram) {
664
+ return `[${engram.id}] ${engram.statement}`;
665
+ }
666
+ function formatLayer3(engram) {
667
+ const lines = [`[${engram.id}] ${engram.statement}`];
668
+ if (engram.rationale) lines.push(` Rationale: ${engram.rationale}`);
669
+ const meta = [];
670
+ if (engram.domain) meta.push(`Domain: ${engram.domain}`);
671
+ if (engram.confidence_score != null) meta.push(`Confidence: ${engram.confidence_score.toFixed(2)}`);
672
+ if (engram.activation?.last_accessed) meta.push(`Last verified: ${engram.activation.last_accessed}`);
673
+ if (meta.length > 0) lines.push(` ${meta.join(" | ")}`);
674
+ return lines.join("\n");
675
+ }
676
+ function assignLayer(bucket) {
677
+ switch (bucket) {
678
+ case "directives":
679
+ return 3;
680
+ case "constraints":
681
+ return 2;
682
+ case "consider":
683
+ return 1;
684
+ }
685
+ }
686
+ function formatWithLayer(engrams, layer) {
687
+ if (engrams.length === 0) return "";
688
+ switch (layer) {
689
+ case 1:
690
+ return engrams.map(formatLayer1).join(" | ");
691
+ case 2:
692
+ return engrams.map(formatLayer2).join("\n");
693
+ case 3:
694
+ return engrams.map(formatLayer3).join("\n");
695
+ }
696
+ }
832
697
 
833
698
  // src/episodes.ts
834
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
835
- import yaml3 from "js-yaml";
699
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
700
+ import yaml2 from "js-yaml";
836
701
  function generateEpisodeId() {
837
702
  const ts = Date.now();
838
703
  const rand = Math.random().toString(36).slice(2, 6);
839
704
  return `EP-${ts}-${rand}`;
840
705
  }
841
- function captureEpisode(path2, summary, context) {
842
- const episodes = loadEpisodes(path2);
706
+ function captureEpisode(path3, summary, context) {
707
+ const episodes = loadEpisodes(path3);
843
708
  const episode = {
844
709
  id: generateEpisodeId(),
845
710
  summary,
@@ -850,11 +715,11 @@ function captureEpisode(path2, summary, context) {
850
715
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
851
716
  };
852
717
  episodes.push(episode);
853
- atomicWrite(path2, yaml3.dump(episodes, { lineWidth: 120, noRefs: true }));
718
+ atomicWrite(path3, yaml2.dump(episodes, { lineWidth: 120, noRefs: true }));
854
719
  return episode;
855
720
  }
856
- function queryTimeline(path2, query) {
857
- let episodes = loadEpisodes(path2);
721
+ function queryTimeline(path3, query) {
722
+ let episodes = loadEpisodes(path3);
858
723
  if (query?.since) episodes = episodes.filter((e) => new Date(e.timestamp) >= query.since);
859
724
  if (query?.until) episodes = episodes.filter((e) => new Date(e.timestamp) <= query.until);
860
725
  if (query?.agent) episodes = episodes.filter((e) => e.agent === query.agent);
@@ -865,10 +730,10 @@ function queryTimeline(path2, query) {
865
730
  }
866
731
  return episodes;
867
732
  }
868
- function loadEpisodes(path2) {
869
- if (!existsSync5(path2)) return [];
733
+ function loadEpisodes(path3) {
734
+ if (!existsSync4(path3)) return [];
870
735
  try {
871
- const raw = yaml3.load(readFileSync3(path2, "utf8"));
736
+ const raw = yaml2.load(readFileSync2(path3, "utf8"));
872
737
  return Array.isArray(raw) ? raw : [];
873
738
  } catch {
874
739
  return [];
@@ -876,7 +741,7 @@ function loadEpisodes(path2) {
876
741
  }
877
742
 
878
743
  // src/conflict.ts
879
- function detectConflicts(newEngram, existing, threshold2 = 0.4) {
744
+ function detectConflicts(newEngram, existing, threshold = 0.4) {
880
745
  const newScope = newEngram.scope || "global";
881
746
  const newTokens = ftsTokenize(newEngram.statement);
882
747
  if (newTokens.length === 0) return [];
@@ -884,7 +749,7 @@ function detectConflicts(newEngram, existing, threshold2 = 0.4) {
884
749
  if (e.status !== "active") return false;
885
750
  if ((e.scope || "global") !== newScope) return false;
886
751
  const score = ftsScore(e, newTokens);
887
- return score >= threshold2;
752
+ return score >= threshold;
888
753
  });
889
754
  }
890
755
 
@@ -1059,11 +924,112 @@ async function expandedSearch(engrams, query, limit, llm, storagePath) {
1059
924
  return merged.slice(0, effectiveLimit);
1060
925
  }
1061
926
 
927
+ // src/search-orchestrator.ts
928
+ function isKeywordQuery(query) {
929
+ const words = query.trim().split(/\s+/);
930
+ if (words.length >= 5) return false;
931
+ const nlSignals = /^(what|where|when|how|why|which|who|can|do|does|is|are|should|would|could)\b/i;
932
+ if (nlSignals.test(query.trim())) return false;
933
+ if (query.includes("?")) return false;
934
+ return true;
935
+ }
936
+ function normalizedBm25Scores(engrams, query) {
937
+ const queryTokens = ftsTokenize(query);
938
+ if (queryTokens.length === 0) return /* @__PURE__ */ new Map();
939
+ const idfWeights = computeIdf(engrams, queryTokens);
940
+ const avgDocLength = engrams.length > 0 ? engrams.reduce((sum, e) => sum + ftsTokenize(engramSearchText(e)).length, 0) / engrams.length : 0;
941
+ const rawScores = [];
942
+ for (const e of engrams) {
943
+ const score = ftsScore(e, queryTokens, idfWeights, avgDocLength);
944
+ if (score > 0) rawScores.push({ id: e.id, score });
945
+ }
946
+ if (rawScores.length === 0) return /* @__PURE__ */ new Map();
947
+ const min = Math.min(...rawScores.map((r) => r.score));
948
+ const max = Math.max(...rawScores.map((r) => r.score));
949
+ const range = max - min || 1;
950
+ const normalized = /* @__PURE__ */ new Map();
951
+ for (const { id, score } of rawScores) {
952
+ normalized.set(id, (score - min) / range);
953
+ }
954
+ return normalized;
955
+ }
956
+ async function recallAuto(engrams, query, limit, storagePath, llm) {
957
+ if (engrams.length === 0) return { results: [], strategy_used: "bm25" };
958
+ if (isKeywordQuery(query)) {
959
+ const bm25Results = searchEngrams(engrams, query, limit);
960
+ const scores2 = normalizedBm25Scores(engrams, query);
961
+ const maxScore2 = bm25Results.length > 0 ? scores2.get(bm25Results[0].id) ?? 0 : 0;
962
+ if (bm25Results.length >= 3 && maxScore2 >= 0.3) {
963
+ return { results: bm25Results, strategy_used: "bm25" };
964
+ }
965
+ try {
966
+ const hybridResults2 = await hybridSearch(engrams, query, limit, storagePath);
967
+ if (hybridResults2.length >= 3) return { results: hybridResults2, strategy_used: "hybrid" };
968
+ } catch {
969
+ }
970
+ if (llm) {
971
+ try {
972
+ const expandedResults = await expandedSearch(engrams, query, limit, llm, storagePath);
973
+ return { results: expandedResults, strategy_used: "expanded" };
974
+ } catch {
975
+ }
976
+ }
977
+ return { results: bm25Results, strategy_used: "bm25" };
978
+ }
979
+ let hybridResults = [];
980
+ try {
981
+ hybridResults = await hybridSearch(engrams, query, limit, storagePath);
982
+ } catch {
983
+ const bm25Results = searchEngrams(engrams, query, limit);
984
+ return { results: bm25Results, strategy_used: "bm25" };
985
+ }
986
+ const scores = normalizedBm25Scores(engrams, query);
987
+ const maxScore = hybridResults.length > 0 ? scores.get(hybridResults[0].id) ?? 0 : 0;
988
+ if (hybridResults.length >= 3 && maxScore >= 0.3) {
989
+ return { results: hybridResults, strategy_used: "hybrid" };
990
+ }
991
+ if (llm) {
992
+ try {
993
+ const expandedResults = await expandedSearch(engrams, query, limit, llm, storagePath);
994
+ return { results: expandedResults, strategy_used: "expanded" };
995
+ } catch {
996
+ }
997
+ }
998
+ if (hybridResults.length > 0) return { results: hybridResults, strategy_used: "hybrid" };
999
+ return { results: searchEngrams(engrams, query, limit), strategy_used: "bm25" };
1000
+ }
1001
+
1002
+ // src/summary.ts
1003
+ var SUMMARY_MAX_LENGTH = 60;
1004
+ var STATEMENT_THRESHOLD = 200;
1005
+ function needsSummary(statement, cognitiveLevel) {
1006
+ if (statement.length <= STATEMENT_THRESHOLD) return false;
1007
+ if (cognitiveLevel && !["remember", "understand"].includes(cognitiveLevel)) return false;
1008
+ return true;
1009
+ }
1010
+ function generateSummary(statement) {
1011
+ const sentenceEnd = statement.search(/[.!?]\s/);
1012
+ if (sentenceEnd > 0 && sentenceEnd <= SUMMARY_MAX_LENGTH) {
1013
+ return statement.slice(0, sentenceEnd + 1);
1014
+ }
1015
+ if (statement.length <= SUMMARY_MAX_LENGTH) return statement;
1016
+ const truncated = statement.slice(0, SUMMARY_MAX_LENGTH);
1017
+ const lastSpace = truncated.lastIndexOf(" ");
1018
+ if (lastSpace > SUMMARY_MAX_LENGTH * 0.5) {
1019
+ return truncated.slice(0, lastSpace) + "...";
1020
+ }
1021
+ return truncated + "...";
1022
+ }
1023
+ function autoSummary(statement, cognitiveLevel) {
1024
+ if (!needsSummary(statement, cognitiveLevel)) return void 0;
1025
+ return generateSummary(statement);
1026
+ }
1027
+
1062
1028
  // src/packs.ts
1063
- import * as fs2 from "fs";
1029
+ import * as fs from "fs";
1064
1030
  import * as path from "path";
1065
1031
  import * as crypto from "crypto";
1066
- import yaml4 from "js-yaml";
1032
+ import yaml3 from "js-yaml";
1067
1033
 
1068
1034
  // src/secrets.ts
1069
1035
  var SECRET_PATTERNS = [
@@ -1094,17 +1060,17 @@ function registryPath(packsDir) {
1094
1060
  }
1095
1061
  function loadRegistry(packsDir) {
1096
1062
  const p = registryPath(packsDir);
1097
- if (!fs2.existsSync(p)) return [];
1063
+ if (!fs.existsSync(p)) return [];
1098
1064
  try {
1099
- const raw = yaml4.load(fs2.readFileSync(p, "utf8"));
1065
+ const raw = yaml3.load(fs.readFileSync(p, "utf8"));
1100
1066
  return Array.isArray(raw?.packs) ? raw.packs : [];
1101
1067
  } catch {
1102
1068
  return [];
1103
1069
  }
1104
1070
  }
1105
1071
  function saveRegistry(packsDir, entries) {
1106
- const content = yaml4.dump({ packs: entries }, { lineWidth: 120, noRefs: true, quotingType: '"' });
1107
- fs2.writeFileSync(registryPath(packsDir), content);
1072
+ const content = yaml3.dump({ packs: entries }, { lineWidth: 120, noRefs: true, quotingType: '"' });
1073
+ fs.writeFileSync(registryPath(packsDir), content);
1108
1074
  }
1109
1075
  function addToRegistry(packsDir, entry) {
1110
1076
  const entries = loadRegistry(packsDir);
@@ -1118,7 +1084,7 @@ function removeFromRegistry(packsDir, name) {
1118
1084
  saveRegistry(packsDir, entries);
1119
1085
  }
1120
1086
  function previewPack(source) {
1121
- if (!fs2.existsSync(source)) throw new Error(`Pack source not found: ${source}`);
1087
+ if (!fs.existsSync(source)) throw new Error(`Pack source not found: ${source}`);
1122
1088
  const pack = loadPack(source);
1123
1089
  const security = scanPrivacy(pack.engrams);
1124
1090
  const warnings = [];
@@ -1181,7 +1147,7 @@ function detectConflicts2(newEngrams, existingEngrams) {
1181
1147
  return conflicts;
1182
1148
  }
1183
1149
  function installPack(packsDir, source, existingEngrams) {
1184
- if (!fs2.existsSync(source)) throw new Error(`Pack source not found: ${source}`);
1150
+ if (!fs.existsSync(source)) throw new Error(`Pack source not found: ${source}`);
1185
1151
  const preview = previewPack(source);
1186
1152
  const secretIssues = preview.security.issues.filter((i) => i.type === "secret");
1187
1153
  if (secretIssues.length > 0) {
@@ -1191,17 +1157,17 @@ ${details}`);
1191
1157
  }
1192
1158
  const sourceName = path.basename(source);
1193
1159
  const destDir = path.join(packsDir, sourceName);
1194
- if (!fs2.existsSync(destDir)) fs2.mkdirSync(destDir, { recursive: true });
1195
- const files = fs2.readdirSync(source);
1160
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
1161
+ const files = fs.readdirSync(source);
1196
1162
  for (const file of files) {
1197
1163
  const srcPath = path.join(source, file);
1198
1164
  const destPath = path.join(destDir, file);
1199
- if (fs2.statSync(srcPath).isFile()) {
1200
- fs2.copyFileSync(srcPath, destPath);
1165
+ if (fs.statSync(srcPath).isFile()) {
1166
+ fs.copyFileSync(srcPath, destPath);
1201
1167
  }
1202
1168
  }
1203
1169
  const engramsPath = path.join(destDir, "engrams.yaml");
1204
- const newEngrams = fs2.existsSync(engramsPath) ? loadEngrams(engramsPath) : [];
1170
+ const newEngrams = fs.existsSync(engramsPath) ? loadEngrams(engramsPath) : [];
1205
1171
  const conflicts = existingEngrams ? detectConflicts2(newEngrams, existingEngrams) : [];
1206
1172
  const integrity = `sha256:${computePackHash(destDir)}`;
1207
1173
  const registryEntry = {
@@ -1217,8 +1183,8 @@ ${details}`);
1217
1183
  }
1218
1184
  function uninstallPack(packsDir, name) {
1219
1185
  let packDir = path.join(packsDir, name);
1220
- if (!fs2.existsSync(packDir)) {
1221
- const entries = fs2.existsSync(packsDir) ? fs2.readdirSync(packsDir) : [];
1186
+ if (!fs.existsSync(packDir)) {
1187
+ const entries = fs.existsSync(packsDir) ? fs.readdirSync(packsDir) : [];
1222
1188
  const match = entries.find((e) => e.toLowerCase() === name.toLowerCase());
1223
1189
  if (match) {
1224
1190
  packDir = path.join(packsDir, match);
@@ -1239,17 +1205,17 @@ function uninstallPack(packsDir, name) {
1239
1205
  }
1240
1206
  removeFromRegistry(packsDir, name);
1241
1207
  if (manifestName && manifestName !== name) removeFromRegistry(packsDir, manifestName);
1242
- fs2.rmSync(packDir, { recursive: true, force: true });
1208
+ fs.rmSync(packDir, { recursive: true, force: true });
1243
1209
  return { name, removed: true, engram_count: count };
1244
1210
  }
1245
1211
  function listPacks(packsDir) {
1246
- if (!fs2.existsSync(packsDir)) return [];
1212
+ if (!fs.existsSync(packsDir)) return [];
1247
1213
  const registry = loadRegistry(packsDir);
1248
1214
  const registryMap = new Map(registry.map((r) => [r.name, r]));
1249
1215
  const result = [];
1250
- for (const entry of fs2.readdirSync(packsDir)) {
1216
+ for (const entry of fs.readdirSync(packsDir)) {
1251
1217
  const packDir = path.join(packsDir, entry);
1252
- if (!fs2.statSync(packDir).isDirectory()) continue;
1218
+ if (!fs.statSync(packDir).isDirectory()) continue;
1253
1219
  try {
1254
1220
  const pack = loadPack(packDir);
1255
1221
  const currentIntegrity = `sha256:${computePackHash(packDir)}`;
@@ -1266,7 +1232,7 @@ function listPacks(packsDir) {
1266
1232
  });
1267
1233
  } catch {
1268
1234
  const engramsPath = path.join(packDir, "engrams.yaml");
1269
- const engrams = fs2.existsSync(engramsPath) ? loadEngrams(engramsPath) : [];
1235
+ const engrams = fs.existsSync(engramsPath) ? loadEngrams(engramsPath) : [];
1270
1236
  const reg = registryMap.get(entry);
1271
1237
  result.push({
1272
1238
  name: entry,
@@ -1356,8 +1322,8 @@ function exportPack(engrams, outputDir, manifest) {
1356
1322
  );
1357
1323
  const safeEngrams = engrams.filter((e) => !blockedIds.has(e.id));
1358
1324
  const matchTerms = deriveMatchTerms(safeEngrams);
1359
- if (!fs2.existsSync(outputDir)) fs2.mkdirSync(outputDir, { recursive: true });
1360
- const frontmatter = yaml4.dump({
1325
+ if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
1326
+ const frontmatter = yaml3.dump({
1361
1327
  name: manifest.name,
1362
1328
  version: manifest.version,
1363
1329
  description: manifest.description,
@@ -1368,7 +1334,7 @@ function exportPack(engrams, outputDir, manifest) {
1368
1334
  engram_count: safeEngrams.length
1369
1335
  }
1370
1336
  });
1371
- fs2.writeFileSync(
1337
+ fs.writeFileSync(
1372
1338
  path.join(outputDir, "SKILL.md"),
1373
1339
  `---
1374
1340
  ${frontmatter}---
@@ -1405,10 +1371,10 @@ ${manifest.description || ""}
1405
1371
  }
1406
1372
  return cleaned;
1407
1373
  });
1408
- const content = yaml4.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
1409
- fs2.writeFileSync(path.join(outputDir, "engrams.yaml"), content);
1374
+ const content = yaml3.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
1375
+ fs.writeFileSync(path.join(outputDir, "engrams.yaml"), content);
1410
1376
  const integrity = computePackHash(outputDir);
1411
- fs2.writeFileSync(path.join(outputDir, "INTEGRITY"), `sha256:${integrity}
1377
+ fs.writeFileSync(path.join(outputDir, "INTEGRITY"), `sha256:${integrity}
1412
1378
  `);
1413
1379
  return {
1414
1380
  path: outputDir,
@@ -1422,14 +1388,14 @@ function computePackHash(packDir) {
1422
1388
  const hash = crypto.createHash("sha256");
1423
1389
  const skillMd = path.join(packDir, "SKILL.md");
1424
1390
  const manifestYaml = path.join(packDir, "manifest.yaml");
1425
- if (fs2.existsSync(skillMd)) {
1426
- hash.update(fs2.readFileSync(skillMd));
1427
- } else if (fs2.existsSync(manifestYaml)) {
1428
- hash.update(fs2.readFileSync(manifestYaml));
1391
+ if (fs.existsSync(skillMd)) {
1392
+ hash.update(fs.readFileSync(skillMd));
1393
+ } else if (fs.existsSync(manifestYaml)) {
1394
+ hash.update(fs.readFileSync(manifestYaml));
1429
1395
  }
1430
1396
  const engramsPath = path.join(packDir, "engrams.yaml");
1431
- if (fs2.existsSync(engramsPath)) {
1432
- hash.update(fs2.readFileSync(engramsPath));
1397
+ if (fs.existsSync(engramsPath)) {
1398
+ hash.update(fs.readFileSync(engramsPath));
1433
1399
  }
1434
1400
  return hash.digest("hex");
1435
1401
  }
@@ -1550,7 +1516,7 @@ async function computeSimilarityMatrix(templates) {
1550
1516
  const n = templates.length;
1551
1517
  const matrix = Array.from({ length: n }, () => Array(n).fill(0));
1552
1518
  try {
1553
- const { embed, cosineSimilarity } = await import("./embeddings-2IODIQAF.js");
1519
+ const { embed, cosineSimilarity } = await import("./embeddings-EX7QPXJS.js");
1554
1520
  const embeddings = [];
1555
1521
  for (const t of templates) {
1556
1522
  embeddings.push(await embed(t));
@@ -1580,11 +1546,11 @@ async function computeSimilarityMatrix(templates) {
1580
1546
  }
1581
1547
  var EMBEDDING_THRESHOLD = 0.65;
1582
1548
  var TOKEN_THRESHOLD = 0.35;
1583
- async function clusterByStructure(analyses, threshold2) {
1549
+ async function clusterByStructure(analyses, threshold) {
1584
1550
  if (analyses.length < 2) return [];
1585
1551
  const templates = analyses.map((a) => analysisTemplate(a));
1586
1552
  const { matrix, method } = await computeSimilarityMatrix(templates);
1587
- const effectiveThreshold = threshold2 ?? (method === "embedding" ? EMBEDDING_THRESHOLD : TOKEN_THRESHOLD);
1553
+ const effectiveThreshold = threshold ?? (method === "embedding" ? EMBEDDING_THRESHOLD : TOKEN_THRESHOLD);
1588
1554
  const seenClusters = /* @__PURE__ */ new Set();
1589
1555
  const clusters = [];
1590
1556
  let clusterId = 0;
@@ -1836,6 +1802,8 @@ Return ONLY valid JSON, no markdown fencing.`;
1836
1802
  abstract: null,
1837
1803
  derived_from: null,
1838
1804
  polarity: null,
1805
+ engram_version: 1,
1806
+ episode_ids: [],
1839
1807
  knowledge_type: {
1840
1808
  memory_class: "metacognitive",
1841
1809
  cognitive_level: "evaluate"
@@ -2165,15 +2133,15 @@ Meta-engrams flagged as "[structural transfer \u2014 untested in current domain]
2165
2133
  }
2166
2134
 
2167
2135
  // src/schemas/meta-engram.ts
2168
- import { z as z4 } from "zod";
2169
- var StructuralTemplateSchema = z4.object({
2170
- goal_type: z4.string().min(1),
2171
- constraint_type: z4.string().min(1),
2172
- outcome_type: z4.string().min(1),
2173
- template: z4.string().min(1),
2136
+ import { z as z2 } from "zod";
2137
+ var StructuralTemplateSchema = z2.object({
2138
+ goal_type: z2.string().min(1),
2139
+ constraint_type: z2.string().min(1),
2140
+ outcome_type: z2.string().min(1),
2141
+ template: z2.string().min(1),
2174
2142
  /** Structural frame — declares which relational pattern this meta-engram uses.
2175
2143
  * Allows flexible analogy beyond rigid [goal]+[constraint]->[outcome]. */
2176
- structure_type: z4.enum([
2144
+ structure_type: z2.enum([
2177
2145
  "goal-constraint-outcome",
2178
2146
  "feedback-loop",
2179
2147
  "causal-chain",
@@ -2182,47 +2150,672 @@ var StructuralTemplateSchema = z4.object({
2182
2150
  "freeform"
2183
2151
  ]).default("goal-constraint-outcome"),
2184
2152
  /** For patterns that don't fit the standard template fields — the LLM's own structural description */
2185
- freeform_structure: z4.string().optional()
2153
+ freeform_structure: z2.string().optional()
2186
2154
  });
2187
- var EvidenceEntrySchema = z4.object({
2188
- engram_id: z4.string(),
2189
- domain: z4.string(),
2190
- mapping_rationale: z4.string(),
2191
- alignment_score: z4.number().min(0).max(1)
2155
+ var EvidenceEntrySchema = z2.object({
2156
+ engram_id: z2.string(),
2157
+ domain: z2.string(),
2158
+ mapping_rationale: z2.string(),
2159
+ alignment_score: z2.number().min(0).max(1)
2192
2160
  });
2193
- var FalsificationSchema = z4.object({
2194
- expected_conditions: z4.string(),
2195
- expected_exceptions: z4.string(),
2196
- test_prediction: z4.string().optional()
2161
+ var FalsificationSchema = z2.object({
2162
+ expected_conditions: z2.string(),
2163
+ expected_exceptions: z2.string(),
2164
+ test_prediction: z2.string().optional()
2197
2165
  });
2198
- var MetaConfidenceSchema = z4.object({
2199
- evidence_count: z4.number().int().min(0),
2200
- domain_count: z4.number().int().min(0),
2201
- structural_depth: z4.number().int().min(1).max(5),
2202
- validation_ratio: z4.number().min(0).max(1).default(0),
2203
- composite: z4.number().min(0).max(1)
2166
+ var MetaConfidenceSchema = z2.object({
2167
+ evidence_count: z2.number().int().min(0),
2168
+ domain_count: z2.number().int().min(0),
2169
+ structural_depth: z2.number().int().min(1).max(5),
2170
+ validation_ratio: z2.number().min(0).max(1).default(0),
2171
+ composite: z2.number().min(0).max(1)
2204
2172
  });
2205
- var DomainCoverageSchema = z4.object({
2206
- validated: z4.array(z4.string()),
2207
- failed: z4.array(z4.string()).default([]),
2208
- predicted: z4.array(z4.string()).default([])
2173
+ var DomainCoverageSchema = z2.object({
2174
+ validated: z2.array(z2.string()),
2175
+ failed: z2.array(z2.string()).default([]),
2176
+ predicted: z2.array(z2.string()).default([])
2209
2177
  });
2210
- var HierarchyPositionSchema = z4.object({
2211
- level: z4.enum(["mop", "top"]),
2212
- parent: z4.string().nullable().default(null),
2213
- children: z4.array(z4.string()).default([])
2178
+ var HierarchyPositionSchema = z2.object({
2179
+ level: z2.enum(["mop", "top"]),
2180
+ parent: z2.string().nullable().default(null),
2181
+ children: z2.array(z2.string()).default([])
2214
2182
  });
2215
- var MetaFieldSchema = z4.object({
2183
+ var MetaFieldSchema = z2.object({
2216
2184
  structure: StructuralTemplateSchema,
2217
- evidence: z4.array(EvidenceEntrySchema).min(2),
2185
+ evidence: z2.array(EvidenceEntrySchema).min(2),
2218
2186
  domain_coverage: DomainCoverageSchema,
2219
2187
  falsification: FalsificationSchema,
2220
2188
  confidence: MetaConfidenceSchema,
2221
2189
  hierarchy: HierarchyPositionSchema,
2222
- pipeline_version: z4.string(),
2223
- last_validated: z4.string().optional()
2190
+ pipeline_version: z2.string(),
2191
+ last_validated: z2.string().optional()
2224
2192
  });
2225
2193
 
2194
+ // src/model-routing.ts
2195
+ var DEFAULT_MODEL_MAP = {
2196
+ fast: "claude-haiku-4-0",
2197
+ balanced: "claude-sonnet-4-20250514",
2198
+ thorough: "claude-opus-4-20250514"
2199
+ };
2200
+ var DEFAULT_TIERS = {
2201
+ dedup_tier: "fast",
2202
+ profile_tier: "balanced",
2203
+ meta_tier: "thorough"
2204
+ };
2205
+ function selectModel(tier, customMap) {
2206
+ const map = { ...DEFAULT_MODEL_MAP, ...customMap };
2207
+ return map[tier];
2208
+ }
2209
+ function resolveOperationTier(operation, config) {
2210
+ const key = `${operation}_tier`;
2211
+ return config?.[key] ?? DEFAULT_TIERS[key];
2212
+ }
2213
+ function selectModelForOperation(operation, config, customMap) {
2214
+ const tier = resolveOperationTier(operation, config);
2215
+ return selectModel(tier, customMap);
2216
+ }
2217
+
2218
+ // src/profile.ts
2219
+ import * as fs2 from "fs";
2220
+ import * as path2 from "path";
2221
+ var PROFILE_PROMPT = `You are analyzing a user's memory engrams to create a brief cognitive profile.
2222
+ Below are the user's stored learnings, grouped by domain. Synthesize a concise profile (3-5 sentences) describing:
2223
+ - Key preferences and working style
2224
+ - Technical domains and expertise areas
2225
+ - Constraints and things to avoid
2226
+ - Notable patterns in their knowledge
2227
+
2228
+ Be specific and actionable. Write in second person ("You prefer...", "You work with...").
2229
+
2230
+ Engrams by domain:
2231
+ {engrams_by_domain}
2232
+
2233
+ Profile:`;
2234
+ function getCachePath(storagePath) {
2235
+ return path2.join(storagePath, "profile_cache.json");
2236
+ }
2237
+ function loadProfileCache(storagePath) {
2238
+ try {
2239
+ return JSON.parse(fs2.readFileSync(getCachePath(storagePath), "utf8"));
2240
+ } catch {
2241
+ return null;
2242
+ }
2243
+ }
2244
+ function saveProfileCache(storagePath, cache2) {
2245
+ fs2.writeFileSync(getCachePath(storagePath), JSON.stringify(cache2, null, 2));
2246
+ }
2247
+ function markProfileDirty(storagePath) {
2248
+ const cache2 = loadProfileCache(storagePath);
2249
+ if (cache2) {
2250
+ cache2.dirty = true;
2251
+ saveProfileCache(storagePath, cache2);
2252
+ }
2253
+ }
2254
+ function profileNeedsRegeneration(cache2, cacheTtlHours = 24) {
2255
+ if (!cache2) return true;
2256
+ if (!cache2.dirty) return false;
2257
+ const hoursSince = (Date.now() - new Date(cache2.generated_at).getTime()) / (1e3 * 60 * 60);
2258
+ return hoursSince >= cacheTtlHours;
2259
+ }
2260
+ function clusterByDomain(engrams) {
2261
+ const clusters = /* @__PURE__ */ new Map();
2262
+ for (const e of engrams) {
2263
+ const domain = e.domain?.split(".")[0] ?? "general";
2264
+ const list = clusters.get(domain) ?? [];
2265
+ list.push(e);
2266
+ clusters.set(domain, list);
2267
+ }
2268
+ return clusters;
2269
+ }
2270
+ function formatClusters(clusters) {
2271
+ const lines = [];
2272
+ for (const [domain, engrams] of clusters) {
2273
+ lines.push(`
2274
+ ### ${domain} (${engrams.length} engrams)`);
2275
+ for (const e of engrams.slice(0, 10)) lines.push(`- ${e.statement}`);
2276
+ if (engrams.length > 10) lines.push(` ... and ${engrams.length - 10} more`);
2277
+ }
2278
+ return lines.join("\n");
2279
+ }
2280
+ async function generateProfile(engrams, llm, storagePath, cacheTtlHours = 24) {
2281
+ const cache2 = loadProfileCache(storagePath);
2282
+ if (cache2 && !profileNeedsRegeneration(cache2, cacheTtlHours)) return cache2.profile;
2283
+ if (engrams.length === 0) return null;
2284
+ const clusters = clusterByDomain(engrams);
2285
+ const prompt = PROFILE_PROMPT.replace("{engrams_by_domain}", formatClusters(clusters));
2286
+ try {
2287
+ const profile = await llm(prompt);
2288
+ if (!profile?.trim()) return cache2?.profile ?? null;
2289
+ saveProfileCache(storagePath, {
2290
+ profile: profile.trim(),
2291
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
2292
+ engram_count: engrams.length,
2293
+ dirty: false
2294
+ });
2295
+ return profile.trim();
2296
+ } catch {
2297
+ return cache2?.profile ?? null;
2298
+ }
2299
+ }
2300
+ function getProfileForInjection(storagePath) {
2301
+ const cache2 = loadProfileCache(storagePath);
2302
+ return cache2?.profile ?? null;
2303
+ }
2304
+
2305
+ // src/migrations/runner.ts
2306
+ import * as fs3 from "fs";
2307
+ import * as yaml4 from "js-yaml";
2308
+
2309
+ // src/migrations/20260406-001-add-commitment.ts
2310
+ var migration = {
2311
+ id: "20260406-001-add-commitment",
2312
+ description: "Add commitment field (default: decided for existing, leaning for new)",
2313
+ up(engrams) {
2314
+ return engrams.map((e) => {
2315
+ const clone = { ...e };
2316
+ if (!clone.commitment) {
2317
+ clone.commitment = e.status === "active" ? "decided" : "leaning";
2318
+ }
2319
+ return clone;
2320
+ });
2321
+ },
2322
+ down(engrams) {
2323
+ return engrams.map((e) => {
2324
+ const clone = { ...e };
2325
+ delete clone.commitment;
2326
+ delete clone.locked_at;
2327
+ delete clone.locked_reason;
2328
+ return clone;
2329
+ });
2330
+ }
2331
+ };
2332
+
2333
+ // src/migrations/20260406-002-add-content-hash.ts
2334
+ var migration2 = {
2335
+ id: "20260406-002-add-content-hash",
2336
+ description: "Add content_hash field (SHA256 of normalized statement)",
2337
+ up(engrams) {
2338
+ return engrams.map((e) => {
2339
+ const clone = { ...e };
2340
+ if (!clone.content_hash) {
2341
+ clone.content_hash = computeContentHash(e.statement);
2342
+ }
2343
+ return clone;
2344
+ });
2345
+ },
2346
+ down(engrams) {
2347
+ return engrams.map((e) => {
2348
+ const clone = { ...e };
2349
+ delete clone.content_hash;
2350
+ return clone;
2351
+ });
2352
+ }
2353
+ };
2354
+
2355
+ // src/migrations/20260406-003-populate-memory-class.ts
2356
+ var TYPE_TO_MEMORY_CLASS = {
2357
+ behavioral: "semantic",
2358
+ terminological: "semantic",
2359
+ procedural: "procedural",
2360
+ architectural: "semantic"
2361
+ };
2362
+ var migration3 = {
2363
+ id: "20260406-003-populate-memory-class",
2364
+ description: "Populate existing knowledge_type.memory_class based on type",
2365
+ up(engrams) {
2366
+ return engrams.map((e) => {
2367
+ const raw = e;
2368
+ if (raw.knowledge_type?.memory_class) return e;
2369
+ const memoryClass = TYPE_TO_MEMORY_CLASS[e.type] ?? "semantic";
2370
+ const cogLevel = raw.knowledge_type?.cognitive_level ?? "remember";
2371
+ return {
2372
+ ...e,
2373
+ knowledge_type: { memory_class: memoryClass, cognitive_level: cogLevel }
2374
+ };
2375
+ });
2376
+ },
2377
+ down(engrams) {
2378
+ return engrams.map((e) => {
2379
+ const raw = e;
2380
+ if (raw.knowledge_type) {
2381
+ const { ...rest } = raw;
2382
+ delete rest.knowledge_type;
2383
+ return rest;
2384
+ }
2385
+ return e;
2386
+ });
2387
+ }
2388
+ };
2389
+
2390
+ // src/migrations/20260406-004-populate-cognitive-level.ts
2391
+ var TYPE_TO_COGNITIVE = {
2392
+ behavioral: "apply",
2393
+ terminological: "remember",
2394
+ procedural: "apply",
2395
+ architectural: "evaluate"
2396
+ };
2397
+ var TYPE_TO_MEMORY_CLASS2 = {
2398
+ behavioral: "semantic",
2399
+ terminological: "semantic",
2400
+ procedural: "procedural",
2401
+ architectural: "semantic"
2402
+ };
2403
+ var migration4 = {
2404
+ id: "20260406-004-populate-cognitive-level",
2405
+ description: "Populate existing knowledge_type.cognitive_level based on type",
2406
+ up(engrams) {
2407
+ return engrams.map((e) => {
2408
+ const clone = { ...e };
2409
+ const cogLevel = TYPE_TO_COGNITIVE[e.type] ?? "remember";
2410
+ const memClass = TYPE_TO_MEMORY_CLASS2[e.type] ?? "semantic";
2411
+ if (!clone.knowledge_type) {
2412
+ clone.knowledge_type = { cognitive_level: cogLevel, memory_class: memClass };
2413
+ } else {
2414
+ if (!clone.knowledge_type.cognitive_level) {
2415
+ clone.knowledge_type = { ...clone.knowledge_type, cognitive_level: cogLevel };
2416
+ }
2417
+ if (!clone.knowledge_type.memory_class) {
2418
+ clone.knowledge_type = { ...clone.knowledge_type, memory_class: memClass };
2419
+ }
2420
+ }
2421
+ return clone;
2422
+ });
2423
+ },
2424
+ down(engrams) {
2425
+ return engrams.map((e) => {
2426
+ const clone = { ...e };
2427
+ delete clone.knowledge_type;
2428
+ return clone;
2429
+ });
2430
+ }
2431
+ };
2432
+
2433
+ // src/migrations/20260406-005-add-version-field.ts
2434
+ var migration5 = {
2435
+ id: "20260406-005-add-version-field",
2436
+ description: "Add engram_version: 1 and episode_ids: [] to all engrams",
2437
+ up(engrams) {
2438
+ return engrams.map((e) => {
2439
+ const raw = e;
2440
+ return {
2441
+ ...e,
2442
+ engram_version: raw.engram_version ?? 1,
2443
+ episode_ids: raw.episode_ids ?? []
2444
+ };
2445
+ });
2446
+ },
2447
+ down(engrams) {
2448
+ return engrams.map((e) => {
2449
+ const raw = e;
2450
+ const { engram_version, episode_ids, previous_version_ref, ...rest } = raw;
2451
+ return rest;
2452
+ });
2453
+ }
2454
+ };
2455
+
2456
+ // src/migrations/runner.ts
2457
+ var ALL_MIGRATIONS = [migration, migration2, migration3, migration4, migration5];
2458
+ var CURRENT_SCHEMA_VERSION = ALL_MIGRATIONS.length;
2459
+ function getSchemaVersion(configPath) {
2460
+ if (!fs3.existsSync(configPath)) return 0;
2461
+ try {
2462
+ const raw = yaml4.load(fs3.readFileSync(configPath, "utf8"));
2463
+ if (!raw || typeof raw.schema_version !== "number") return 0;
2464
+ return raw.schema_version;
2465
+ } catch {
2466
+ return 0;
2467
+ }
2468
+ }
2469
+ function setSchemaVersion(configPath, version) {
2470
+ let configData = {};
2471
+ try {
2472
+ const raw = fs3.readFileSync(configPath, "utf8");
2473
+ if (raw) configData = yaml4.load(raw) ?? {};
2474
+ } catch {
2475
+ }
2476
+ configData.schema_version = version;
2477
+ fs3.writeFileSync(configPath, yaml4.dump(configData, { lineWidth: 120, noRefs: true }));
2478
+ }
2479
+ function createBackup(engramsPath, version) {
2480
+ if (!fs3.existsSync(engramsPath)) return null;
2481
+ const backupPath = `${engramsPath}.bak.${version}`;
2482
+ fs3.copyFileSync(engramsPath, backupPath);
2483
+ return backupPath;
2484
+ }
2485
+ function restoreBackup(engramsPath, backupPath) {
2486
+ fs3.copyFileSync(backupPath, engramsPath);
2487
+ }
2488
+ function runMigrations(engramsPath, configPath, options) {
2489
+ const currentVersion = getSchemaVersion(configPath);
2490
+ const pending = ALL_MIGRATIONS.slice(currentVersion);
2491
+ if (pending.length === 0) {
2492
+ return { applied: [], schema_version: currentVersion, backup_path: null };
2493
+ }
2494
+ const backupPath = options?.dryRun ? null : createBackup(engramsPath, currentVersion);
2495
+ let engrams = loadEngrams(engramsPath);
2496
+ const applied = [];
2497
+ for (const migration6 of pending) {
2498
+ logger.info(`Running migration: ${migration6.id} \u2014 ${migration6.description}`);
2499
+ try {
2500
+ engrams = migration6.up(engrams);
2501
+ applied.push(migration6.id);
2502
+ } catch (err) {
2503
+ logger.error(`Migration ${migration6.id} failed: ${err}`);
2504
+ if (backupPath) {
2505
+ restoreBackup(engramsPath, backupPath);
2506
+ logger.info(`Restored engrams.yaml from backup: ${backupPath}`);
2507
+ }
2508
+ throw new Error(`Migration ${migration6.id} failed: ${err}. Engrams restored from backup.`);
2509
+ }
2510
+ }
2511
+ if (!options?.dryRun) {
2512
+ saveEngrams(engramsPath, engrams);
2513
+ const newVersion = currentVersion + applied.length;
2514
+ setSchemaVersion(configPath, newVersion);
2515
+ }
2516
+ return {
2517
+ applied,
2518
+ schema_version: currentVersion + applied.length,
2519
+ backup_path: backupPath
2520
+ };
2521
+ }
2522
+ function rollbackMigrations(engramsPath, configPath, targetVersion) {
2523
+ const currentVersion = getSchemaVersion(configPath);
2524
+ if (targetVersion >= currentVersion) {
2525
+ return { applied: [], schema_version: currentVersion, backup_path: null };
2526
+ }
2527
+ if (targetVersion < 0) {
2528
+ throw new Error("Target version cannot be negative");
2529
+ }
2530
+ const backupPath = createBackup(engramsPath, currentVersion);
2531
+ let engrams = loadEngrams(engramsPath);
2532
+ const rolledBack = [];
2533
+ const toRollback = ALL_MIGRATIONS.slice(targetVersion, currentVersion).reverse();
2534
+ for (const migration6 of toRollback) {
2535
+ logger.info(`Rolling back migration: ${migration6.id}`);
2536
+ try {
2537
+ engrams = migration6.down(engrams);
2538
+ rolledBack.push(migration6.id);
2539
+ } catch (err) {
2540
+ logger.error(`Rollback of ${migration6.id} failed: ${err}`);
2541
+ if (backupPath) {
2542
+ restoreBackup(engramsPath, backupPath);
2543
+ logger.info(`Restored engrams.yaml from backup: ${backupPath}`);
2544
+ }
2545
+ throw new Error(`Rollback of ${migration6.id} failed: ${err}. Engrams restored from backup.`);
2546
+ }
2547
+ }
2548
+ saveEngrams(engramsPath, engrams);
2549
+ setSchemaVersion(configPath, targetVersion);
2550
+ return {
2551
+ applied: rolledBack,
2552
+ schema_version: targetVersion,
2553
+ backup_path: backupPath
2554
+ };
2555
+ }
2556
+
2557
+ // src/store/yaml-store.ts
2558
+ import { existsSync as existsSync8 } from "fs";
2559
+ import { readFile } from "fs/promises";
2560
+ import * as yaml5 from "js-yaml";
2561
+
2562
+ // src/store/async-fs.ts
2563
+ import { existsSync as existsSync7 } from "fs";
2564
+ import { writeFile, rename, mkdir } from "fs/promises";
2565
+ import { dirname } from "path";
2566
+ async function asyncAtomicWrite(filePath, content) {
2567
+ const dir = dirname(filePath);
2568
+ if (!existsSync7(dir)) await mkdir(dir, { recursive: true });
2569
+ const tmp = filePath + ".tmp";
2570
+ await writeFile(tmp, content);
2571
+ await rename(tmp, filePath);
2572
+ }
2573
+
2574
+ // src/store/async-lock.ts
2575
+ import { writeFile as writeFile2, unlink, stat } from "fs/promises";
2576
+ import { constants } from "fs";
2577
+ function sleep(ms) {
2578
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
2579
+ }
2580
+ async function withAsyncLock(filePath, fn, options) {
2581
+ const lockPath = filePath + ".lock";
2582
+ const maxRetries = options?.maxRetries ?? 5;
2583
+ const baseDelay = options?.baseDelay ?? 100;
2584
+ const staleThreshold = options?.staleThreshold ?? 1e4;
2585
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2586
+ try {
2587
+ await writeFile2(lockPath, `${process.pid}`, { flag: constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL });
2588
+ break;
2589
+ } catch (err) {
2590
+ if (err.code !== "EEXIST") throw err;
2591
+ try {
2592
+ const s = await stat(lockPath);
2593
+ if (Date.now() - s.mtimeMs > staleThreshold) {
2594
+ await unlink(lockPath).catch(() => {
2595
+ });
2596
+ continue;
2597
+ }
2598
+ } catch {
2599
+ continue;
2600
+ }
2601
+ if (attempt === maxRetries) {
2602
+ throw new Error(`Failed to acquire lock on ${filePath} after ${maxRetries} retries`);
2603
+ }
2604
+ const delay = baseDelay * Math.pow(2, attempt);
2605
+ await sleep(delay);
2606
+ }
2607
+ }
2608
+ try {
2609
+ return await fn();
2610
+ } finally {
2611
+ await unlink(lockPath).catch(() => {
2612
+ });
2613
+ }
2614
+ }
2615
+
2616
+ // src/store/yaml-store.ts
2617
+ var YamlStore = class {
2618
+ filePath;
2619
+ constructor(filePath) {
2620
+ this.filePath = filePath;
2621
+ }
2622
+ async load() {
2623
+ if (!existsSync8(this.filePath)) return [];
2624
+ try {
2625
+ const content = await readFile(this.filePath, "utf8");
2626
+ const raw = yaml5.load(content);
2627
+ if (!raw?.engrams || !Array.isArray(raw.engrams)) return [];
2628
+ const valid = [];
2629
+ let skipped = 0;
2630
+ for (const entry of raw.engrams) {
2631
+ const result = EngramSchemaPassthrough.safeParse(entry);
2632
+ if (result.success) valid.push(result.data);
2633
+ else skipped++;
2634
+ }
2635
+ if (skipped > 0) logger.warning(`Skipped ${skipped} invalid engram(s) in ${this.filePath}`);
2636
+ return valid;
2637
+ } catch (err) {
2638
+ logger.error(`Failed to parse engrams file ${this.filePath}: ${err}`);
2639
+ return [];
2640
+ }
2641
+ }
2642
+ async save(engrams) {
2643
+ await withAsyncLock(this.filePath, async () => {
2644
+ const content = yaml5.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
2645
+ await asyncAtomicWrite(this.filePath, content);
2646
+ });
2647
+ }
2648
+ async append(engram) {
2649
+ await withAsyncLock(this.filePath, async () => {
2650
+ const engrams = await this._loadRaw();
2651
+ engrams.push(engram);
2652
+ const content = yaml5.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
2653
+ await asyncAtomicWrite(this.filePath, content);
2654
+ });
2655
+ }
2656
+ async getById(id) {
2657
+ const engrams = await this.load();
2658
+ return engrams.find((e) => e.id === id) ?? null;
2659
+ }
2660
+ async remove(id) {
2661
+ return await withAsyncLock(this.filePath, async () => {
2662
+ const engrams = await this._loadRaw();
2663
+ const idx = engrams.findIndex((e) => e.id === id);
2664
+ if (idx === -1) return false;
2665
+ engrams.splice(idx, 1);
2666
+ const content = yaml5.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
2667
+ await asyncAtomicWrite(this.filePath, content);
2668
+ return true;
2669
+ });
2670
+ }
2671
+ async count(filter) {
2672
+ const engrams = await this.load();
2673
+ if (filter?.status) {
2674
+ return engrams.filter((e) => e.status === filter.status).length;
2675
+ }
2676
+ return engrams.length;
2677
+ }
2678
+ async close() {
2679
+ }
2680
+ /** Raw load without validation — for internal mutate-and-save operations. */
2681
+ async _loadRaw() {
2682
+ if (!existsSync8(this.filePath)) return [];
2683
+ try {
2684
+ const content = await readFile(this.filePath, "utf8");
2685
+ const raw = yaml5.load(content);
2686
+ if (!raw?.engrams || !Array.isArray(raw.engrams)) return [];
2687
+ const valid = [];
2688
+ for (const entry of raw.engrams) {
2689
+ const result = EngramSchemaPassthrough.safeParse(entry);
2690
+ if (result.success) valid.push(result.data);
2691
+ }
2692
+ return valid;
2693
+ } catch {
2694
+ return [];
2695
+ }
2696
+ }
2697
+ };
2698
+
2699
+ // src/store/sqlite-store.ts
2700
+ import { createRequire as createRequire2 } from "module";
2701
+ var require3 = createRequire2(import.meta.url);
2702
+ var Database2 = null;
2703
+ function getDatabase2() {
2704
+ if (!Database2) {
2705
+ try {
2706
+ Database2 = require3("better-sqlite3");
2707
+ } catch {
2708
+ throw new Error(
2709
+ "better-sqlite3 is required for sqlite backend. Install it with: npm install better-sqlite3"
2710
+ );
2711
+ }
2712
+ }
2713
+ return Database2;
2714
+ }
2715
+ var SqliteStore = class {
2716
+ dbPath;
2717
+ db = null;
2718
+ constructor(dbPath) {
2719
+ this.dbPath = dbPath;
2720
+ }
2721
+ getDb() {
2722
+ if (!this.db) {
2723
+ const DB = getDatabase2();
2724
+ this.db = new DB(this.dbPath);
2725
+ this.db.pragma("journal_mode = WAL");
2726
+ this.db.exec(`
2727
+ CREATE TABLE IF NOT EXISTS engrams (
2728
+ id TEXT PRIMARY KEY,
2729
+ status TEXT NOT NULL,
2730
+ scope TEXT NOT NULL,
2731
+ domain TEXT,
2732
+ last_accessed TEXT,
2733
+ data TEXT NOT NULL
2734
+ );
2735
+ CREATE INDEX IF NOT EXISTS idx_status ON engrams(status);
2736
+ CREATE INDEX IF NOT EXISTS idx_scope ON engrams(scope);
2737
+ CREATE INDEX IF NOT EXISTS idx_domain ON engrams(domain);
2738
+ `);
2739
+ }
2740
+ return this.db;
2741
+ }
2742
+ async load() {
2743
+ const db = this.getDb();
2744
+ const rows = db.prepare("SELECT data FROM engrams").all();
2745
+ return rows.map((r) => JSON.parse(r.data));
2746
+ }
2747
+ async save(engrams) {
2748
+ const db = this.getDb();
2749
+ const upsert = db.prepare(`
2750
+ INSERT OR REPLACE INTO engrams (id, status, scope, domain, last_accessed, data)
2751
+ VALUES (?, ?, ?, ?, ?, ?)
2752
+ `);
2753
+ const tx = db.transaction(() => {
2754
+ db.exec("DELETE FROM engrams");
2755
+ for (const e of engrams) {
2756
+ upsert.run(e.id, e.status, e.scope, e.domain ?? null, e.activation.last_accessed, JSON.stringify(e));
2757
+ }
2758
+ });
2759
+ tx();
2760
+ }
2761
+ async append(engram) {
2762
+ const db = this.getDb();
2763
+ db.prepare(`
2764
+ INSERT OR REPLACE INTO engrams (id, status, scope, domain, last_accessed, data)
2765
+ VALUES (?, ?, ?, ?, ?, ?)
2766
+ `).run(engram.id, engram.status, engram.scope, engram.domain ?? null, engram.activation.last_accessed, JSON.stringify(engram));
2767
+ }
2768
+ async getById(id) {
2769
+ const db = this.getDb();
2770
+ const row = db.prepare("SELECT data FROM engrams WHERE id = ?").get(id);
2771
+ if (!row) return null;
2772
+ return JSON.parse(row.data);
2773
+ }
2774
+ async remove(id) {
2775
+ const db = this.getDb();
2776
+ const result = db.prepare("DELETE FROM engrams WHERE id = ?").run(id);
2777
+ return result.changes > 0;
2778
+ }
2779
+ async count(filter) {
2780
+ const db = this.getDb();
2781
+ if (filter?.status) {
2782
+ return db.prepare("SELECT COUNT(*) as c FROM engrams WHERE status = ?").get(filter.status).c;
2783
+ }
2784
+ return db.prepare("SELECT COUNT(*) as c FROM engrams").get().c;
2785
+ }
2786
+ async close() {
2787
+ if (this.db) {
2788
+ this.db.close();
2789
+ this.db = null;
2790
+ }
2791
+ }
2792
+ /** Import engrams from an array (e.g., from YAML migration). */
2793
+ async importFrom(engrams) {
2794
+ await this.save(engrams);
2795
+ }
2796
+ /** Export all engrams as an array (e.g., for YAML migration). */
2797
+ async exportAll() {
2798
+ return this.load();
2799
+ }
2800
+ };
2801
+
2802
+ // src/store/factory.ts
2803
+ import { join as join4 } from "path";
2804
+ function createStore(config) {
2805
+ switch (config.backend) {
2806
+ case "sqlite":
2807
+ return new SqliteStore(join4(config.path, "engrams.db"));
2808
+ case "yaml":
2809
+ default:
2810
+ return new YamlStore(join4(config.path, "engrams.yaml"));
2811
+ }
2812
+ }
2813
+ async function migrateStore(from, to) {
2814
+ const engrams = await from.load();
2815
+ await to.save(engrams);
2816
+ return engrams.length;
2817
+ }
2818
+
2226
2819
  // src/version-check.ts
2227
2820
  var cache = /* @__PURE__ */ new Map();
2228
2821
  async function checkForUpdate(packageName, currentVersion, onResult) {
@@ -2275,6 +2868,18 @@ function isNewer(a, b) {
2275
2868
  }
2276
2869
 
2277
2870
  // src/index.ts
2871
+ var COMMITMENT_MULTIPLIER = {
2872
+ locked: 1,
2873
+ decided: 0.9,
2874
+ leaning: 0.7,
2875
+ exploring: 0.5
2876
+ };
2877
+ var TYPE_TO_COGNITIVE2 = {
2878
+ behavioral: "apply",
2879
+ terminological: "remember",
2880
+ procedural: "apply",
2881
+ architectural: "evaluate"
2882
+ };
2278
2883
  var INGEST_PATTERNS = [
2279
2884
  { re: /(?:we decided|the decision is|agreed to)\s+(.+?)\.?$/gim, type: "architectural" },
2280
2885
  { re: /(?:always|never|must|should)\s+(.+?)\.?$/gim, type: "behavioral" },
@@ -2287,6 +2892,8 @@ var Plur = class {
2287
2892
  config;
2288
2893
  indexedStorage = null;
2289
2894
  _engramCache = /* @__PURE__ */ new Map();
2895
+ _llmFailureCount = 0;
2896
+ _llmDisabledUntil = null;
2290
2897
  constructor(options) {
2291
2898
  this.paths = detectPlurStorage(options?.path);
2292
2899
  this.config = loadConfig(this.paths.config);
@@ -2334,17 +2941,17 @@ var Plur = class {
2334
2941
  return all;
2335
2942
  }
2336
2943
  /** Load engrams from a path with mtime-based caching */
2337
- _loadCached(path2) {
2944
+ _loadCached(path3) {
2338
2945
  let mtime = 0;
2339
2946
  try {
2340
- mtime = fs3.statSync(path2).mtimeMs;
2947
+ mtime = fs4.statSync(path3).mtimeMs;
2341
2948
  } catch {
2342
2949
  return [];
2343
2950
  }
2344
- const cached = this._engramCache.get(path2);
2951
+ const cached = this._engramCache.get(path3);
2345
2952
  if (cached && cached.mtime === mtime) return cached.engrams;
2346
- const engrams = loadEngrams(path2);
2347
- this._engramCache.set(path2, { mtime, engrams });
2953
+ const engrams = loadEngrams(path3);
2954
+ this._engramCache.set(path3, { mtime, engrams });
2348
2955
  return engrams;
2349
2956
  }
2350
2957
  /** Find which store owns an engram by ID. For namespaced IDs, strips prefix to find in store. */
@@ -2367,7 +2974,35 @@ var Plur = class {
2367
2974
  }
2368
2975
  return null;
2369
2976
  }
2370
- /** Create engram, detect conflicts, save. Returns the created engram. */
2977
+ /** Content hash fast-path dedup. */
2978
+ _hashDedup(statement, engrams) {
2979
+ const hash = computeContentHash(statement);
2980
+ for (const e of engrams) {
2981
+ if (e.status === "active" && e.content_hash === hash) return e;
2982
+ }
2983
+ return null;
2984
+ }
2985
+ _isLlmDedupAvailable() {
2986
+ if (this._llmDisabledUntil !== null) {
2987
+ if (Date.now() < this._llmDisabledUntil) return false;
2988
+ this._llmDisabledUntil = null;
2989
+ this._llmFailureCount = 0;
2990
+ }
2991
+ return true;
2992
+ }
2993
+ _recordLlmFailure() {
2994
+ this._llmFailureCount++;
2995
+ if (this._llmFailureCount >= 3) {
2996
+ this._llmDisabledUntil = Date.now() + 60 * 60 * 1e3;
2997
+ logger.warning("LLM dedup circuit breaker tripped \u2014 disabled for 1 hour");
2998
+ }
2999
+ }
3000
+ _recordLlmSuccess() {
3001
+ this._llmFailureCount = 0;
3002
+ }
3003
+ /** Create engram with content hash + commitment + cognitive level.
3004
+ * Fast-path hash dedup returns existing on exact match.
3005
+ */
2371
3006
  learn(statement, context) {
2372
3007
  if (!this.config.allow_secrets) {
2373
3008
  const secrets = detectSecrets(statement);
@@ -2378,17 +3013,34 @@ var Plur = class {
2378
3013
  return withLock(this.paths.engrams, () => {
2379
3014
  const engrams = loadEngrams(this.paths.engrams);
2380
3015
  const allEngrams = this._loadAllEngrams();
3016
+ const hashMatch = this._hashDedup(statement, allEngrams);
3017
+ if (hashMatch) return hashMatch;
2381
3018
  const id = generateEngramId(allEngrams);
2382
3019
  const scope = context?.scope ?? "global";
2383
3020
  const now = (/* @__PURE__ */ new Date()).toISOString();
3021
+ const type = context?.type ?? "behavioral";
3022
+ const cogLevel = TYPE_TO_COGNITIVE2[type] ?? "remember";
3023
+ const commitment = context?.commitment ?? "leaning";
2384
3024
  const conflictingEngrams = detectConflicts({ statement, scope }, allEngrams);
2385
3025
  const conflictIds = conflictingEngrams.map((e) => e.id);
3026
+ const TYPE_TO_MEMORY_CLASS3 = {
3027
+ behavioral: "semantic",
3028
+ terminological: "semantic",
3029
+ procedural: "procedural",
3030
+ architectural: "semantic"
3031
+ };
3032
+ const engramType = context?.type ?? "behavioral";
3033
+ const memoryClass = context?.memory_class ?? TYPE_TO_MEMORY_CLASS3[engramType] ?? "semantic";
3034
+ const episodeIds = [];
3035
+ if (context?.session_episode_id) {
3036
+ episodeIds.push(context.session_episode_id);
3037
+ }
2386
3038
  const engram = {
2387
3039
  id,
2388
3040
  version: 2,
2389
3041
  status: "active",
2390
3042
  consolidated: false,
2391
- type: context?.type ?? "behavioral",
3043
+ type,
2392
3044
  scope,
2393
3045
  visibility: context?.visibility ?? (context?.domain ? "public" : "private"),
2394
3046
  statement,
@@ -2402,6 +3054,7 @@ var Plur = class {
2402
3054
  last_accessed: now.slice(0, 10)
2403
3055
  },
2404
3056
  feedback_signals: { positive: 0, negative: 0, neutral: 0 },
3057
+ knowledge_type: { memory_class: memoryClass, cognitive_level: cogLevel },
2405
3058
  knowledge_anchors: (context?.knowledge_anchors ?? []).map((a) => ({
2406
3059
  path: a.path,
2407
3060
  relevance: a.relevance ?? "supporting",
@@ -2415,6 +3068,13 @@ var Plur = class {
2415
3068
  derived_from: context?.derived_from ?? null,
2416
3069
  dual_coding: context?.dual_coding,
2417
3070
  polarity: null,
3071
+ content_hash: computeContentHash(statement),
3072
+ commitment,
3073
+ locked_at: commitment === "locked" ? now : void 0,
3074
+ locked_reason: commitment === "locked" ? context?.locked_reason : void 0,
3075
+ summary: autoSummary(statement, void 0),
3076
+ engram_version: 1,
3077
+ episode_ids: episodeIds ?? [],
2418
3078
  relations: conflictIds.length > 0 ? {
2419
3079
  broader: [],
2420
3080
  narrower: [],
@@ -2425,9 +3085,42 @@ var Plur = class {
2425
3085
  engrams.push(engram);
2426
3086
  saveEngrams(this.paths.engrams, engrams);
2427
3087
  this._syncIndex();
3088
+ appendHistory(this.paths.root, {
3089
+ event: "engram_created",
3090
+ engram_id: engram.id,
3091
+ timestamp: now,
3092
+ data: { type: engram.type, scope: engram.scope, source: engram.source }
3093
+ });
2428
3094
  return engram;
2429
3095
  });
2430
3096
  }
3097
+ /** Build deps for learn-async module. */
3098
+ _learnAsyncDeps() {
3099
+ return {
3100
+ hashDedup: (statement) => this._hashDedup(statement, this._loadAllEngrams()),
3101
+ recallHybrid: (query, options) => this.recallHybrid(query, options),
3102
+ recall: (query, options) => this.recall(query, options),
3103
+ learn: (statement, context) => this.learn(statement, context),
3104
+ getById: (id) => this.getById(id),
3105
+ engramsPath: this.paths.engrams,
3106
+ rootPath: this.paths.root,
3107
+ dedupConfig: this.config.dedup ?? {},
3108
+ isLlmAvailable: () => this._isLlmDedupAvailable(),
3109
+ recordLlmSuccess: () => this._recordLlmSuccess(),
3110
+ recordLlmFailure: () => this._recordLlmFailure(),
3111
+ syncIndex: () => this._syncIndex()
3112
+ };
3113
+ }
3114
+ /** Async learn with LLM-driven deduplication (Ideas 1+2+19). */
3115
+ async learnAsync(statement, context) {
3116
+ const { learnAsync: learnAsyncImpl } = await import("./learn-async-VXBH3TYE.js");
3117
+ return learnAsyncImpl(this._learnAsyncDeps(), statement, context);
3118
+ }
3119
+ /** Batch learn with LLM dedup. */
3120
+ async learnBatch(statements, llm) {
3121
+ const { learnBatch: learnBatchImpl } = await import("./learn-async-VXBH3TYE.js");
3122
+ return learnBatchImpl(this._learnAsyncDeps(), statements, llm);
3123
+ }
2431
3124
  /**
2432
3125
  * Search engrams, filter by scope/domain/strength, reactivate accessed.
2433
3126
  * Supports two modes:
@@ -2474,6 +3167,13 @@ var Plur = class {
2474
3167
  this._reactivateResults(results);
2475
3168
  return results;
2476
3169
  }
3170
+ async recallAutoSearch(query, options) {
3171
+ const filtered = this._filterEngrams(options);
3172
+ const limit = options?.limit ?? 20;
3173
+ const result = await recallAuto(filtered, query, limit, this.paths.root, options?.llm);
3174
+ this._reactivateResults(result.results);
3175
+ return result;
3176
+ }
2477
3177
  /** Get a single engram by ID, regardless of status. Searches primary + all stores. */
2478
3178
  getById(id) {
2479
3179
  const engrams = this._loadAllEngrams();
@@ -2610,13 +3310,9 @@ var Plur = class {
2610
3310
  },
2611
3311
  embeddingBoosts
2612
3312
  );
2613
- const formatEngrams = (wires) => {
2614
- if (wires.length === 0) return "";
2615
- return wires.map((e) => `[${e.id}] ${e.statement}`).join("\n");
2616
- };
2617
- const directivesStr = formatEngrams(result.directives);
2618
- const constraintsStr = formatEngrams(result.constraints);
2619
- const considerStr = formatEngrams(result.consider);
3313
+ const directivesStr = formatWithLayer(result.directives, assignLayer("directives"));
3314
+ const constraintsStr = formatWithLayer(result.constraints, assignLayer("constraints"));
3315
+ const considerStr = formatWithLayer(result.consider, assignLayer("consider"));
2620
3316
  const count = result.directives.length + result.constraints.length + result.consider.length;
2621
3317
  const tokensUsed = result.tokens_used.directives + result.tokens_used.consider;
2622
3318
  const injected_ids = [
@@ -2645,11 +3341,20 @@ var Plur = class {
2645
3341
  engram.feedback_signals[signal] += 1;
2646
3342
  if (signal === "positive") {
2647
3343
  engram.activation.retrieval_strength = Math.min(1, engram.activation.retrieval_strength + 0.05);
3344
+ const e = engram;
3345
+ if (e.commitment === "exploring") e.commitment = "leaning";
3346
+ else if (e.commitment === "leaning") e.commitment = "decided";
2648
3347
  } else if (signal === "negative") {
2649
3348
  engram.activation.retrieval_strength = Math.max(0, engram.activation.retrieval_strength - 0.1);
2650
3349
  }
2651
3350
  saveEngrams(this.paths.engrams, engrams);
2652
3351
  this._syncIndex();
3352
+ appendHistory(this.paths.root, {
3353
+ event: "feedback_received",
3354
+ engram_id: id,
3355
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3356
+ data: { signal }
3357
+ });
2653
3358
  return true;
2654
3359
  });
2655
3360
  if (found) return;
@@ -2724,6 +3429,12 @@ var Plur = class {
2724
3429
  }
2725
3430
  saveEngrams(this.paths.engrams, engrams);
2726
3431
  this._syncIndex();
3432
+ appendHistory(this.paths.root, {
3433
+ event: "engram_retired",
3434
+ engram_id: id,
3435
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3436
+ data: { reason: reason ?? null }
3437
+ });
2727
3438
  return true;
2728
3439
  });
2729
3440
  if (foundInPrimary) return;
@@ -2775,12 +3486,12 @@ var Plur = class {
2775
3486
  }
2776
3487
  /** Search packs for an engram by ID and apply feedback, writing back to the pack's engrams.yaml. */
2777
3488
  _feedbackPack(id, signal) {
2778
- if (!fs3.existsSync(this.paths.packs)) throw new Error(`Engram not found: ${id}`);
2779
- for (const entry of fs3.readdirSync(this.paths.packs)) {
3489
+ if (!fs4.existsSync(this.paths.packs)) throw new Error(`Engram not found: ${id}`);
3490
+ for (const entry of fs4.readdirSync(this.paths.packs)) {
2780
3491
  const packDir = `${this.paths.packs}/${entry}`;
2781
- if (!fs3.statSync(packDir).isDirectory()) continue;
3492
+ if (!fs4.statSync(packDir).isDirectory()) continue;
2782
3493
  const engramsPath = `${packDir}/engrams.yaml`;
2783
- if (!fs3.existsSync(engramsPath)) continue;
3494
+ if (!fs4.existsSync(engramsPath)) continue;
2784
3495
  const engrams = loadEngrams(engramsPath);
2785
3496
  const engram = engrams.find((e) => e.id === id);
2786
3497
  if (!engram) continue;
@@ -2859,6 +3570,12 @@ var Plur = class {
2859
3570
  listPacks() {
2860
3571
  return listPacks(this.paths.packs);
2861
3572
  }
3573
+ // SP5 methods (deferred — vault-export, registry not yet merged)
3574
+ // exportToVault, discoverPacks, getRegistryUrl will be added when SP5 merges
3575
+ /** Get the PLUR storage root path. */
3576
+ getStorageRoot() {
3577
+ return this.paths.root;
3578
+ }
2862
3579
  /** Sync engrams to git. Initializes repo on first call, commits + push/pull on subsequent calls. */
2863
3580
  sync(remote) {
2864
3581
  return sync(this.paths.root, remote);
@@ -2867,17 +3584,158 @@ var Plur = class {
2867
3584
  syncStatus() {
2868
3585
  return getSyncStatus(this.paths.root);
2869
3586
  }
3587
+ /**
3588
+ * Promote an episode to an episodic engram (SP2 Idea 3).
3589
+ * Creates a new engram with memory_class='episodic' from an episode's summary.
3590
+ */
3591
+ episodeToEngram(episodeId, context) {
3592
+ const episodes = queryTimeline(this.paths.episodes);
3593
+ const episode = episodes.find((e) => e.id === episodeId);
3594
+ if (!episode) throw new Error(`Episode not found: ${episodeId}`);
3595
+ const engram = this.learn(episode.summary, {
3596
+ ...context,
3597
+ type: context?.type ?? "behavioral",
3598
+ source: context?.source ?? `episode:${episodeId}`,
3599
+ memory_class: "episodic",
3600
+ session_episode_id: episodeId
3601
+ });
3602
+ appendHistory(this.paths.root, {
3603
+ event: "engram_promoted",
3604
+ engram_id: engram.id,
3605
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3606
+ data: { from_episode: episodeId }
3607
+ });
3608
+ return engram;
3609
+ }
3610
+ /**
3611
+ * Get history events for a specific engram (SP2 Idea 7).
3612
+ * Returns all events across all months for the given engram ID.
3613
+ */
3614
+ getEngramHistory(engramId) {
3615
+ return readHistoryForEngram(this.paths.root, engramId);
3616
+ }
3617
+ /**
3618
+ * Report a failure for a procedural engram (SP2 Idea 18).
3619
+ * If LLM is provided, generates an improved procedure and updates the engram.
3620
+ * Without LLM, logs the failure without rewriting.
3621
+ * Returns the updated engram and the failure episode.
3622
+ */
3623
+ async reportFailure(engramId, failureContext, llm) {
3624
+ const engram = this.getById(engramId);
3625
+ if (!engram) throw new Error(`Engram not found: ${engramId}`);
3626
+ const memClass = engram.knowledge_type?.memory_class;
3627
+ if (memClass !== "procedural" && engram.type !== "procedural") {
3628
+ throw new Error(`Only procedural engrams can evolve. This engram has type=${engram.type}, memory_class=${memClass}`);
3629
+ }
3630
+ const history = readHistoryForEngram(this.paths.root, engramId);
3631
+ const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
3632
+ const recentEvolutions = history.filter(
3633
+ (e) => e.event === "procedure_evolved" && e.timestamp > dayAgo
3634
+ );
3635
+ if (recentEvolutions.length >= 3) {
3636
+ throw new Error(`Rate limit: engram ${engramId} has been evolved ${recentEvolutions.length} times in the last 24h (max 3)`);
3637
+ }
3638
+ const episode = this.capture(`Failure report for ${engramId}: ${failureContext}`, {
3639
+ tags: ["failure", "procedure-evolution"]
3640
+ });
3641
+ const failureEventId = generateEventId();
3642
+ appendHistory(this.paths.root, {
3643
+ event: "failure_reported",
3644
+ engram_id: engramId,
3645
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3646
+ data: { failure_context: failureContext, episode_id: episode.id, event_id: failureEventId }
3647
+ });
3648
+ let evolved = false;
3649
+ if (llm) {
3650
+ try {
3651
+ const prompt = `You are improving a procedural memory based on a failure report.
3652
+
3653
+ Current procedure: "${engram.statement}"
3654
+ Failure report: "${failureContext}"
3655
+ ${recentEvolutions.length > 0 ? `
3656
+ Previous revisions in last 24h: ${recentEvolutions.length}` : ""}
3657
+
3658
+ Generate an improved version of the procedure that prevents this failure. Return ONLY the improved procedure statement, nothing else.`;
3659
+ const improved = await llm(prompt);
3660
+ if (improved && improved.trim().length > 0) {
3661
+ const eventId = generateEventId();
3662
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3663
+ return withLock(this.paths.engrams, () => {
3664
+ const engrams = loadEngrams(this.paths.engrams);
3665
+ const idx = engrams.findIndex((e) => e.id === engramId);
3666
+ if (idx === -1) throw new Error(`Engram not found in store: ${engramId}`);
3667
+ const raw = engrams[idx];
3668
+ const oldStatement = raw.statement;
3669
+ const oldVersion = raw.engram_version ?? 1;
3670
+ raw.statement = improved.trim();
3671
+ raw.engram_version = oldVersion + 1;
3672
+ raw.previous_version_ref = { event_id: eventId, changed_at: now };
3673
+ if (!raw.episode_ids) raw.episode_ids = [];
3674
+ raw.episode_ids.push(episode.id);
3675
+ saveEngrams(this.paths.engrams, engrams);
3676
+ this._syncIndex();
3677
+ appendHistory(this.paths.root, {
3678
+ event: "procedure_evolved",
3679
+ engram_id: engramId,
3680
+ timestamp: now,
3681
+ data: {
3682
+ event_id: eventId,
3683
+ old_statement: oldStatement,
3684
+ new_statement: improved.trim(),
3685
+ old_version: oldVersion,
3686
+ new_version: oldVersion + 1,
3687
+ failure_context: failureContext,
3688
+ failure_episode_id: episode.id
3689
+ }
3690
+ });
3691
+ evolved = true;
3692
+ return { engram: engrams[idx], episode, evolved };
3693
+ });
3694
+ }
3695
+ } catch {
3696
+ }
3697
+ }
3698
+ withLock(this.paths.engrams, () => {
3699
+ const engrams = loadEngrams(this.paths.engrams);
3700
+ const idx = engrams.findIndex((e) => e.id === engramId);
3701
+ if (idx !== -1) {
3702
+ const raw = engrams[idx];
3703
+ if (!raw.episode_ids) raw.episode_ids = [];
3704
+ raw.episode_ids.push(episode.id);
3705
+ saveEngrams(this.paths.engrams, engrams);
3706
+ this._syncIndex();
3707
+ }
3708
+ });
3709
+ const updated = this.getById(engramId);
3710
+ return { engram: updated ?? engram, episode, evolved };
3711
+ }
2870
3712
  /** Return system health info. */
2871
3713
  status() {
2872
3714
  const engrams = this._loadAllEngrams();
2873
3715
  const episodes = queryTimeline(this.paths.episodes);
2874
3716
  const packs = listPacks(this.paths.packs);
3717
+ const active = engrams.filter((e) => e.status !== "retired");
3718
+ const lockedCount = active.filter((e) => e.commitment === "locked").length;
3719
+ const tensionPairs = /* @__PURE__ */ new Set();
3720
+ for (const e of active) {
3721
+ if (!e.relations?.conflicts?.length) continue;
3722
+ for (const cid of e.relations.conflicts) {
3723
+ tensionPairs.add([e.id, cid].sort().join(":"));
3724
+ }
3725
+ }
3726
+ const versionedCount = engrams.filter((e) => {
3727
+ const raw = e;
3728
+ return (raw.engram_version ?? 1) > 1;
3729
+ }).length;
2875
3730
  return {
2876
- engram_count: engrams.filter((e) => e.status !== "retired").length,
3731
+ engram_count: active.length,
2877
3732
  episode_count: episodes.length,
2878
3733
  pack_count: packs.length,
2879
3734
  storage_root: this.paths.root,
2880
- config: this.config
3735
+ config: this.config,
3736
+ locked_count: lockedCount,
3737
+ tension_count: tensionPairs.size,
3738
+ versioned_engram_count: versionedCount
2881
3739
  };
2882
3740
  }
2883
3741
  /** Register an additional engram store. */
@@ -2893,12 +3751,12 @@ var Plur = class {
2893
3751
  }];
2894
3752
  let configData = {};
2895
3753
  try {
2896
- const raw = fs3.readFileSync(this.paths.config, "utf8");
2897
- if (raw) configData = yaml5.load(raw) ?? {};
3754
+ const raw = fs4.readFileSync(this.paths.config, "utf8");
3755
+ if (raw) configData = yaml6.load(raw) ?? {};
2898
3756
  } catch {
2899
3757
  }
2900
3758
  configData.stores = stores;
2901
- fs3.writeFileSync(this.paths.config, yaml5.dump(configData, { lineWidth: 120, noRefs: true }));
3759
+ fs4.writeFileSync(this.paths.config, yaml6.dump(configData, { lineWidth: 120, noRefs: true }));
2902
3760
  this.config = loadConfig(this.paths.config);
2903
3761
  }
2904
3762
  /** List all configured stores. */
@@ -2923,6 +3781,9 @@ var Plur = class {
2923
3781
  }
2924
3782
  };
2925
3783
  export {
3784
+ ALL_MIGRATIONS,
3785
+ COMMITMENT_MULTIPLIER,
3786
+ CURRENT_SCHEMA_VERSION,
2926
3787
  DomainCoverageSchema,
2927
3788
  EvidenceEntrySchema,
2928
3789
  FalsificationSchema,
@@ -2933,25 +3794,64 @@ export {
2933
3794
  PLATITUDE_PATTERNS,
2934
3795
  Plur,
2935
3796
  SessionBreadcrumbs,
3797
+ SqliteStore,
2936
3798
  StructuralTemplateSchema,
3799
+ YamlStore,
2937
3800
  alignCluster,
2938
3801
  analyzeStructure,
3802
+ appendHistory,
3803
+ assignLayer,
3804
+ asyncAtomicWrite,
3805
+ autoSummary,
3806
+ buildBatchDedupPrompt,
3807
+ buildDedupPrompt,
2939
3808
  checkForUpdate,
2940
3809
  classifyPolarity,
2941
3810
  clearVersionCache,
2942
3811
  clusterByStructure,
2943
3812
  computeConfidence,
3813
+ computeContentHash,
2944
3814
  computeMetaConfidence,
2945
3815
  confidenceBand,
3816
+ createStore,
2946
3817
  detectPlurStorage,
2947
3818
  detectSecrets,
2948
3819
  engramSearchText,
2949
3820
  extractMetaEngrams,
3821
+ formatLayer1,
3822
+ formatLayer2,
3823
+ formatLayer3,
3824
+ formatWithLayer,
2950
3825
  formulateMetaEngram,
3826
+ freshTailBoost,
3827
+ generateEventId,
2951
3828
  generateGuardrails,
3829
+ generateProfile,
3830
+ generateSummary,
2952
3831
  getCachedUpdateCheck,
3832
+ getProfileForInjection,
3833
+ getSchemaVersion,
2953
3834
  isPlatitude,
3835
+ listHistoryMonths,
3836
+ loadProfileCache,
3837
+ markProfileDirty,
3838
+ migrateStore,
3839
+ needsSummary,
3840
+ normalizeStatement,
2954
3841
  organizeHierarchy,
3842
+ parseDedupResponse,
3843
+ profileNeedsRegeneration,
3844
+ readHistory,
3845
+ readHistoryForEngram,
3846
+ recallAuto,
3847
+ resolveOperationTier,
3848
+ rollbackMigrations,
3849
+ runMigrations,
3850
+ saveProfileCache,
3851
+ selectModel,
3852
+ selectModelForOperation,
3853
+ setSchemaVersion,
2955
3854
  tokenSimilarity,
2956
- validateMetaEngram
3855
+ validateMetaEngram,
3856
+ withAsyncLock
2957
3857
  };