@plur-ai/core 0.8.2 → 0.9.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  atomicWrite
3
- } from "./chunk-MY4XVDCE.js";
3
+ } from "./chunk-PRK3B7WR.js";
4
4
 
5
5
  // src/logger.ts
6
6
  var level = process.env.PLUR_LOG_LEVEL || "warning";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  atomicWrite
3
- } from "./chunk-MY4XVDCE.js";
3
+ } from "./chunk-PRK3B7WR.js";
4
4
 
5
5
  // src/fts.ts
6
6
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -183,6 +183,38 @@ async function embeddingSearch(engrams, query, limit, storagePath) {
183
183
  similarities.sort((a, b) => b.score - a.score);
184
184
  return similarities.slice(0, limit).map((s) => s.engram);
185
185
  }
186
+ async function embeddingSearchWithScores(engrams, query, limit, storagePath) {
187
+ if (engrams.length === 0) return [];
188
+ const cachePath = storagePath ? join(storagePath, ".embeddings-cache.json") : ".embeddings-cache.json";
189
+ const cache = loadCache(cachePath);
190
+ const queryEmbedding = await embed(query);
191
+ if (!queryEmbedding) {
192
+ return [];
193
+ }
194
+ const similarities = [];
195
+ for (const engram of engrams) {
196
+ const searchText = engramSearchText(engram);
197
+ const hash = hashStatement(searchText);
198
+ let engramEmbedding;
199
+ if (cache[engram.id]?.hash === hash) {
200
+ engramEmbedding = new Float32Array(cache[engram.id].embedding);
201
+ } else {
202
+ const emb = await embed(searchText);
203
+ if (!emb) return [];
204
+ engramEmbedding = emb;
205
+ cache[engram.id] = {
206
+ hash,
207
+ embedding: Array.from(engramEmbedding)
208
+ };
209
+ }
210
+ const rawScore = cosineSimilarity(queryEmbedding, engramEmbedding);
211
+ const score = Math.max(0, Math.min(1, rawScore));
212
+ similarities.push({ engram, score });
213
+ }
214
+ saveCache(cachePath, cache);
215
+ similarities.sort((a, b) => b.score - a.score);
216
+ return similarities.slice(0, limit);
217
+ }
186
218
 
187
219
  export {
188
220
  ftsTokenize,
@@ -192,5 +224,6 @@ export {
192
224
  searchEngrams,
193
225
  embed,
194
226
  cosineSimilarity,
195
- embeddingSearch
227
+ embeddingSearch,
228
+ embeddingSearchWithScores
196
229
  };
@@ -80,10 +80,11 @@ function hasConflictMarkers(root) {
80
80
  return result !== null && result.length > 0;
81
81
  }
82
82
  function pullRebase(root) {
83
- const result = gitSafe(["pull", "--rebase", "origin", "main"], root);
83
+ const branch = gitSafe(["rev-parse", "--abbrev-ref", "HEAD"], root) || "main";
84
+ const result = gitSafe(["pull", "--rebase", "origin", branch], root);
84
85
  if (result !== null) return true;
85
86
  gitSafe(["rebase", "--abort"], root);
86
- const mergeResult = gitSafe(["pull", "origin", "main", "--no-edit"], root);
87
+ const mergeResult = gitSafe(["pull", "origin", branch, "--no-edit"], root);
87
88
  if (mergeResult !== null) return true;
88
89
  if (hasConflictMarkers(root)) {
89
90
  gitSafe(["merge", "--abort"], root);
@@ -0,0 +1,14 @@
1
+ import {
2
+ cosineSimilarity,
3
+ embed,
4
+ embeddingSearch,
5
+ embeddingSearchWithScores
6
+ } from "./chunk-MW5EPL7G.js";
7
+ import "./chunk-PRK3B7WR.js";
8
+ import "./chunk-2ZDO52B4.js";
9
+ export {
10
+ cosineSimilarity,
11
+ embed,
12
+ embeddingSearch,
13
+ embeddingSearchWithScores
14
+ };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  interface HistoryEvent {
4
- event: 'engram_created' | 'engram_updated' | 'engram_merged' | 'feedback_received' | 'engram_retired' | 'engram_promoted' | 'failure_reported' | 'procedure_evolved';
4
+ event: 'engram_created' | 'engram_updated' | 'engram_merged' | 'feedback_received' | 'engram_retired' | 'engram_promoted' | 'failure_reported' | 'procedure_evolved' | 'recurrence_detected' | 'contradiction_detected' | 'scope_promoted' | 'buffer_pruned' | 'weekly_review';
5
5
  engram_id: string;
6
6
  timestamp: string;
7
7
  data: Record<string, unknown>;
@@ -536,6 +536,43 @@ type KnowledgeAnchor = z.infer<typeof KnowledgeAnchorSchema>;
536
536
  type Association = z.infer<typeof AssociationSchema>;
537
537
  type PreviousVersionRef = z.infer<typeof PreviousVersionRefSchema>;
538
538
 
539
+ /** Map retrieval strength to a human-readable status label. */
540
+ declare function strengthToStatus(strength: number): string;
541
+ interface DecayTransition {
542
+ engram_id: string;
543
+ old_strength: number;
544
+ new_strength: number;
545
+ old_status: string;
546
+ new_status: string;
547
+ }
548
+ interface BatchDecayResult {
549
+ total: number;
550
+ decayed: number;
551
+ skipped: number;
552
+ transitions: DecayTransition[];
553
+ }
554
+ interface BatchDecayOptions {
555
+ contextScope?: string;
556
+ lambda?: number;
557
+ now?: Date;
558
+ }
559
+ /**
560
+ * Apply ACT-R decay to a batch of engrams.
561
+ * Scope-matched engrams are skipped (they never decay).
562
+ * Status transitions are logged to history.
563
+ * Returns the result summary and the list of engrams that were modified.
564
+ */
565
+ declare function applyBatchDecay(engrams: Engram[], historyRoot: string, options?: BatchDecayOptions): {
566
+ result: BatchDecayResult;
567
+ modified: Engram[];
568
+ };
569
+
570
+ /** Result with cosine similarity score attached. */
571
+ interface SimilarityResult {
572
+ engram: Engram;
573
+ score: number;
574
+ }
575
+
539
576
  declare const EpisodeSchema: z.ZodObject<{
540
577
  id: z.ZodString;
541
578
  summary: z.ZodString;
@@ -1850,6 +1887,18 @@ declare class Plur {
1850
1887
  private _loadAllEngrams;
1851
1888
  /** Load engrams from a path with mtime-based caching */
1852
1889
  private _loadCached;
1890
+ /**
1891
+ * Write engrams to disk and invalidate the cache for that path.
1892
+ *
1893
+ * Why: `_loadCached` uses mtime-based invalidation, but on CI tmpfs
1894
+ * (ubuntu-latest runners) mtime resolution can be coarse enough that a
1895
+ * stat() taken before and after a write returns the same mtime. When that
1896
+ * happens the cache serves a pre-write snapshot and a subsequent `getById`
1897
+ * returns `undefined` for an engram that `learn()` just created. Explicit
1898
+ * invalidation on write removes the filesystem as a source of cache
1899
+ * freshness and closes the race. See issue #25.
1900
+ */
1901
+ private _writeEngrams;
1853
1902
  /** Find which store owns an engram by ID. For namespaced IDs, strips prefix to find in store. */
1854
1903
  private _findEngramStore;
1855
1904
  /** Content hash fast-path dedup. */
@@ -1886,6 +1935,12 @@ declare class Plur {
1886
1935
  recallSemantic(query: string, options?: Omit<RecallOptions, 'mode' | 'llm'>): Promise<Engram[]>;
1887
1936
  /** Hybrid search: BM25 + embeddings merged via Reciprocal Rank Fusion. Async, no API calls. */
1888
1937
  recallHybrid(query: string, options?: Omit<RecallOptions, 'mode' | 'llm'>): Promise<Engram[]>;
1938
+ /** Embedding search returning {engram, score}[] with cosine similarity scores. Async, no API calls. */
1939
+ similaritySearch(query: string, options?: {
1940
+ limit?: number;
1941
+ scope?: string;
1942
+ domain?: string;
1943
+ }): Promise<SimilarityResult[]>;
1889
1944
  /** Expanded search: LLM query expansion + hybrid search + RRF merge. Opt-in, requires LLM function. */
1890
1945
  recallExpanded(query: string, options: RecallOptions & {
1891
1946
  llm: LlmFunction;
@@ -1924,6 +1979,16 @@ declare class Plur {
1924
1979
  removed: number;
1925
1980
  remaining: number;
1926
1981
  };
1982
+ /**
1983
+ * Apply ACT-R decay to all primary store engrams.
1984
+ * Scope-matched engrams are skipped, status transitions logged to history.
1985
+ * Modified engrams are saved back to the store.
1986
+ */
1987
+ batchDecay(options?: {
1988
+ contextScope?: string;
1989
+ lambda?: number;
1990
+ now?: Date;
1991
+ }): BatchDecayResult;
1927
1992
  /** Rebuild SQLite index from YAML source of truth. Only works when index: true. */
1928
1993
  reindex(): void;
1929
1994
  /** Sync SQLite index after YAML write (no-op if index disabled) */
@@ -1995,4 +2060,4 @@ declare class Plur {
1995
2060
  }>;
1996
2061
  }
1997
2062
 
1998
- export { ALL_MIGRATIONS, type AlignmentResult, type Association, type AutoSearchResult, type BoundedRecallResult, COMMITMENT_MULTIPLIER, CURRENT_SCHEMA_VERSION, type CaptureContext, type DedupConfig, type DedupDecision, type DomainCoverage, DomainCoverageSchema, type Engram, type EngramCluster, type EngramStore, type Episode, type EvidenceEntry, EvidenceEntrySchema, type ExtractOptions, type ExtractionResult, type Falsification, FalsificationSchema, type HierarchyPosition, HierarchyPositionSchema, type HistoryEvent, IndexedStorage, type IngestCandidate, type IngestOptions, type InjectOptions, type InjectionLayer, type InjectionResult, type KnowledgeAnchor, type LearnAsyncContext, type LearnAsyncResult, type LearnBatchResult, type LearnContext, type LlmFunction, type LlmTierConfig, type MemberAlignment, type MetaConfidence, MetaConfidenceSchema, type MetaField, MetaFieldSchema, type Migration, type MigrationResult, type ModelTier, PLATITUDE_PATTERNS, type PackManifest, Plur, type PlurConfig, type PlurPaths, type PreviewResult, type PreviousVersionRef, type PrivacyIssue, type PrivacyScanResult, type ProfileCache, type RecallBudget, type RecallOptions, type RegistryEntry, type RelationalAnalysis, type RelationalTriple, type SearchStrategy, SessionBreadcrumbs, SqliteStore, type StatusResult, type StorageBackend, type StorageConfig, type StoreEntry, type StructuralTemplate, StructuralTemplateSchema, type SyncResult, type SyncStatus, type TimelineQuery, type TypedRole, type ValidationResult, type VersionCheckResult, YamlStore, alignCluster, analyzeStructure, appendHistory, assignLayer, asyncAtomicWrite, autoSummary, buildBatchDedupPrompt, buildDedupPrompt, checkForUpdate, classifyPolarity, clearVersionCache, clusterByStructure, computeConfidence, computeContentHash, computeMetaConfidence, confidenceBand, createStore, detectPlurStorage, detectSecrets, engramSearchText, extractMetaEngrams, formatLayer1, formatLayer2, formatLayer3, formatWithLayer, formulateMetaEngram, freshTailBoost, generateEventId, generateGuardrails, generateProfile, generateSummary, getCachedUpdateCheck, getProfileForInjection, getSchemaVersion, isPlatitude, listHistoryMonths, loadProfileCache, markProfileDirty, migrateStore, needsSummary, normalizeStatement, organizeHierarchy, parseDedupResponse, profileNeedsRegeneration, readHistory, readHistoryForEngram, recallAuto, resolveOperationTier, rollbackMigrations, runMigrations, saveProfileCache, selectModel, selectModelForOperation, setSchemaVersion, tokenSimilarity, validateMetaEngram, withAsyncLock };
2063
+ export { ALL_MIGRATIONS, type AlignmentResult, type Association, type AutoSearchResult, type BatchDecayOptions, type BatchDecayResult, type BoundedRecallResult, COMMITMENT_MULTIPLIER, CURRENT_SCHEMA_VERSION, type CaptureContext, type DecayTransition, type DedupConfig, type DedupDecision, type DomainCoverage, DomainCoverageSchema, type Engram, type EngramCluster, type EngramStore, type Episode, type EvidenceEntry, EvidenceEntrySchema, type ExtractOptions, type ExtractionResult, type Falsification, FalsificationSchema, type HierarchyPosition, HierarchyPositionSchema, type HistoryEvent, IndexedStorage, type IngestCandidate, type IngestOptions, type InjectOptions, type InjectionLayer, type InjectionResult, type KnowledgeAnchor, type LearnAsyncContext, type LearnAsyncResult, type LearnBatchResult, type LearnContext, type LlmFunction, type LlmTierConfig, type MemberAlignment, type MetaConfidence, MetaConfidenceSchema, type MetaField, MetaFieldSchema, type Migration, type MigrationResult, type ModelTier, PLATITUDE_PATTERNS, type PackManifest, Plur, type PlurConfig, type PlurPaths, type PreviewResult, type PreviousVersionRef, type PrivacyIssue, type PrivacyScanResult, type ProfileCache, type RecallBudget, type RecallOptions, type RegistryEntry, type RelationalAnalysis, type RelationalTriple, type SearchStrategy, SessionBreadcrumbs, type SimilarityResult, SqliteStore, type StatusResult, type StorageBackend, type StorageConfig, type StoreEntry, type StructuralTemplate, StructuralTemplateSchema, type SyncResult, type SyncStatus, type TimelineQuery, type TypedRole, type ValidationResult, type VersionCheckResult, YamlStore, alignCluster, analyzeStructure, appendHistory, applyBatchDecay, assignLayer, asyncAtomicWrite, autoSummary, buildBatchDedupPrompt, buildDedupPrompt, checkForUpdate, classifyPolarity, clearVersionCache, clusterByStructure, computeConfidence, computeContentHash, computeMetaConfidence, confidenceBand, createStore, detectPlurStorage, detectSecrets, engramSearchText, extractMetaEngrams, formatLayer1, formatLayer2, formatLayer3, formatWithLayer, formulateMetaEngram, freshTailBoost, generateEventId, generateGuardrails, generateProfile, generateSummary, getCachedUpdateCheck, getProfileForInjection, getSchemaVersion, isPlatitude, listHistoryMonths, loadProfileCache, markProfileDirty, migrateStore, needsSummary, normalizeStatement, organizeHierarchy, parseDedupResponse, profileNeedsRegeneration, readHistory, readHistoryForEngram, recallAuto, resolveOperationTier, rollbackMigrations, runMigrations, saveProfileCache, selectModel, selectModelForOperation, setSchemaVersion, strengthToStatus, tokenSimilarity, validateMetaEngram, withAsyncLock };
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  computeIdf,
3
3
  embeddingSearch,
4
+ embeddingSearchWithScores,
4
5
  engramSearchText,
5
6
  ftsScore,
6
7
  ftsTokenize,
7
8
  searchEngrams
8
- } from "./chunk-UETCDULF.js";
9
+ } from "./chunk-MW5EPL7G.js";
9
10
  import {
10
11
  EngramSchemaPassthrough,
11
12
  appendHistory,
@@ -25,13 +26,13 @@ import {
25
26
  readHistoryForEngram,
26
27
  saveEngrams,
27
28
  storePrefix
28
- } from "./chunk-GRDNBUIJ.js";
29
+ } from "./chunk-3ZPTRZE3.js";
29
30
  import {
30
31
  atomicWrite,
31
32
  getSyncStatus,
32
33
  sync,
33
34
  withLock
34
- } from "./chunk-MY4XVDCE.js";
35
+ } from "./chunk-PRK3B7WR.js";
35
36
  import "./chunk-2ZDO52B4.js";
36
37
 
37
38
  // src/index.ts
@@ -316,6 +317,72 @@ function confidenceDecay(retrievalStrength, lastPositiveFeedbackDate, commitment
316
317
  const decayed = retrievalStrength * multiplier;
317
318
  return Math.max(CONFIDENCE_DECAY_FLOOR, decayed);
318
319
  }
320
+ function strengthToStatus(strength) {
321
+ if (strength > 0.5) return "active";
322
+ if (strength > 0.3) return "fading";
323
+ if (strength > 0.1) return "dormant";
324
+ return "retirement_candidate";
325
+ }
326
+ function applyBatchDecay(engrams, historyRoot, options) {
327
+ const now = options?.now ?? /* @__PURE__ */ new Date();
328
+ const lambda = options?.lambda ?? DECAY_RATE;
329
+ const contextScope = options?.contextScope;
330
+ const active = engrams.filter((e) => e.status === "active");
331
+ const transitions = [];
332
+ const modified = [];
333
+ let decayed = 0;
334
+ let skipped = 0;
335
+ for (const engram of active) {
336
+ if (contextScope && isScopeMatched(engram.scope, contextScope)) {
337
+ skipped++;
338
+ continue;
339
+ }
340
+ const days = daysSince(engram.activation.last_accessed, now);
341
+ if (days === 0) continue;
342
+ const emotionalWeight = engram.episodic?.emotional_weight ?? 5;
343
+ const effectiveLambda = lambda * (1 - emotionalWeight / 20);
344
+ const oldStrength = engram.activation.retrieval_strength;
345
+ const newStrength = decayedStrength(oldStrength, days, effectiveLambda);
346
+ if (Math.abs(newStrength - oldStrength) < 1e-10) continue;
347
+ const oldStatus = strengthToStatus(oldStrength);
348
+ const newStatus = strengthToStatus(newStrength);
349
+ engram.activation.retrieval_strength = newStrength;
350
+ modified.push(engram);
351
+ decayed++;
352
+ if (oldStatus !== newStatus) {
353
+ const transition = {
354
+ engram_id: engram.id,
355
+ old_strength: oldStrength,
356
+ new_strength: newStrength,
357
+ old_status: oldStatus,
358
+ new_status: newStatus
359
+ };
360
+ transitions.push(transition);
361
+ const event = {
362
+ event: "engram_updated",
363
+ engram_id: engram.id,
364
+ timestamp: now.toISOString(),
365
+ data: {
366
+ reason: "decay_status_transition",
367
+ old_strength: oldStrength,
368
+ new_strength: newStrength,
369
+ old_status: oldStatus,
370
+ new_status: newStatus
371
+ }
372
+ };
373
+ appendHistory(historyRoot, event);
374
+ }
375
+ }
376
+ return {
377
+ result: { total: active.length, decayed, skipped, transitions },
378
+ modified
379
+ };
380
+ }
381
+ function isScopeMatched(engramScope, contextScope) {
382
+ if (engramScope === contextScope) return true;
383
+ if (engramScope.startsWith(contextScope + "/")) return true;
384
+ return false;
385
+ }
319
386
 
320
387
  // src/polarity.ts
321
388
  var DONT_PATTERNS = [
@@ -1516,7 +1583,7 @@ async function computeSimilarityMatrix(templates) {
1516
1583
  const n = templates.length;
1517
1584
  const matrix = Array.from({ length: n }, () => Array(n).fill(0));
1518
1585
  try {
1519
- const { embed, cosineSimilarity } = await import("./embeddings-EX7QPXJS.js");
1586
+ const { embed, cosineSimilarity } = await import("./embeddings-VXK2E2IF.js");
1520
1587
  const embeddings = [];
1521
1588
  for (const t of templates) {
1522
1589
  embeddings.push(await embed(t));
@@ -2601,7 +2668,7 @@ async function withAsyncLock(filePath, fn, options) {
2601
2668
  if (attempt === maxRetries) {
2602
2669
  throw new Error(`Failed to acquire lock on ${filePath} after ${maxRetries} retries`);
2603
2670
  }
2604
- const delay = baseDelay * Math.pow(2, attempt);
2671
+ const delay = Math.min(baseDelay * Math.pow(2, attempt), 5e3);
2605
2672
  await sleep(delay);
2606
2673
  }
2607
2674
  }
@@ -2942,9 +3009,9 @@ var Plur = class {
2942
3009
  }
2943
3010
  /** Load engrams from a path with mtime-based caching */
2944
3011
  _loadCached(path3) {
2945
- let mtime = 0;
3012
+ let mtime;
2946
3013
  try {
2947
- mtime = fs4.statSync(path3).mtimeMs;
3014
+ mtime = fs4.statSync(path3, { bigint: true }).mtimeNs;
2948
3015
  } catch {
2949
3016
  return [];
2950
3017
  }
@@ -2954,6 +3021,21 @@ var Plur = class {
2954
3021
  this._engramCache.set(path3, { mtime, engrams });
2955
3022
  return engrams;
2956
3023
  }
3024
+ /**
3025
+ * Write engrams to disk and invalidate the cache for that path.
3026
+ *
3027
+ * Why: `_loadCached` uses mtime-based invalidation, but on CI tmpfs
3028
+ * (ubuntu-latest runners) mtime resolution can be coarse enough that a
3029
+ * stat() taken before and after a write returns the same mtime. When that
3030
+ * happens the cache serves a pre-write snapshot and a subsequent `getById`
3031
+ * returns `undefined` for an engram that `learn()` just created. Explicit
3032
+ * invalidation on write removes the filesystem as a source of cache
3033
+ * freshness and closes the race. See issue #25.
3034
+ */
3035
+ _writeEngrams(path3, engrams) {
3036
+ saveEngrams(path3, engrams);
3037
+ this._engramCache.delete(path3);
3038
+ }
2957
3039
  /** Find which store owns an engram by ID. For namespaced IDs, strips prefix to find in store. */
2958
3040
  _findEngramStore(id) {
2959
3041
  const primaryEngrams = this._loadCached(this.paths.engrams);
@@ -3083,7 +3165,7 @@ var Plur = class {
3083
3165
  } : void 0
3084
3166
  };
3085
3167
  engrams.push(engram);
3086
- saveEngrams(this.paths.engrams, engrams);
3168
+ this._writeEngrams(this.paths.engrams, engrams);
3087
3169
  this._syncIndex();
3088
3170
  appendHistory(this.paths.root, {
3089
3171
  event: "engram_created",
@@ -3113,12 +3195,12 @@ var Plur = class {
3113
3195
  }
3114
3196
  /** Async learn with LLM-driven deduplication (Ideas 1+2+19). */
3115
3197
  async learnAsync(statement, context) {
3116
- const { learnAsync: learnAsyncImpl } = await import("./learn-async-VXBH3TYE.js");
3198
+ const { learnAsync: learnAsyncImpl } = await import("./learn-async-UZAPPQK4.js");
3117
3199
  return learnAsyncImpl(this._learnAsyncDeps(), statement, context);
3118
3200
  }
3119
3201
  /** Batch learn with LLM dedup. */
3120
3202
  async learnBatch(statements, llm) {
3121
- const { learnBatch: learnBatchImpl } = await import("./learn-async-VXBH3TYE.js");
3203
+ const { learnBatch: learnBatchImpl } = await import("./learn-async-UZAPPQK4.js");
3122
3204
  return learnBatchImpl(this._learnAsyncDeps(), statements, llm);
3123
3205
  }
3124
3206
  /**
@@ -3159,6 +3241,12 @@ var Plur = class {
3159
3241
  this._reactivateResults(results);
3160
3242
  return results;
3161
3243
  }
3244
+ /** Embedding search returning {engram, score}[] with cosine similarity scores. Async, no API calls. */
3245
+ async similaritySearch(query, options) {
3246
+ const filtered = this._filterEngrams(options);
3247
+ const limit = options?.limit ?? 20;
3248
+ return embeddingSearchWithScores(filtered, query, limit, this.paths.root);
3249
+ }
3162
3250
  /** Expanded search: LLM query expansion + hybrid search + RRF merge. Opt-in, requires LLM function. */
3163
3251
  async recallExpanded(query, options) {
3164
3252
  const filtered = this._filterEngrams(options);
@@ -3267,7 +3355,7 @@ var Plur = class {
3267
3355
  }
3268
3356
  }
3269
3357
  if (modified) {
3270
- saveEngrams(this.paths.engrams, allEngrams);
3358
+ this._writeEngrams(this.paths.engrams, allEngrams);
3271
3359
  this._syncIndex();
3272
3360
  }
3273
3361
  });
@@ -3347,7 +3435,7 @@ var Plur = class {
3347
3435
  } else if (signal === "negative") {
3348
3436
  engram.activation.retrieval_strength = Math.max(0, engram.activation.retrieval_strength - 0.1);
3349
3437
  }
3350
- saveEngrams(this.paths.engrams, engrams);
3438
+ this._writeEngrams(this.paths.engrams, engrams);
3351
3439
  this._syncIndex();
3352
3440
  appendHistory(this.paths.root, {
3353
3441
  event: "feedback_received",
@@ -3375,8 +3463,7 @@ var Plur = class {
3375
3463
  } else if (signal === "negative") {
3376
3464
  engram.activation.retrieval_strength = Math.max(0, engram.activation.retrieval_strength - 0.1);
3377
3465
  }
3378
- saveEngrams(storeInfo.path, storeEngrams);
3379
- this._engramCache.delete(storeInfo.path);
3466
+ this._writeEngrams(storeInfo.path, storeEngrams);
3380
3467
  this._syncIndex();
3381
3468
  return;
3382
3469
  }
@@ -3399,7 +3486,7 @@ var Plur = class {
3399
3486
  }
3400
3487
  }
3401
3488
  if (saved > 0) {
3402
- saveEngrams(this.paths.engrams, engrams);
3489
+ this._writeEngrams(this.paths.engrams, engrams);
3403
3490
  this._syncIndex();
3404
3491
  }
3405
3492
  return { saved, skipped };
@@ -3412,7 +3499,7 @@ var Plur = class {
3412
3499
  const idx = engrams.findIndex((e) => e.id === updated.id);
3413
3500
  if (idx === -1) return false;
3414
3501
  engrams[idx] = updated;
3415
- saveEngrams(this.paths.engrams, engrams);
3502
+ this._writeEngrams(this.paths.engrams, engrams);
3416
3503
  this._syncIndex();
3417
3504
  return true;
3418
3505
  });
@@ -3427,7 +3514,7 @@ var Plur = class {
3427
3514
  if (reason && !engram.rationale) {
3428
3515
  engram.rationale = `Retired: ${reason}`;
3429
3516
  }
3430
- saveEngrams(this.paths.engrams, engrams);
3517
+ this._writeEngrams(this.paths.engrams, engrams);
3431
3518
  this._syncIndex();
3432
3519
  appendHistory(this.paths.root, {
3433
3520
  event: "engram_retired",
@@ -3450,8 +3537,7 @@ var Plur = class {
3450
3537
  if (reason && !engram.rationale) {
3451
3538
  engram.rationale = `Retired: ${reason}`;
3452
3539
  }
3453
- saveEngrams(storeInfo.path, storeEngrams);
3454
- this._engramCache.delete(storeInfo.path);
3540
+ this._writeEngrams(storeInfo.path, storeEngrams);
3455
3541
  this._syncIndex();
3456
3542
  return;
3457
3543
  }
@@ -3465,12 +3551,28 @@ var Plur = class {
3465
3551
  const active = engrams.filter((e) => e.status !== "retired");
3466
3552
  const removed = engrams.length - active.length;
3467
3553
  if (removed > 0) {
3468
- saveEngrams(this.paths.engrams, active);
3554
+ this._writeEngrams(this.paths.engrams, active);
3469
3555
  this._syncIndex();
3470
3556
  }
3471
3557
  return { removed, remaining: active.length };
3472
3558
  });
3473
3559
  }
3560
+ /**
3561
+ * Apply ACT-R decay to all primary store engrams.
3562
+ * Scope-matched engrams are skipped, status transitions logged to history.
3563
+ * Modified engrams are saved back to the store.
3564
+ */
3565
+ batchDecay(options) {
3566
+ return withLock(this.paths.engrams, () => {
3567
+ const engrams = loadEngrams(this.paths.engrams);
3568
+ const { result, modified } = applyBatchDecay(engrams, this.paths.root, options);
3569
+ if (result.transitions.length > 0) {
3570
+ this._writeEngrams(this.paths.engrams, modified);
3571
+ this._syncIndex();
3572
+ }
3573
+ return result;
3574
+ });
3575
+ }
3474
3576
  /** Rebuild SQLite index from YAML source of truth. Only works when index: true. */
3475
3577
  reindex() {
3476
3578
  if (!this.indexedStorage) {
@@ -3672,7 +3774,7 @@ Generate an improved version of the procedure that prevents this failure. Return
3672
3774
  raw.previous_version_ref = { event_id: eventId, changed_at: now };
3673
3775
  if (!raw.episode_ids) raw.episode_ids = [];
3674
3776
  raw.episode_ids.push(episode.id);
3675
- saveEngrams(this.paths.engrams, engrams);
3777
+ this._writeEngrams(this.paths.engrams, engrams);
3676
3778
  this._syncIndex();
3677
3779
  appendHistory(this.paths.root, {
3678
3780
  event: "procedure_evolved",
@@ -3702,7 +3804,7 @@ Generate an improved version of the procedure that prevents this failure. Return
3702
3804
  const raw = engrams[idx];
3703
3805
  if (!raw.episode_ids) raw.episode_ids = [];
3704
3806
  raw.episode_ids.push(episode.id);
3705
- saveEngrams(this.paths.engrams, engrams);
3807
+ this._writeEngrams(this.paths.engrams, engrams);
3706
3808
  this._syncIndex();
3707
3809
  }
3708
3810
  });
@@ -3800,6 +3902,7 @@ export {
3800
3902
  alignCluster,
3801
3903
  analyzeStructure,
3802
3904
  appendHistory,
3905
+ applyBatchDecay,
3803
3906
  assignLayer,
3804
3907
  asyncAtomicWrite,
3805
3908
  autoSummary,
@@ -3851,6 +3954,7 @@ export {
3851
3954
  selectModel,
3852
3955
  selectModelForOperation,
3853
3956
  setSchemaVersion,
3957
+ strengthToStatus,
3854
3958
  tokenSimilarity,
3855
3959
  validateMetaEngram,
3856
3960
  withAsyncLock
@@ -6,10 +6,10 @@ import {
6
6
  logger,
7
7
  parseDedupResponse,
8
8
  saveEngrams
9
- } from "./chunk-GRDNBUIJ.js";
9
+ } from "./chunk-3ZPTRZE3.js";
10
10
  import {
11
11
  withLock
12
- } from "./chunk-MY4XVDCE.js";
12
+ } from "./chunk-PRK3B7WR.js";
13
13
  import "./chunk-2ZDO52B4.js";
14
14
 
15
15
  // src/learn-async.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/core",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,12 +0,0 @@
1
- import {
2
- cosineSimilarity,
3
- embed,
4
- embeddingSearch
5
- } from "./chunk-UETCDULF.js";
6
- import "./chunk-MY4XVDCE.js";
7
- import "./chunk-2ZDO52B4.js";
8
- export {
9
- cosineSimilarity,
10
- embed,
11
- embeddingSearch
12
- };