@psiclawops/hypermem 0.1.0 → 0.5.1

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.
Files changed (153) hide show
  1. package/ARCHITECTURE.md +4 -3
  2. package/README.md +457 -174
  3. package/dist/background-indexer.d.ts +19 -4
  4. package/dist/background-indexer.d.ts.map +1 -1
  5. package/dist/background-indexer.js +329 -17
  6. package/dist/cache.d.ts +110 -0
  7. package/dist/cache.d.ts.map +1 -0
  8. package/dist/cache.js +495 -0
  9. package/dist/compaction-fence.d.ts +1 -1
  10. package/dist/compaction-fence.js +1 -1
  11. package/dist/compositor.d.ts +114 -27
  12. package/dist/compositor.d.ts.map +1 -1
  13. package/dist/compositor.js +1678 -229
  14. package/dist/content-type-classifier.d.ts +41 -0
  15. package/dist/content-type-classifier.d.ts.map +1 -0
  16. package/dist/content-type-classifier.js +181 -0
  17. package/dist/cross-agent.d.ts +5 -0
  18. package/dist/cross-agent.d.ts.map +1 -1
  19. package/dist/cross-agent.js +5 -0
  20. package/dist/db.d.ts +1 -1
  21. package/dist/db.d.ts.map +1 -1
  22. package/dist/db.js +6 -2
  23. package/dist/desired-state-store.d.ts +1 -1
  24. package/dist/desired-state-store.d.ts.map +1 -1
  25. package/dist/desired-state-store.js +15 -5
  26. package/dist/doc-chunk-store.d.ts +26 -1
  27. package/dist/doc-chunk-store.d.ts.map +1 -1
  28. package/dist/doc-chunk-store.js +114 -1
  29. package/dist/doc-chunker.d.ts +1 -1
  30. package/dist/doc-chunker.js +1 -1
  31. package/dist/dreaming-promoter.d.ts +86 -0
  32. package/dist/dreaming-promoter.d.ts.map +1 -0
  33. package/dist/dreaming-promoter.js +381 -0
  34. package/dist/episode-store.d.ts +2 -1
  35. package/dist/episode-store.d.ts.map +1 -1
  36. package/dist/episode-store.js +4 -4
  37. package/dist/fact-store.d.ts +19 -1
  38. package/dist/fact-store.d.ts.map +1 -1
  39. package/dist/fact-store.js +64 -3
  40. package/dist/fleet-store.d.ts +1 -1
  41. package/dist/fleet-store.js +1 -1
  42. package/dist/fos-mod.d.ts +178 -0
  43. package/dist/fos-mod.d.ts.map +1 -0
  44. package/dist/fos-mod.js +416 -0
  45. package/dist/hybrid-retrieval.d.ts +5 -1
  46. package/dist/hybrid-retrieval.d.ts.map +1 -1
  47. package/dist/hybrid-retrieval.js +7 -3
  48. package/dist/image-eviction.d.ts +49 -0
  49. package/dist/image-eviction.d.ts.map +1 -0
  50. package/dist/image-eviction.js +251 -0
  51. package/dist/index.d.ts +50 -11
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +73 -43
  54. package/dist/keystone-scorer.d.ts +51 -0
  55. package/dist/keystone-scorer.d.ts.map +1 -0
  56. package/dist/keystone-scorer.js +52 -0
  57. package/dist/knowledge-graph.d.ts +1 -1
  58. package/dist/knowledge-graph.js +1 -1
  59. package/dist/knowledge-lint.d.ts +29 -0
  60. package/dist/knowledge-lint.d.ts.map +1 -0
  61. package/dist/knowledge-lint.js +116 -0
  62. package/dist/knowledge-store.d.ts +1 -1
  63. package/dist/knowledge-store.d.ts.map +1 -1
  64. package/dist/knowledge-store.js +8 -2
  65. package/dist/library-schema.d.ts +3 -3
  66. package/dist/library-schema.d.ts.map +1 -1
  67. package/dist/library-schema.js +324 -3
  68. package/dist/message-store.d.ts +15 -2
  69. package/dist/message-store.d.ts.map +1 -1
  70. package/dist/message-store.js +51 -1
  71. package/dist/metrics-dashboard.d.ts +114 -0
  72. package/dist/metrics-dashboard.d.ts.map +1 -0
  73. package/dist/metrics-dashboard.js +260 -0
  74. package/dist/obsidian-exporter.d.ts +57 -0
  75. package/dist/obsidian-exporter.d.ts.map +1 -0
  76. package/dist/obsidian-exporter.js +274 -0
  77. package/dist/obsidian-watcher.d.ts +147 -0
  78. package/dist/obsidian-watcher.d.ts.map +1 -0
  79. package/dist/obsidian-watcher.js +403 -0
  80. package/dist/open-domain.d.ts +46 -0
  81. package/dist/open-domain.d.ts.map +1 -0
  82. package/dist/open-domain.js +125 -0
  83. package/dist/preference-store.d.ts +1 -1
  84. package/dist/preference-store.js +1 -1
  85. package/dist/preservation-gate.d.ts +1 -1
  86. package/dist/preservation-gate.js +1 -1
  87. package/dist/proactive-pass.d.ts +63 -0
  88. package/dist/proactive-pass.d.ts.map +1 -0
  89. package/dist/proactive-pass.js +239 -0
  90. package/dist/profiles.d.ts +44 -0
  91. package/dist/profiles.d.ts.map +1 -0
  92. package/dist/profiles.js +227 -0
  93. package/dist/provider-translator.d.ts +13 -3
  94. package/dist/provider-translator.d.ts.map +1 -1
  95. package/dist/provider-translator.js +63 -9
  96. package/dist/rate-limiter.d.ts +1 -1
  97. package/dist/rate-limiter.js +1 -1
  98. package/dist/repair-tool-pairs.d.ts +38 -0
  99. package/dist/repair-tool-pairs.d.ts.map +1 -0
  100. package/dist/repair-tool-pairs.js +138 -0
  101. package/dist/retrieval-policy.d.ts +51 -0
  102. package/dist/retrieval-policy.d.ts.map +1 -0
  103. package/dist/retrieval-policy.js +77 -0
  104. package/dist/schema.d.ts +2 -2
  105. package/dist/schema.d.ts.map +1 -1
  106. package/dist/schema.js +28 -2
  107. package/dist/secret-scanner.d.ts +1 -1
  108. package/dist/secret-scanner.js +1 -1
  109. package/dist/seed.d.ts +2 -2
  110. package/dist/seed.js +2 -2
  111. package/dist/session-flusher.d.ts +53 -0
  112. package/dist/session-flusher.d.ts.map +1 -0
  113. package/dist/session-flusher.js +69 -0
  114. package/dist/session-topic-map.d.ts +41 -0
  115. package/dist/session-topic-map.d.ts.map +1 -0
  116. package/dist/session-topic-map.js +77 -0
  117. package/dist/spawn-context.d.ts +54 -0
  118. package/dist/spawn-context.d.ts.map +1 -0
  119. package/dist/spawn-context.js +159 -0
  120. package/dist/system-store.d.ts +1 -1
  121. package/dist/system-store.js +1 -1
  122. package/dist/temporal-store.d.ts +80 -0
  123. package/dist/temporal-store.d.ts.map +1 -0
  124. package/dist/temporal-store.js +149 -0
  125. package/dist/topic-detector.d.ts +35 -0
  126. package/dist/topic-detector.d.ts.map +1 -0
  127. package/dist/topic-detector.js +249 -0
  128. package/dist/topic-store.d.ts +1 -1
  129. package/dist/topic-store.js +1 -1
  130. package/dist/topic-synthesizer.d.ts +51 -0
  131. package/dist/topic-synthesizer.d.ts.map +1 -0
  132. package/dist/topic-synthesizer.js +315 -0
  133. package/dist/trigger-registry.d.ts +63 -0
  134. package/dist/trigger-registry.d.ts.map +1 -0
  135. package/dist/trigger-registry.js +163 -0
  136. package/dist/types.d.ts +214 -10
  137. package/dist/types.d.ts.map +1 -1
  138. package/dist/types.js +1 -1
  139. package/dist/vector-store.d.ts +43 -5
  140. package/dist/vector-store.d.ts.map +1 -1
  141. package/dist/vector-store.js +189 -10
  142. package/dist/version.d.ts +34 -0
  143. package/dist/version.d.ts.map +1 -0
  144. package/dist/version.js +34 -0
  145. package/dist/wiki-page-emitter.d.ts +65 -0
  146. package/dist/wiki-page-emitter.d.ts.map +1 -0
  147. package/dist/wiki-page-emitter.js +258 -0
  148. package/dist/work-store.d.ts +1 -1
  149. package/dist/work-store.js +1 -1
  150. package/package.json +15 -5
  151. package/dist/redis.d.ts +0 -188
  152. package/dist/redis.d.ts.map +0 -1
  153. package/dist/redis.js +0 -534
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperMem — Agent-Centric Memory & Context Composition Engine
2
+ * hypermem — Agent-Centric Memory & Context Composition Engine
3
3
  *
4
4
  * @module @psiclawops/hypermem
5
5
  *
@@ -9,6 +9,7 @@
9
9
  * L3: vectors.db — per-agent semantic search index (reconstructable)
10
10
  * L4: library.db — fleet-wide structured knowledge (crown jewel)
11
11
  */
12
+ export { ENGINE_VERSION, MIN_NODE_VERSION, MIN_REDIS_VERSION, SQLITE_VEC_VERSION, MAIN_SCHEMA_VERSION, LIBRARY_SCHEMA_VERSION_EXPORT, HYPERMEM_COMPAT_VERSION, SCHEMA_COMPAT } from './version.js';
12
13
  export { DatabaseManager } from './db.js';
13
14
  export { MessageStore } from './message-store.js';
14
15
  export { FactStore } from './fact-store.js';
@@ -20,13 +21,15 @@ export { FleetStore } from './fleet-store.js';
20
21
  export { SystemStore } from './system-store.js';
21
22
  export { WorkStore } from './work-store.js';
22
23
  export { DesiredStateStore } from './desired-state-store.js';
24
+ export { evictStaleContent, DEFAULT_EVICTION_CONFIG } from './image-eviction.js';
23
25
  export { KnowledgeGraph } from './knowledge-graph.js';
24
26
  export { RateLimiter, createRateLimitedEmbedder } from './rate-limiter.js';
25
- export { RedisLayer } from './redis.js';
26
- export { Compositor } from './compositor.js';
27
+ export { CacheLayer } from './cache.js';
28
+ export { Compositor, applyToolGradientToWindow, canPersistReshapedHistory } from './compositor.js';
29
+ export { TRIGGER_REGISTRY, TRIGGER_REGISTRY_VERSION, TRIGGER_REGISTRY_HASH, DEFAULT_TRIGGERS, matchTriggers, } from './trigger-registry.js';
27
30
  export { ensureCompactionFenceSchema, updateCompactionFence, getCompactionFence, getCompactionEligibility, getCompactableMessages, } from './compaction-fence.js';
28
31
  export { verifyPreservation, verifyPreservationFromVectors, } from './preservation-gate.js';
29
- export { toProviderFormat, fromProviderFormat, userMessageToNeutral, toolResultsToNeutral, normalizeToolCallId, generateToolCallId, detectProvider, } from './provider-translator.js';
32
+ export { toProviderFormat, fromProviderFormat, userMessageToNeutral, toolResultsToNeutral, normalizeToolCallId, generateToolCallId, detectProvider, repairToolCallPairs, } from './provider-translator.js';
30
33
  export { migrate, SCHEMA_VERSION } from './schema.js';
31
34
  export { migrateLibrary, LIBRARY_SCHEMA_VERSION } from './library-schema.js';
32
35
  export { VectorStore, generateEmbeddings } from './vector-store.js';
@@ -34,8 +37,18 @@ export { hybridSearch, buildFtsQuery } from './hybrid-retrieval.js';
34
37
  export { DocChunkStore } from './doc-chunk-store.js';
35
38
  export { WorkspaceSeeder, seedWorkspace } from './seed.js';
36
39
  export { chunkMarkdown, chunkFile, inferCollection, hashContent, ACA_COLLECTIONS } from './doc-chunker.js';
37
- export { crossAgentQuery, canAccess, visibilityFilter, defaultOrgRegistry, buildOrgRegistryFromDb, } from './cross-agent.js';
40
+ export { crossAgentQuery, canAccess, visibilityFilter, defaultOrgRegistry, buildOrgRegistryFromDb, loadOrgRegistryFromDb, } from './cross-agent.js';
38
41
  export { BackgroundIndexer, createIndexer } from './background-indexer.js';
42
+ export { runDreamingPromoter, runDreamingPassForFleet, resolveAgentWorkspacePath, DEFAULT_DREAMER_CONFIG, } from './dreaming-promoter.js';
43
+ export { TopicSynthesizer } from './topic-synthesizer.js';
44
+ export { WikiPageEmitter } from './wiki-page-emitter.js';
45
+ export { lintKnowledge } from './knowledge-lint.js';
46
+ export { buildSpawnContext } from './spawn-context.js';
47
+ export { runNoiseSweep, runToolDecay } from './proactive-pass.js';
48
+ export { classifyContentType, signalWeight, isSignalBearing, SIGNAL_WEIGHT } from './content-type-classifier.js';
49
+ export { detectTopicShift, stripMessageMetadata } from './topic-detector.js';
50
+ export { SessionTopicMap } from './session-topic-map.js';
51
+ export { getActiveFOS, matchMOD, renderFOS, renderMOD, recordOutputMetrics, } from './fos-mod.js';
39
52
  import { DatabaseManager } from './db.js';
40
53
  import { MessageStore } from './message-store.js';
41
54
  import { FactStore } from './fact-store.js';
@@ -48,10 +61,11 @@ import { SystemStore } from './system-store.js';
48
61
  import { WorkStore } from './work-store.js';
49
62
  import { KnowledgeGraph } from './knowledge-graph.js';
50
63
  import { DesiredStateStore } from './desired-state-store.js';
51
- import { RedisLayer } from './redis.js';
64
+ import { CacheLayer } from './cache.js';
52
65
  import { Compositor } from './compositor.js';
53
66
  import { VectorStore } from './vector-store.js';
54
67
  import { userMessageToNeutral, fromProviderFormat } from './provider-translator.js';
68
+ import { stripMessageMetadata } from './topic-detector.js';
55
69
  import { DocChunkStore } from './doc-chunk-store.js';
56
70
  import { WorkspaceSeeder } from './seed.js';
57
71
  import { crossAgentQuery, buildOrgRegistryFromDb } from './cross-agent.js';
@@ -60,13 +74,10 @@ import os from 'node:os';
60
74
  const DEFAULT_CONFIG = {
61
75
  enabled: true,
62
76
  dataDir: path.join(process.env.HOME || os.homedir(), '.openclaw', 'hypermem'),
63
- redis: {
64
- host: 'localhost',
65
- port: 6379,
77
+ cache: {
66
78
  keyPrefix: 'hm:',
67
79
  sessionTTL: 14400, // 4 hours — system/identity/meta slots
68
- historyTTL: 86400, // 24 hourshistory list outlives other slots
69
- flushInterval: 1000,
80
+ historyTTL: 604800, // 7 daysextended for ClawCanvas display
70
81
  },
71
82
  compositor: {
72
83
  // TUNE-010 (2026-04-02): Raised from 65000 → 90000.
@@ -75,9 +86,9 @@ const DEFAULT_CONFIG = {
75
86
  // re-run composition, so 90k is safe — leaves ~30k headroom for in-flight
76
87
  // tool results on a 120k window. Budget is better spent on context quality.
77
88
  defaultTokenBudget: 90000,
78
- maxHistoryMessages: 250,
79
- maxFacts: 40,
80
- maxCrossSessionContext: 8000,
89
+ maxHistoryMessages: 1000,
90
+ maxFacts: 28,
91
+ maxCrossSessionContext: 6000,
81
92
  maxRecentToolPairs: 3,
82
93
  maxProseToolPairs: 10,
83
94
  warmHistoryBudgetFraction: 0.4,
@@ -90,6 +101,8 @@ const DEFAULT_CONFIG = {
90
101
  factDecayRate: 0.01,
91
102
  episodeSignificanceThreshold: 0.5,
92
103
  periodicInterval: 300000,
104
+ batchSize: 128,
105
+ maxMessagesPerTick: 500,
93
106
  },
94
107
  embedding: {
95
108
  ollamaUrl: 'http://localhost:11434',
@@ -100,24 +113,24 @@ const DEFAULT_CONFIG = {
100
113
  },
101
114
  };
102
115
  /**
103
- * HyperMem — the main API facade.
116
+ * hypermem — the main API facade.
104
117
  *
105
118
  * Usage:
106
- * const hm = await HyperMem.create({ dataDir: '~/.openclaw/hypermem' });
119
+ * const hm = await hypermem.create({ dataDir: '~/.openclaw/hypermem' });
107
120
  * await hm.record('forge', 'agent:forge:webchat:main', userMsg);
108
121
  * const result = await hm.compose({ agentId: 'forge', sessionKey: '...', ... });
109
122
  */
110
123
  export class HyperMem {
111
124
  dbManager;
112
- redis;
125
+ cache;
113
126
  compositor;
114
127
  config;
115
128
  constructor(config) {
116
129
  this.config = config;
117
130
  this.dbManager = new DatabaseManager({ dataDir: config.dataDir });
118
- this.redis = new RedisLayer(config.redis);
131
+ this.cache = new CacheLayer(config.cache);
119
132
  this.compositor = new Compositor({
120
- redis: this.redis,
133
+ cache: this.cache,
121
134
  vectorStore: null, // Set after create() when vector DB is available
122
135
  libraryDb: null, // Set after create() when library DB is available
123
136
  }, config.compositor);
@@ -130,13 +143,13 @@ export class HyperMem {
130
143
  return this.compositor.vectorStore;
131
144
  }
132
145
  /**
133
- * Create and initialize a HyperMem instance.
146
+ * Create and initialize a hypermem instance.
134
147
  */
135
148
  static async create(config) {
136
149
  const merged = {
137
150
  ...DEFAULT_CONFIG,
138
151
  ...config,
139
- redis: { ...DEFAULT_CONFIG.redis, ...config?.redis },
152
+ cache: { ...DEFAULT_CONFIG.cache, ...config?.cache },
140
153
  compositor: { ...DEFAULT_CONFIG.compositor, ...config?.compositor },
141
154
  indexer: { ...DEFAULT_CONFIG.indexer, ...config?.indexer },
142
155
  embedding: {
@@ -145,12 +158,12 @@ export class HyperMem {
145
158
  },
146
159
  };
147
160
  const hm = new HyperMem(merged);
148
- const redisOk = await hm.redis.connect();
149
- if (redisOk) {
150
- console.log('[hypermem] Redis connected');
161
+ const cacheOk = await hm.cache.connect();
162
+ if (cacheOk) {
163
+ console.log('[hypermem] Cache connected');
151
164
  }
152
165
  else {
153
- console.warn('[hypermem] Redis unavailable — running in SQLite-only mode');
166
+ console.warn('[hypermem] Cache unavailable — running in SQLite-only mode');
154
167
  }
155
168
  // ── Vector store init ─────────────────────────────────────
156
169
  // Attempt to wire up sqlite-vec + nomic-embed-text for semantic recall.
@@ -164,7 +177,10 @@ export class HyperMem {
164
177
  const vs = new VectorStore(vectorDb, merged.embedding, hm.dbManager.getLibraryDb());
165
178
  vs.ensureTables();
166
179
  hm.compositor.setVectorStore(vs);
167
- console.log('[hypermem] Vector store initialized (sqlite-vec + nomic-embed-text)');
180
+ const embeddingDesc = merged.embedding.provider === 'openai'
181
+ ? `${merged.embedding.openaiBaseUrl?.includes('openrouter') ? 'openrouter' : 'openai'}/${merged.embedding.model ?? 'text-embedding-3-small'}`
182
+ : `ollama/${merged.embedding.model ?? 'nomic-embed-text'}`;
183
+ console.log(`[hypermem] Vector store initialized (sqlite-vec + ${embeddingDesc})`);
168
184
  }
169
185
  else {
170
186
  console.warn('[hypermem] sqlite-vec unavailable — semantic recall in FTS5-only mode');
@@ -189,13 +205,13 @@ export class HyperMem {
189
205
  provider: opts?.provider,
190
206
  model: opts?.model,
191
207
  });
192
- const neutral = userMessageToNeutral(content);
208
+ const neutral = userMessageToNeutral(stripMessageMetadata(content));
193
209
  const stored = store.recordMessage(conversation.id, agentId, neutral, {
194
210
  tokenCount: opts?.tokenCount,
195
211
  isHeartbeat: opts?.isHeartbeat,
196
212
  });
197
- await this.redis.pushHistory(agentId, sessionKey, [stored], this.config.compositor.maxHistoryMessages);
198
- await this.redis.touchSession(agentId, sessionKey);
213
+ await this.cache.pushHistory(agentId, sessionKey, [stored], this.config.compositor.maxHistoryMessages);
214
+ await this.cache.touchSession(agentId, sessionKey);
199
215
  return stored;
200
216
  }
201
217
  /**
@@ -211,8 +227,8 @@ export class HyperMem {
211
227
  const stored = store.recordMessage(conversation.id, agentId, message, {
212
228
  tokenCount: opts?.tokenCount,
213
229
  });
214
- await this.redis.pushHistory(agentId, sessionKey, [stored], this.config.compositor.maxHistoryMessages);
215
- await this.redis.touchSession(agentId, sessionKey);
230
+ await this.cache.pushHistory(agentId, sessionKey, [stored], this.config.compositor.maxHistoryMessages);
231
+ await this.cache.touchSession(agentId, sessionKey);
216
232
  return stored;
217
233
  }
218
234
  /**
@@ -238,6 +254,13 @@ export class HyperMem {
238
254
  const libraryDb = this.dbManager.getLibraryDb();
239
255
  await this.compositor.warmSession(agentId, sessionKey, db, { ...opts, libraryDb });
240
256
  }
257
+ /**
258
+ * Recompute the Redis hot history view from SQLite and re-apply tool gradient.
259
+ */
260
+ async refreshRedisGradient(agentId, sessionKey, tokenBudget) {
261
+ const db = this.dbManager.getMessageDb(agentId);
262
+ await this.compositor.refreshRedisGradient(agentId, sessionKey, db, tokenBudget);
263
+ }
241
264
  /**
242
265
  * Full-text search across all messages for an agent.
243
266
  */
@@ -363,7 +386,7 @@ export class HyperMem {
363
386
  const store = new FleetStore(db);
364
387
  const result = store.upsertAgent(id, data);
365
388
  // Invalidate cache — fire and forget
366
- this.redis.invalidateFleetAgent(id).catch(() => { });
389
+ this.cache.invalidateFleetAgent(id).catch(() => { });
367
390
  return result;
368
391
  }
369
392
  /**
@@ -371,14 +394,14 @@ export class HyperMem {
371
394
  */
372
395
  async getFleetAgentCached(id) {
373
396
  // Try cache first
374
- const cached = await this.redis.getCachedFleetAgent(id);
397
+ const cached = await this.cache.getCachedFleetAgent(id);
375
398
  if (cached)
376
399
  return cached;
377
400
  // Fall back to SQLite
378
401
  const agent = this.getFleetAgent(id);
379
402
  if (agent) {
380
403
  // Warm cache — fire and forget
381
- this.redis.cacheFleetAgent(id, agent).catch(() => { });
404
+ this.cache.cacheFleetAgent(id, agent).catch(() => { });
382
405
  }
383
406
  return agent;
384
407
  }
@@ -531,7 +554,7 @@ export class HyperMem {
531
554
  const store = new DesiredStateStore(db);
532
555
  const result = store.setDesired(agentId, configKey, desiredValue, opts);
533
556
  // Invalidate cache — desired state change affects fleet view
534
- this.redis.invalidateFleetAgent(agentId).catch(() => { });
557
+ this.cache.invalidateFleetAgent(agentId).catch(() => { });
535
558
  return result;
536
559
  }
537
560
  /**
@@ -541,7 +564,7 @@ export class HyperMem {
541
564
  const db = this.dbManager.getLibraryDb();
542
565
  const store = new DesiredStateStore(db);
543
566
  const result = store.reportActual(agentId, configKey, actualValue);
544
- this.redis.invalidateFleetAgent(agentId).catch(() => { });
567
+ this.cache.invalidateFleetAgent(agentId).catch(() => { });
545
568
  return result;
546
569
  }
547
570
  /**
@@ -551,7 +574,7 @@ export class HyperMem {
551
574
  const db = this.dbManager.getLibraryDb();
552
575
  const store = new DesiredStateStore(db);
553
576
  const result = store.reportActualBulk(agentId, actuals);
554
- this.redis.invalidateFleetAgent(agentId).catch(() => { });
577
+ this.cache.invalidateFleetAgent(agentId).catch(() => { });
555
578
  return result;
556
579
  }
557
580
  /**
@@ -722,7 +745,7 @@ export class HyperMem {
722
745
  */
723
746
  async getSessionCursor(agentId, sessionKey) {
724
747
  // Try Redis first (hot path)
725
- const redisCursor = await this.redis.getCursor(agentId, sessionKey);
748
+ const redisCursor = await this.cache.getCursor(agentId, sessionKey);
726
749
  if (redisCursor)
727
750
  return redisCursor;
728
751
  // Fallback to SQLite
@@ -746,7 +769,7 @@ export class HyperMem {
746
769
  };
747
770
  // Re-warm Redis so subsequent reads are fast
748
771
  try {
749
- await this.redis.setCursor(agentId, sessionKey, cursor);
772
+ await this.cache.setCursor(agentId, sessionKey, cursor);
750
773
  }
751
774
  catch {
752
775
  // Best-effort re-warm
@@ -976,7 +999,7 @@ export class HyperMem {
976
999
  * - Fleet summary (counts, drift status)
977
1000
  */
978
1001
  async hydrateFleetCache() {
979
- if (!this.redis.isConnected)
1002
+ if (!this.cache.isConnected)
980
1003
  return { agents: 0, summary: false };
981
1004
  const db = this.dbManager.getLibraryDb();
982
1005
  const fleetStore = new FleetStore(db);
@@ -1000,7 +1023,7 @@ export class HyperMem {
1000
1023
  })),
1001
1024
  desiredConfig,
1002
1025
  };
1003
- await this.redis.cacheFleetAgent(agent.id, composite);
1026
+ await this.cache.cacheFleetAgent(agent.id, composite);
1004
1027
  hydrated++;
1005
1028
  }
1006
1029
  catch (err) {
@@ -1022,7 +1045,7 @@ export class HyperMem {
1022
1045
  drift: driftSummary,
1023
1046
  hydratedAt: new Date().toISOString(),
1024
1047
  };
1025
- await this.redis.cacheFleetSummary(summary);
1048
+ await this.cache.cacheFleetSummary(summary);
1026
1049
  }
1027
1050
  catch {
1028
1051
  return { agents: hydrated, summary: false };
@@ -1034,9 +1057,16 @@ export class HyperMem {
1034
1057
  * Clean shutdown.
1035
1058
  */
1036
1059
  async close() {
1037
- await this.redis.disconnect();
1060
+ await this.cache.disconnect();
1038
1061
  this.dbManager.close();
1039
1062
  }
1040
1063
  }
1041
1064
  export default HyperMem;
1065
+ export { SessionFlusher, flushSession } from './session-flusher.js';
1066
+ export { importVault, watchVault, parseObsidianNote, parseFrontmatter, extractWikilinks, extractTags, cleanObsidianMarkdown } from './obsidian-watcher.js';
1067
+ export { exportToVault } from './obsidian-exporter.js';
1068
+ export { collectMetrics, formatMetricsSummary } from './metrics-dashboard.js';
1069
+ export { getProfile, mergeProfile, PROFILES, lightProfile, standardProfile, fullProfile, extendedProfile, minimalProfile, richProfile } from './profiles.js';
1070
+ export { renderStarterFOS, resolveOutputTier } from './fos-mod.js';
1071
+ export { repairToolPairs } from './repair-tool-pairs.js';
1042
1072
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * hypermem Keystone Scorer
3
+ *
4
+ * Scores message candidates for inclusion in the Keystone History Slot (P2.1).
5
+ * A "keystone" is an older, high-signal message that provides critical context
6
+ * for the current conversation — decisions, specs, discoveries that happened
7
+ * before the recent history window.
8
+ *
9
+ * Scoring formula (weights sum to 1.0):
10
+ * - episodeSignificance × 0.5 (was this message linked to a significant episode?)
11
+ * - ftsRelevance × 0.3 (is it semantically relevant to the current prompt?)
12
+ * - recencyFactor × 0.2 (how recent is it, relative to maxAgeHours?)
13
+ *
14
+ * Content-type bonus: messages classified as 'decision' or 'spec' get +0.1,
15
+ * capped at 1.0. These are the highest-value signals for context recall.
16
+ */
17
+ export interface KeystoneCandidate {
18
+ messageId: number;
19
+ messageIndex: number;
20
+ role: string;
21
+ content: string;
22
+ timestamp: string;
23
+ /** Significance from the episodes table (NULL if no episode was linked). */
24
+ episodeSignificance: number | null;
25
+ /** FTS5 BM25 relevance rank, already normalized to [0, 1]. */
26
+ ftsRank: number;
27
+ /** Age in hours from now. */
28
+ ageHours: number;
29
+ }
30
+ export interface ScoredKeystone extends KeystoneCandidate {
31
+ score: number;
32
+ }
33
+ /**
34
+ * Score a single keystone candidate.
35
+ *
36
+ * @param candidate - The message candidate with its signals
37
+ * @param maxAgeHours - The age ceiling for the recency factor (messages older
38
+ * than this get recencyFactor = 0, but are not excluded — they can still score
39
+ * via significance + ftsRelevance).
40
+ * @returns Score in [0.0, 1.0]
41
+ */
42
+ export declare function scoreKeystone(candidate: KeystoneCandidate, maxAgeHours: number): number;
43
+ /**
44
+ * Score an array of candidates and sort by score descending.
45
+ *
46
+ * @param candidates - Raw candidates from the DB query
47
+ * @param maxAgeHours - Age ceiling for recency scoring (e.g. 720 = 30 days)
48
+ * @returns Candidates sorted by score DESC with score field attached
49
+ */
50
+ export declare function rankKeystones(candidates: KeystoneCandidate[], maxAgeHours: number): ScoredKeystone[];
51
+ //# sourceMappingURL=keystone-scorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keystone-scorer.d.ts","sourceRoot":"","sources":["../src/keystone-scorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACvD,KAAK,EAAE,MAAM,CAAC;CACf;AAID;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,iBAAiB,EAC5B,WAAW,EAAE,MAAM,GAClB,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,iBAAiB,EAAE,EAC/B,WAAW,EAAE,MAAM,GAClB,cAAc,EAAE,CAIlB"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * hypermem Keystone Scorer
3
+ *
4
+ * Scores message candidates for inclusion in the Keystone History Slot (P2.1).
5
+ * A "keystone" is an older, high-signal message that provides critical context
6
+ * for the current conversation — decisions, specs, discoveries that happened
7
+ * before the recent history window.
8
+ *
9
+ * Scoring formula (weights sum to 1.0):
10
+ * - episodeSignificance × 0.5 (was this message linked to a significant episode?)
11
+ * - ftsRelevance × 0.3 (is it semantically relevant to the current prompt?)
12
+ * - recencyFactor × 0.2 (how recent is it, relative to maxAgeHours?)
13
+ *
14
+ * Content-type bonus: messages classified as 'decision' or 'spec' get +0.1,
15
+ * capped at 1.0. These are the highest-value signals for context recall.
16
+ */
17
+ import { classifyContentType } from './content-type-classifier.js';
18
+ // ─── Scorer ──────────────────────────────────────────────────────
19
+ /**
20
+ * Score a single keystone candidate.
21
+ *
22
+ * @param candidate - The message candidate with its signals
23
+ * @param maxAgeHours - The age ceiling for the recency factor (messages older
24
+ * than this get recencyFactor = 0, but are not excluded — they can still score
25
+ * via significance + ftsRelevance).
26
+ * @returns Score in [0.0, 1.0]
27
+ */
28
+ export function scoreKeystone(candidate, maxAgeHours) {
29
+ const significance = candidate.episodeSignificance ?? 0.3;
30
+ const ftsRelevance = Math.min(1.0, Math.max(0, candidate.ftsRank));
31
+ const recencyFactor = Math.max(0, 1.0 - (candidate.ageHours / maxAgeHours));
32
+ let score = (significance * 0.5) + (ftsRelevance * 0.3) + (recencyFactor * 0.2);
33
+ // Content-type bonus: decisions and specs get +0.1 (capped at 1.0)
34
+ const contentType = classifyContentType(candidate.content);
35
+ if (contentType.type === 'decision' || contentType.type === 'spec') {
36
+ score = Math.min(1.0, score + 0.1);
37
+ }
38
+ return score;
39
+ }
40
+ /**
41
+ * Score an array of candidates and sort by score descending.
42
+ *
43
+ * @param candidates - Raw candidates from the DB query
44
+ * @param maxAgeHours - Age ceiling for recency scoring (e.g. 720 = 30 days)
45
+ * @returns Candidates sorted by score DESC with score field attached
46
+ */
47
+ export function rankKeystones(candidates, maxAgeHours) {
48
+ return candidates
49
+ .map(c => ({ ...c, score: scoreKeystone(c, maxAgeHours) }))
50
+ .sort((a, b) => b.score - a.score);
51
+ }
52
+ //# sourceMappingURL=keystone-scorer.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperMem Knowledge Graph
2
+ * hypermem Knowledge Graph
3
3
  *
4
4
  * DAG traversal over knowledge_links in library.db.
5
5
  * Links connect entities across collections:
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperMem Knowledge Graph
2
+ * hypermem Knowledge Graph
3
3
  *
4
4
  * DAG traversal over knowledge_links in library.db.
5
5
  * Links connect entities across collections:
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Knowledge Lint
3
+ *
4
+ * Health checks for the knowledge table:
5
+ * 1. Stale syntheses — decay confidence on old topic-synthesis entries
6
+ * 2. Orphan topics — topics with too few messages, stale > 48h
7
+ * 3. Coverage gaps — topics with many messages but no synthesis
8
+ */
9
+ import type { DatabaseSync } from 'node:sqlite';
10
+ export interface LintResult {
11
+ staleDecayed: number;
12
+ orphansFound: number;
13
+ coverageGaps: string[];
14
+ }
15
+ /**
16
+ * Run lint checks on the knowledge table.
17
+ *
18
+ * 1. Stale syntheses: topic-synthesis entries where the source topic's
19
+ * updated_at is older than LINT_STALE_DAYS and there are no new messages.
20
+ * Marks these with confidence = 0.3.
21
+ *
22
+ * 2. Orphan topics: topics with message_count < 3 and updated_at older than 48h.
23
+ * Logged but not synthesized.
24
+ *
25
+ * 3. Coverage gaps: topics with message_count >= 20 but no corresponding
26
+ * knowledge entry (domain='topic-synthesis', key=topic.name).
27
+ */
28
+ export declare function lintKnowledge(libraryDb: DatabaseSync): LintResult;
29
+ //# sourceMappingURL=knowledge-lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge-lint.d.ts","sourceRoot":"","sources":["../src/knowledge-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,YAAY,GAAG,UAAU,CAuGjE"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Knowledge Lint
3
+ *
4
+ * Health checks for the knowledge table:
5
+ * 1. Stale syntheses — decay confidence on old topic-synthesis entries
6
+ * 2. Orphan topics — topics with too few messages, stale > 48h
7
+ * 3. Coverage gaps — topics with many messages but no synthesis
8
+ */
9
+ import { LINT_STALE_DAYS } from './topic-synthesizer.js';
10
+ // ─── lintKnowledge ──────────────────────────────────────────────
11
+ /**
12
+ * Run lint checks on the knowledge table.
13
+ *
14
+ * 1. Stale syntheses: topic-synthesis entries where the source topic's
15
+ * updated_at is older than LINT_STALE_DAYS and there are no new messages.
16
+ * Marks these with confidence = 0.3.
17
+ *
18
+ * 2. Orphan topics: topics with message_count < 3 and updated_at older than 48h.
19
+ * Logged but not synthesized.
20
+ *
21
+ * 3. Coverage gaps: topics with message_count >= 20 but no corresponding
22
+ * knowledge entry (domain='topic-synthesis', key=topic.name).
23
+ */
24
+ export function lintKnowledge(libraryDb) {
25
+ const result = {
26
+ staleDecayed: 0,
27
+ orphansFound: 0,
28
+ coverageGaps: [],
29
+ };
30
+ // ── 1. Stale syntheses ─────────────────────────────────────────
31
+ // Find topic-synthesis knowledge entries whose source topic hasn't been
32
+ // updated in LINT_STALE_DAYS days.
33
+ try {
34
+ const staleSyntheses = libraryDb.prepare(`
35
+ SELECT k.id, k.source_ref, k.agent_id, k.key
36
+ FROM knowledge k
37
+ WHERE k.domain = 'topic-synthesis'
38
+ AND k.superseded_by IS NULL
39
+ AND k.updated_at < datetime('now', '-${LINT_STALE_DAYS} days')
40
+ `).all();
41
+ for (const entry of staleSyntheses) {
42
+ // Extract topic id from source_ref: "topic:<id>" or "topic:<id>:mc:<count>"
43
+ if (!entry.source_ref)
44
+ continue;
45
+ const match = entry.source_ref.match(/^topic:(\d+)/);
46
+ if (!match)
47
+ continue;
48
+ const topicId = parseInt(match[1], 10);
49
+ // Check if topic exists and is stale (no recent updates)
50
+ let topicRow;
51
+ try {
52
+ topicRow = libraryDb.prepare('SELECT updated_at, message_count FROM topics WHERE id = ?').get(topicId);
53
+ }
54
+ catch {
55
+ continue;
56
+ }
57
+ if (!topicRow)
58
+ continue;
59
+ // Check if topic is stale (updated_at older than LINT_STALE_DAYS)
60
+ const topicAge = libraryDb.prepare(`
61
+ SELECT CASE WHEN datetime(?) < datetime('now', '-${LINT_STALE_DAYS} days') THEN 1 ELSE 0 END AS is_stale
62
+ `).get(topicRow.updated_at);
63
+ if (topicAge.is_stale) {
64
+ // Decay confidence to 0.3
65
+ libraryDb.prepare('UPDATE knowledge SET confidence = 0.3, updated_at = datetime(\'now\') WHERE id = ?').run(entry.id);
66
+ result.staleDecayed++;
67
+ }
68
+ }
69
+ }
70
+ catch {
71
+ // Non-fatal — continue with other checks
72
+ }
73
+ // ── 2. Orphan topics ───────────────────────────────────────────
74
+ // Topics with message_count < 3 and updated_at > 48h ago
75
+ try {
76
+ const orphans = libraryDb.prepare(`
77
+ SELECT id, name, message_count, updated_at FROM topics
78
+ WHERE message_count < 3
79
+ AND updated_at < datetime('now', '-48 hours')
80
+ `).all();
81
+ result.orphansFound = orphans.length;
82
+ if (orphans.length > 0) {
83
+ console.log(`[lint] ${orphans.length} orphan topic(s) found (< 3 messages, stale > 48h): ${orphans.map(o => o.name).join(', ')}`);
84
+ }
85
+ }
86
+ catch {
87
+ // Non-fatal
88
+ }
89
+ // ── 3. Coverage gaps ───────────────────────────────────────────
90
+ // Topics with >= 20 messages but no synthesis in knowledge table
91
+ try {
92
+ const bigTopics = libraryDb.prepare(`
93
+ SELECT t.name, t.agent_id
94
+ FROM topics t
95
+ WHERE t.message_count >= 20
96
+ `).all();
97
+ for (const topic of bigTopics) {
98
+ const synthesis = libraryDb.prepare(`
99
+ SELECT id FROM knowledge
100
+ WHERE agent_id = ?
101
+ AND domain = 'topic-synthesis'
102
+ AND key = ?
103
+ AND superseded_by IS NULL
104
+ LIMIT 1
105
+ `).get(topic.agent_id, topic.name);
106
+ if (!synthesis) {
107
+ result.coverageGaps.push(topic.name);
108
+ }
109
+ }
110
+ }
111
+ catch {
112
+ // Non-fatal
113
+ }
114
+ return result;
115
+ }
116
+ //# sourceMappingURL=knowledge-lint.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperMem Knowledge Store
2
+ * hypermem Knowledge Store
3
3
  *
4
4
  * Long-term structured knowledge — replaces MEMORY.md.
5
5
  * Lives in the central library DB.
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge-store.d.ts","sourceRoot":"","sources":["../src/knowledge-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAuB5C,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;AAE5F,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAE7C;;;;;;;;;;;;OAYG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,SAAS;IAsEZ;;OAEG;IACH,SAAS,CACP,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,SAAS,EAAE;IAyBd;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAUnE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE;IAUrE;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,SAAS,EAAE;IAavE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IAUrC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAO/D;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAOjC;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;CAwDlF"}
1
+ {"version":3,"file":"knowledge-store.d.ts","sourceRoot":"","sources":["../src/knowledge-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAwB5C,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;AAE5F,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAE7C;;;;;;;;;;;;OAYG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,SAAS;IA4EZ;;OAEG;IACH,SAAS,CACP,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,SAAS,EAAE;IAyBd;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAUnE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE;IAUrE;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,SAAS,EAAE;IAavE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IAUrC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAO/D;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAOjC;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;CAwDlF"}
@@ -1,11 +1,12 @@
1
1
  /**
2
- * HyperMem Knowledge Store
2
+ * hypermem Knowledge Store
3
3
  *
4
4
  * Long-term structured knowledge — replaces MEMORY.md.
5
5
  * Lives in the central library DB.
6
6
  * Knowledge entries are keyed (domain + key), versioned via superseded_by,
7
7
  * and linked to each other via knowledge_links.
8
8
  */
9
+ import { isSafeForSharedVisibility, requiresScan } from './secret-scanner.js';
9
10
  function nowIso() {
10
11
  return new Date().toISOString();
11
12
  }
@@ -47,7 +48,12 @@ export class KnowledgeStore {
47
48
  const now = nowIso();
48
49
  const sourceType = opts?.sourceType || 'manual';
49
50
  const confidence = opts?.confidence ?? 1.0;
50
- const visibility = opts?.visibility ?? 'private';
51
+ // Secret gate: if requested visibility is shared, verify content is clean.
52
+ // Downgrade to 'private' rather than reject — matches episode-store pattern.
53
+ let visibility = opts?.visibility ?? 'private';
54
+ if (requiresScan(visibility) && !isSafeForSharedVisibility(content)) {
55
+ visibility = 'private';
56
+ }
51
57
  // Find current active entry (not superseded, not expired)
52
58
  const existing = this.db.prepare(`
53
59
  SELECT * FROM knowledge