total-recall-brain 3.44.0 → 3.45.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.
Files changed (212) hide show
  1. package/bin/total-recall.mjs +6 -4
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +13 -8
  4. package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
  5. package/frontend/dist/index.html +1 -1
  6. package/frontend/src/App.tsx +2 -2
  7. package/frontend/src/pages/PluginsPage.tsx +1 -1
  8. package/package.json +1 -1
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  11. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  12. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  14. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  15. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  16. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  17. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
  20. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  21. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  22. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
  25. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  26. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
  27. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  33. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
  34. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  38. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  42. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
  44. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
  45. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
  46. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
  68. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
  69. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
  70. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  71. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
  72. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  73. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  74. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
  75. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
  76. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
  77. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
  78. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
  79. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
  80. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  81. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
  82. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
  84. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
  85. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
  86. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  87. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
  88. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  89. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  90. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
  91. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
  93. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
  96. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
  97. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
  98. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  99. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
  101. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  105. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  106. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  108. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  109. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  110. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
  111. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  112. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
  113. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  114. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
  115. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
  122. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
  123. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  124. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  125. package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
  126. package/src/cli/agent.mjs +1 -1
  127. package/src/cli/command.mjs +41 -2
  128. package/src/cli/dream.mjs +93 -69
  129. package/src/cli/harness.mjs +4 -4
  130. package/src/cli/help.mjs +1 -1
  131. package/src/cli/init.mjs +0 -25
  132. package/src/cli/lessons.mjs +51 -0
  133. package/src/cli/memory-plugin.mjs +1 -1
  134. package/src/cli/plugin/copies.mjs +64 -14
  135. package/src/cli/plugin/create.mjs +6 -7
  136. package/src/cli/plugin/index.mjs +11 -7
  137. package/src/cli/plugin/install.mjs +10 -15
  138. package/src/cli/plugin/remove.mjs +1 -1
  139. package/src/cli/plugin/search.mjs +2 -2
  140. package/src/cli/preflight.mjs +19 -6
  141. package/src/cli/proposals.mjs +1 -1
  142. package/src/cli/recall.mjs +66 -23
  143. package/src/cli/remember.mjs +3 -14
  144. package/src/cli/server.mjs +18 -0
  145. package/src/cli/startup.mjs +1 -0
  146. package/src/cli/task.mjs +9 -2
  147. package/src/cli/update.mjs +6 -2
  148. package/src/core/access-ledger.mjs +78 -0
  149. package/src/core/agent-manager.mjs +2 -2
  150. package/src/core/brain-locate.mjs +15 -0
  151. package/src/core/brain-updates.mjs +31 -11
  152. package/src/core/claude-hooks.mjs +3 -1
  153. package/src/core/cleanup-task.mjs +15 -0
  154. package/src/core/cli-commands.mjs +2 -0
  155. package/src/core/cli-maintenance.mjs +4 -1
  156. package/src/core/cli-semantic-index.mjs +23 -3
  157. package/src/core/context-compiler.mjs +20 -0
  158. package/src/core/daemon-loop.mjs +39 -62
  159. package/src/core/default-plugins.mjs +1 -1
  160. package/src/core/dream.mjs +33 -105
  161. package/src/core/ensure-dependencies.mjs +148 -0
  162. package/src/core/fast-recall.mjs +2 -2
  163. package/src/core/field-evaluation.mjs +23 -0
  164. package/src/core/gate-mode.mjs +1 -1
  165. package/src/core/harness-hooks.mjs +14 -2
  166. package/src/core/lesson-hooks.mjs +74 -0
  167. package/src/core/lesson-memory.mjs +24 -0
  168. package/src/core/lesson-sessions.mjs +52 -0
  169. package/src/core/lessons.mjs +96 -0
  170. package/src/core/meta-harness.mjs +2 -2
  171. package/src/core/optimizer.mjs +66 -165
  172. package/src/core/plugin-contracts/card.mjs +3 -0
  173. package/src/core/plugin-loader.mjs +12 -3
  174. package/src/core/plugin-runtime.mjs +40 -1
  175. package/src/core/plugin-store.mjs +35 -23
  176. package/src/core/post-mortem.mjs +52 -36
  177. package/src/core/preflight-gate.mjs +23 -2
  178. package/src/core/proposal-applier.mjs +31 -2
  179. package/src/core/repo-sync.mjs +4 -6
  180. package/src/core/required-plugins.mjs +26 -4
  181. package/src/core/rule-length.mjs +23 -0
  182. package/src/core/runtime.mjs +1 -1
  183. package/src/core/scheduler.mjs +35 -43
  184. package/src/core/schema.mjs +1 -2
  185. package/src/core/search.mjs +4 -3
  186. package/src/core/server-service.mjs +148 -0
  187. package/src/core/session-watcher.mjs +19 -1
  188. package/src/core/skill-gap.mjs +16 -0
  189. package/src/core/ssss-kernel-bridge.mjs +32 -4
  190. package/src/core/startup-health.mjs +6 -1
  191. package/src/core/surface.mjs +65 -54
  192. package/src/core/task-executors.mjs +54 -106
  193. package/src/core/validated-write.mjs +1 -0
  194. package/src/core/vector-field.mjs +134 -192
  195. package/src/server/invoke-core-child.mjs +24 -0
  196. package/src/server/invoke-core-operations.mjs +102 -0
  197. package/src/server/route-manifest.json +0 -56
  198. package/src/server/routes/brains.mjs +0 -18
  199. package/src/server/routes/context.mjs +0 -41
  200. package/src/server/routes/invoke.mjs +26 -1
  201. package/src/server/routes/ssss.mjs +0 -169
  202. package/src/server/routes/system.mjs +0 -15
  203. package/src/server/routes/tasks.mjs +0 -26
  204. package/src/server/routes/vault.mjs +2 -23
  205. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  206. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  207. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  208. package/src/core/append-log.mjs +0 -131
  209. package/src/core/plugin-tasks.mjs +0 -173
  210. package/templates/onboarding-interview.md +0 -82
  211. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
  212. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
@@ -20,11 +20,13 @@
20
20
 
21
21
  import fs from 'node:fs';
22
22
  import path from 'node:path';
23
- import { getNodes } from './vault-cache.mjs';
24
- import { getEmbedding, cosineSimilarity, nodeToEmbedText, loadEmbeddingsIndex, saveEmbeddingToIndex } from './embeddings.mjs';
23
+ import { loadNodes } from './vault.mjs';
24
+ import { getEmbedding, loadEmbeddingsIndex } from './embeddings.mjs';
25
25
  import { inferMemoryLayer } from './memory-layers.mjs';
26
26
  import { logger } from './logger.mjs';
27
27
  import { brainDir } from './config.mjs';
28
+ import { readAccess, recordAccess, lastUsedMs, activeDays, activeDaysSince } from './access-ledger.mjs';
29
+ import { atomicWrite } from './vault-files.mjs';
28
30
 
29
31
  const DERIVED_DIR = path.join(brainDir, 'memory-derived');
30
32
 
@@ -32,10 +34,6 @@ function fieldPath(derivedDir = DERIVED_DIR) {
32
34
  return path.join(derivedDir, 'vector-field.json');
33
35
  }
34
36
 
35
- function queryLogPath(derivedDir = DERIVED_DIR) {
36
- return path.join(derivedDir, 'query-log.jsonl');
37
- }
38
-
39
37
  const LN2 = Math.log(2);
40
38
 
41
39
  // ─── Half-Life Constants (milliseconds) ─────────────────────────────────────
@@ -74,12 +72,14 @@ const HALF_LIFE_TABLE = {
74
72
  */
75
73
  function computeHalfLife(node) {
76
74
  // Priority overrides category
77
- if (node.priority === 'absolute') return Infinity;
75
+ if (node.priority === 'absolute' || HALF_LIFE_TABLE[node.category] === Infinity) return Infinity;
76
+ const configuredDays = Number(node.decay?.half_life_days);
77
+ const categoryHL = configuredDays > 0 ? configuredDays * 86400000 :
78
+ (HALF_LIFE_TABLE[node.category] || HALF_LIFE_TABLE.fact);
78
79
  if (node.priority === 'high') {
79
- const categoryHL = HALF_LIFE_TABLE[node.category] || HALF_LIFE_TABLE.fact;
80
80
  return Math.max(categoryHL, HALF_LIFE_TABLE._high);
81
81
  }
82
- return HALF_LIFE_TABLE[node.category] || HALF_LIFE_TABLE.fact;
82
+ return categoryHL;
83
83
  }
84
84
 
85
85
  /**
@@ -120,7 +120,7 @@ function computeNovelty(accessCount) {
120
120
  /**
121
121
  * Compile the full vector field from the vault.
122
122
  *
123
- * 1. Embed every active node → position vectors
123
+ * 1. Read existing active-node embeddings → position vectors
124
124
  * 2. Compute half-life, trajectory, novelty per point
125
125
  * 3. Compute full pairwise covariance → coupling matrix
126
126
  * 4. Extract dependency graph
@@ -128,133 +128,97 @@ function computeNovelty(accessCount) {
128
128
  */
129
129
  export async function compileField({ vaultDir, derivedDir = DERIVED_DIR } = {}) {
130
130
  const startTime = Date.now();
131
- const allNodes = getNodes(vaultDir).filter(n => n.status === 'active');
131
+ const allNodes = loadNodes(vaultDir || path.join(brainDir, 'memory-vault')).filter(n => n.status === 'active');
132
132
  const totalNodes = allNodes.length;
133
133
  const now = Date.now();
134
134
 
135
135
  logger.info('vector-field', `Compiling field from ${totalNodes} nodes...`);
136
136
 
137
- // ── Load query log for trajectory computation ──
138
- const queryHistory = loadQueryHistory(derivedDir);
137
+ const ledger = readAccess(derivedDir);
139
138
 
140
- // ── Phase 1: Embed all nodes + compute physics ──
141
- const existingIndex = loadEmbeddingsIndex(derivedDir);
139
+ // ── Phase 1: Read stored vectors and compute physics ──
140
+ const existingIndex = fs.existsSync(path.join(derivedDir, 'embeddings.db')) ? loadEmbeddingsIndex(derivedDir) : {};
142
141
  const points = [];
143
- const newlyEmbedded = new Map();
144
- let embedded = 0;
145
142
  let cached = 0;
146
-
147
- const EMBED_CONCURRENCY = 5;
148
- let cursor = 0;
149
-
150
- async function embedWorker() {
151
- while (cursor < allNodes.length) {
152
- const i = cursor++;
153
- const node = allNodes[i];
154
- const text = nodeToEmbedText(node);
155
-
156
- let embedding;
157
- if (existingIndex[node.slug]?.embedding) {
158
- embedding = existingIndex[node.slug].embedding;
159
- cached++;
160
- } else {
161
- try {
162
- embedding = await getEmbedding(text);
163
- newlyEmbedded.set(node.slug, embedding);
164
- embedded++;
165
- } catch (err) {
166
- logger.error('vector-field', `Failed to embed ${node.slug}: ${err.message}`);
167
- continue;
168
- }
169
- }
170
-
171
- // ── Physics computation ──
172
- const decay = node.decay || {};
173
- const accessCount = decay.access_count || 0;
174
- const lastAccessed = node.last_accessed ? new Date(node.last_accessed).getTime() : 0;
175
- const createdAt = node.created ? new Date(node.created).getTime() : now;
176
- const ageMs = now - createdAt;
177
-
178
- // Half-life
179
- const halfLife = computeHalfLife(node);
180
- const lambda = decayConstant(halfLife);
181
-
182
- // Velocity: dR/dt from query log
183
- const { velocity, trajectory } = computeTrajectory(node.slug, queryHistory, now);
184
-
185
- // Novelty
186
- const novelty = computeNovelty(accessCount);
187
-
188
- // Peak relevance (baseline from importance + priority)
189
- const importanceWeight = (node.importance || 3) / 5;
190
- const priorityWeight = node.priority === 'absolute' ? 1.0 : node.priority === 'high' ? 0.8 : node.priority === 'normal' ? 0.5 : 0.3;
191
- const peakRelevance = Math.max(importanceWeight, priorityWeight);
192
-
193
- // Time since last access (for decay computation at sample time)
194
- const timeSinceAccess = lastAccessed > 0 ? now - lastAccessed : ageMs;
195
-
196
- // Dependencies: extract from node.related field
197
- const dependencies = node.related ? (Array.isArray(node.related) ? node.related : String(node.related).split(',').map(s => s.trim())) : [];
198
-
199
- // Precompute L2 norm
200
- let mag = 0;
201
- for (let j = 0; j < embedding.length; j++) mag += embedding[j] * embedding[j];
202
- mag = Math.sqrt(mag);
203
-
204
- points.push({
205
- slug: node.slug,
206
- title: node.title || node.slug,
207
- category: node.category || 'unknown',
208
- layer: inferMemoryLayer(node),
209
- embedding,
210
- magnitude: mag,
211
- // ── Physics ──
212
- half_life: isFinite(halfLife) ? halfLife : null, // null = infinite
213
- lambda, // decay constant
214
- peak_relevance: peakRelevance, // R₀
215
- velocity, // dR/dt
216
- trajectory, // d²R/dt² (acceleration)
217
- novelty, // information density
218
- // ── Metadata ──
219
- access_count: accessCount,
220
- last_accessed: lastAccessed,
221
- created_at: createdAt,
222
- time_since_access: timeSinceAccess,
223
- importance: node.importance || 3,
224
- priority: node.priority || 'normal',
225
- dependencies,
226
- body_length: (node.body || node.content || '').length,
227
- });
143
+ let missing = 0;
144
+ let dimensions = null;
145
+
146
+ for (const node of allNodes) {
147
+ const embedding = existingIndex[node.slug]?.embedding;
148
+ if (!Array.isArray(embedding) || !embedding.length || !embedding.every(Number.isFinite) ||
149
+ !embedding.some(value => value !== 0) || (dimensions !== null && embedding.length !== dimensions)) {
150
+ missing++;
151
+ continue;
228
152
  }
229
- }
230
-
231
- const workers = Array.from(
232
- { length: Math.min(EMBED_CONCURRENCY, allNodes.length) },
233
- () => embedWorker()
234
- );
235
- await Promise.all(workers);
236
-
237
- for (const [slug, embedding] of newlyEmbedded) {
238
- saveEmbeddingToIndex(derivedDir, slug, embedding);
153
+ dimensions ??= embedding.length;
154
+ cached++;
155
+
156
+ // ── Physics computation ──
157
+ const { accessCount, lastAccessed, elapsedMs, velocity, trajectory } = accessPhysics(node, ledger, now);
158
+ const createdAt = new Date(node.created || now).getTime();
159
+ const halfLife = computeHalfLife(node);
160
+ const lambda = decayConstant(halfLife);
161
+
162
+ // Novelty
163
+ const novelty = computeNovelty(accessCount);
164
+
165
+ // Peak relevance (baseline from importance + priority)
166
+ const importanceWeight = (node.importance || 3) / 5;
167
+ const priorityWeight = node.priority === 'absolute' ? 1.0 : node.priority === 'high' ? 0.8 : node.priority === 'normal' ? 0.5 : 0.3;
168
+ const peakRelevance = Math.max(importanceWeight, priorityWeight);
169
+
170
+ // Time since last access (for decay computation at sample time)
171
+ const timeSinceAccess = elapsedMs;
172
+
173
+ // Dependencies: extract from node.related field
174
+ const dependencies = node.related ? (Array.isArray(node.related) ? node.related : String(node.related).split(',').map(s => s.trim())) : [];
175
+
176
+ // Precompute L2 norm
177
+ let mag = 0;
178
+ for (let j = 0; j < embedding.length; j++) mag += embedding[j] * embedding[j];
179
+ mag = Math.sqrt(mag);
180
+
181
+ points.push({
182
+ slug: node.slug,
183
+ title: node.title || node.slug,
184
+ category: node.category || 'unknown',
185
+ layer: inferMemoryLayer(node),
186
+ embedding,
187
+ magnitude: mag,
188
+ // ── Physics ──
189
+ half_life: isFinite(halfLife) ? halfLife : null, // null = infinite
190
+ lambda, // decay constant
191
+ peak_relevance: peakRelevance, // R₀
192
+ velocity, // dR/dt
193
+ trajectory, // d²R/dt² (acceleration)
194
+ novelty, // information density
195
+ // ── Metadata ──
196
+ access_count: accessCount,
197
+ last_accessed: lastAccessed,
198
+ created_at: createdAt,
199
+ time_since_access: timeSinceAccess,
200
+ importance: node.importance || 3,
201
+ priority: node.priority || 'normal',
202
+ dependencies,
203
+ body_length: (node.body || node.content || '').length,
204
+ });
239
205
  }
240
206
 
241
207
  const embedMs = Date.now() - startTime;
242
- logger.info('vector-field', `Embedded ${embedded} new + ${cached} cached in ${embedMs}ms`);
208
+ logger.info('vector-field', `Loaded ${cached} existing vectors; ${missing} missing or incompatible`);
243
209
 
244
210
  // ── Phase 2: Pairwise covariance matrix ──
245
211
  const N = points.length;
246
212
  const slugIndex = points.map(p => p.slug);
247
- const covariance = new Array(N);
213
+ const covariance = Array.from({ length: N }, () => new Float32Array(N));
248
214
 
249
215
  for (let i = 0; i < N; i++) {
250
- covariance[i] = new Float32Array(N);
251
216
  for (let j = i; j < N; j++) {
252
217
  if (i === j) {
253
218
  covariance[i][j] = 1.0;
254
219
  } else {
255
220
  const sim = fastCosine(points[i].embedding, points[j].embedding, points[i].magnitude, points[j].magnitude);
256
221
  covariance[i][j] = sim;
257
- if (!covariance[j]) covariance[j] = new Float32Array(N);
258
222
  covariance[j][i] = sim;
259
223
  }
260
224
  }
@@ -274,7 +238,8 @@ export async function compileField({ vaultDir, derivedDir = DERIVED_DIR } = {})
274
238
  meta: {
275
239
  total_nodes: totalNodes,
276
240
  points_compiled: N,
277
- embedded_new: embedded,
241
+ embedded_new: 0,
242
+ missing_embeddings: missing,
278
243
  embedded_cached: cached,
279
244
  embed_ms: embedMs,
280
245
  covariance_ms: covMs,
@@ -289,54 +254,51 @@ export async function compileField({ vaultDir, derivedDir = DERIVED_DIR } = {})
289
254
 
290
255
  const outPath = fieldPath(derivedDir);
291
256
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
292
- fs.writeFileSync(outPath, JSON.stringify(field));
257
+ atomicWrite(outPath, JSON.stringify(field));
293
258
 
294
259
  logger.info('vector-field', `Field compiled: ${N} points, ${N * N} couplings, ${field.meta.infinite_halflife} immortal, ${field.meta.with_dependencies} with deps, ${field.meta.compile_ms}ms`);
295
260
 
296
261
  return field;
297
262
  }
298
263
 
299
- // ─── Trajectory Computation ─────────────────────────────────────────────────
300
-
301
- /**
302
- * Compute velocity (dR/dt) and trajectory (d²R/dt²) from query log.
303
- * Uses 3 time windows to detect acceleration/deceleration.
304
- */
305
- function computeTrajectory(slug, queryHistory, now) {
306
- const WINDOW = 24 * 60 * 60 * 1000; // 1 day
307
-
308
- // Count hits in 3 windows: [0-24h], [24-48h], [48-72h]
309
- let w0 = 0, w1 = 0, w2 = 0;
310
- for (const entry of queryHistory) {
311
- if (!entry.hits?.includes(slug)) continue;
312
- const age = now - entry.t;
313
- if (age < WINDOW) w0++;
314
- else if (age < 2 * WINDOW) w1++;
315
- else if (age < 3 * WINDOW) w2++;
316
- }
317
-
318
- // Velocity = first derivative (recent vs previous)
319
- const velocity = (w0 - w1) * 0.1;
320
-
321
- // Trajectory = second derivative (acceleration of velocity)
322
- const v_prev = (w1 - w2) * 0.1;
323
- const trajectory = velocity - v_prev; // positive = accelerating, negative = decelerating
324
-
325
- return {
326
- velocity: Math.max(-1, Math.min(1, velocity)),
327
- trajectory: Math.max(-1, Math.min(1, trajectory)),
328
- };
264
+ /** Read live usage. Old ledgers have no daily counts, so their trend is unknown (zero). */
265
+ function accessPhysics(node, ledger, now) {
266
+ const entry = ledger[node.slug];
267
+ const accessCount = Math.max(0, Number(entry?.count ?? node.access_count ?? node.decay?.access_count) || 0);
268
+ const lastAccessed = lastUsedMs(node, ledger);
269
+ const days = activeDays(ledger).filter(day => day <= new Date(now).toISOString().slice(0, 10));
270
+ const counts = [0, 1, 2].map(offset => Number(entry?.daily_counts?.[days.at(-1 - offset)]) || 0);
271
+ const clamp = value => Math.max(-1, Math.min(1, value));
272
+ return { accessCount, lastAccessed,
273
+ elapsedMs: days.length ? activeDaysSince({ _active_days: days }, lastAccessed) * 86400000 : Math.max(0, now - lastAccessed),
274
+ velocity: clamp((counts[0] - counts[1]) * .1),
275
+ trajectory: clamp((counts[0] - 2 * counts[1] + counts[2]) * .1) };
329
276
  }
330
277
 
331
- function loadQueryHistory(derivedDir = DERIVED_DIR) {
332
- try {
333
- const logPath = queryLogPath(derivedDir);
334
- if (!fs.existsSync(logPath)) return [];
335
- const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n');
336
- const cutoff = Date.now() - (3 * 24 * 60 * 60 * 1000); // only last 3 days
337
- return lines.map(l => { try { return JSON.parse(l); } catch { return null; } })
338
- .filter(e => e && e.t > cutoff);
339
- } catch { return []; }
278
+ /** Re-rank only recall candidates. No provider call or access write. */
279
+ export function rankWithField(results, { field, derivedDir = DERIVED_DIR, now = Date.now() } = {}) {
280
+ if (!field) throw new Error('Vector field not compiled. Run total-recall recall --compile-field first.');
281
+ const ledger = readAccess(derivedDir);
282
+ const indexes = new Map(field.points.map((point, index) => [point.slug, index]));
283
+ const ranked = results.map(result => {
284
+ const index = result.type === 'session' ? undefined : indexes.get(result.slug);
285
+ if (index === undefined) return result;
286
+ const point = field.points[index];
287
+ const physics = accessPhysics({ ...point, last_accessed: point.last_accessed, created: point.created_at }, ledger, now);
288
+ const relevance = effectiveRelevance(point.peak_relevance, point.lambda, physics.elapsedMs, physics.trajectory);
289
+ let coupling = 0, peers = 0;
290
+ for (const candidate of results) {
291
+ const other = candidate.type === 'session' ? undefined : indexes.get(candidate.slug);
292
+ if (other === undefined || other === index) continue;
293
+ coupling += Math.max(0, field.covariance[index][other]) * candidate.score;
294
+ peers++;
295
+ }
296
+ // The base score remains the main signal. Missing vectors and sessions retain their score.
297
+ const score = result.score * (.7 + .3 * relevance) + .05 * physics.velocity +
298
+ .05 * computeNovelty(physics.accessCount) + .15 * (peers ? coupling / peers : 0);
299
+ return { ...result, base_score: result.score, score: round4(score), field_score: round4(score) };
300
+ });
301
+ return ranked.sort((a, b) => b.score - a.score);
340
302
  }
341
303
 
342
304
  // ─── Fast Cosine ────────────────────────────────────────────────────────────
@@ -381,7 +343,14 @@ export async function sampleField({
381
343
  if (!field) throw new Error('Vector field not compiled. Run compileField() first.');
382
344
  }
383
345
 
384
- const { points, covariance, slugIndex } = field;
346
+ const ledger = readAccess(derivedDir);
347
+ const points = field.points.map(point => {
348
+ const usage = accessPhysics({ ...point, created: point.created_at }, ledger, now);
349
+ return { ...point, velocity: usage.velocity, trajectory: usage.trajectory,
350
+ novelty: computeNovelty(usage.accessCount), access_count: usage.accessCount,
351
+ last_accessed: usage.lastAccessed, time_since_access: usage.elapsedMs };
352
+ });
353
+ const { covariance } = field;
385
354
  const N = points.length;
386
355
  if (N === 0) return { results: [], query_embedding: [], sample_ms: 0, interference: [] };
387
356
 
@@ -401,7 +370,7 @@ export async function sampleField({
401
370
  const decayedScores = new Float64Array(N);
402
371
  for (let i = 0; i < N; i++) {
403
372
  const p = points[i];
404
- const elapsed = p.last_accessed > 0 ? now - p.last_accessed : p.time_since_access || 0;
373
+ const elapsed = p.time_since_access || 0;
405
374
  const decayedRelevance = effectiveRelevance(p.peak_relevance, p.lambda, elapsed, p.trajectory);
406
375
  // Blend: 70% similarity, 30% decayed relevance
407
376
  decayedScores[i] = (directScores[i] * 0.7) + (decayedRelevance * 0.3);
@@ -460,7 +429,7 @@ export async function sampleField({
460
429
  // ── Format results ──
461
430
  const results = topResults.map(({ index, score }) => {
462
431
  const p = points[index];
463
- const elapsed = p.last_accessed > 0 ? now - p.last_accessed : p.time_since_access || 0;
432
+ const elapsed = p.time_since_access || 0;
464
433
  const remainingHL = p.lambda > 0 ? Math.round((LN2 / p.lambda) / (24 * 60 * 60 * 1000)) : null;
465
434
  return {
466
435
  slug: p.slug,
@@ -485,7 +454,7 @@ export async function sampleField({
485
454
  });
486
455
 
487
456
  const sampleMs = Date.now() - startTime;
488
- logQuery(query, results.map(r => r.slug), derivedDir);
457
+ recordAccess(derivedDir, results.map(r => r.slug));
489
458
 
490
459
  logger.info('vector-field', `Sampled in ${sampleMs}ms: ${results.length} results, ${depsPulledIn.length} deps pulled, ${interference.length} interference pairs`);
491
460
 
@@ -557,52 +526,25 @@ function round4(n) { return Math.round(n * 10000) / 10000; }
557
526
  // ─── Field Loading ──────────────────────────────────────────────────────────
558
527
 
559
528
  let _cachedField = null;
560
- let _cachedFieldMtime = 0;
529
+ let _cachedFieldStamp = null;
561
530
 
562
531
  export function loadField(derivedDir = DERIVED_DIR) {
563
532
  try {
564
533
  const fp = fieldPath(derivedDir);
565
534
  if (!fs.existsSync(fp)) return null;
566
535
  const stat = fs.statSync(fp);
567
- if (_cachedField && stat.mtimeMs === _cachedFieldMtime) return _cachedField;
536
+ const stamp = `${path.resolve(fp)}:${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`;
537
+ if (_cachedField && stamp === _cachedFieldStamp) return _cachedField;
568
538
  const raw = JSON.parse(fs.readFileSync(fp, 'utf8'));
539
+ if (!Array.isArray(raw.points) || !Array.isArray(raw.covariance) ||
540
+ raw.covariance.length !== raw.points.length || raw.covariance.some(row =>
541
+ !Array.isArray(row) || row.length !== raw.points.length || !row.every(Number.isFinite))) return null;
569
542
  _cachedField = raw;
570
- _cachedFieldMtime = stat.mtimeMs;
543
+ _cachedFieldStamp = stamp;
571
544
  return raw;
572
545
  } catch { return null; }
573
546
  }
574
547
 
575
- // ─── Query Logging ──────────────────────────────────────────────────────────
576
-
577
- function logQuery(query, hitSlugs, derivedDir = DERIVED_DIR) {
578
- try {
579
- const logPath = queryLogPath(derivedDir);
580
- fs.mkdirSync(path.dirname(logPath), { recursive: true });
581
- fs.appendFileSync(logPath, JSON.stringify({ t: Date.now(), q: query.slice(0, 200), hits: hitSlugs.slice(0, 20) }) + '\n');
582
- } catch { /* fire and forget */ }
583
- }
584
-
585
- // ─── Velocity Recomputation ─────────────────────────────────────────────────
586
-
587
- /**
588
- * Recompute velocity + trajectory vectors from query log.
589
- * Call periodically (dream cycle) to update the field in-place.
590
- */
591
- export function recomputeVelocities(field, derivedDir = DERIVED_DIR) {
592
- if (!field) return field;
593
- const now = Date.now();
594
- const history = loadQueryHistory(derivedDir);
595
-
596
- for (const point of field.points) {
597
- const { velocity, trajectory } = computeTrajectory(point.slug, history, now);
598
- point.velocity = velocity;
599
- point.trajectory = trajectory;
600
- point.novelty = computeNovelty(point.access_count);
601
- }
602
-
603
- return field;
604
- }
605
-
606
548
  // ─── Field Statistics ───────────────────────────────────────────────────────
607
549
 
608
550
  export function fieldStats(derivedDir = DERIVED_DIR) {
@@ -0,0 +1,24 @@
1
+ /** Full-mode owner work shares invoke's process-group and output limits. */
2
+ import path from 'node:path';
3
+ console.log = console.info = console.debug = (...args) => console.error(...args);
4
+
5
+ try {
6
+ let input = '';
7
+ for await (const chunk of process.stdin) {
8
+ input += chunk;
9
+ if (Buffer.byteLength(input) > 32768) throw new Error('Input limit');
10
+ }
11
+ const { operation, params, brain, registryBrain } = JSON.parse(input);
12
+ // Config and Auth must use the serving brain, not a brain inferred from cwd.
13
+ process.env.AGENT_DIR = path.dirname(path.dirname(brain));
14
+ process.env.TR_SECRETS_BRAIN = brain;
15
+ const { executeCoreOperation } = await import('./invoke-core-operations.mjs');
16
+ const result = await executeCoreOperation(operation, params, { brain, registryBrain });
17
+ process.stdout.write(JSON.stringify({ ok: true, result }));
18
+ } catch (err) {
19
+ // Return curated policy codes only. Never return owner exception text.
20
+ const { InvokeError } = await import('./invoke-execution.mjs');
21
+ process.stdout.write(JSON.stringify({ ok: false, error: err instanceof InvokeError ? err.code : 'operation-failed',
22
+ status: err instanceof InvokeError ? err.status : 502 }));
23
+ process.exitCode = 1;
24
+ }
@@ -0,0 +1,102 @@
1
+ /** Owner adapters for the retired one-off remote routes. */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import matter from '../core/frontmatter.mjs';
5
+ import { readRegisteredProjects } from '../core/brain-registry.mjs';
6
+ import { InvokeError } from './invoke-execution.mjs';
7
+
8
+ export const CORE_OPERATIONS = Object.freeze({
9
+ 'context.preview': { scope: 'memory:read', family: 'context' },
10
+ 'context.stream': { scope: 'memory:read', family: 'context' },
11
+ 'context.flash.health': { scope: 'memory:read', family: 'context' },
12
+ 'brains.sync': { scope: 'memory:write', family: 'brains' },
13
+ 'tasks.cleanup': { scope: 'tasks:write', family: 'tasks' },
14
+ 'tasks.failed': { scope: 'tasks:read', family: 'tasks' },
15
+ 'vault.hash': { scope: 'memory:read', family: 'vault' },
16
+ });
17
+
18
+ export function validateCoreParams(operation, params) {
19
+ if (params.timeoutMs !== undefined && (!Number.isFinite(Number(params.timeoutMs)) || Number(params.timeoutMs) <= 0)) {
20
+ throw new InvokeError(400, 'invalid-timeout', 'timeoutMs must be positive and finite.');
21
+ }
22
+ const allowed = new Set(['target', 'timeoutMs']);
23
+ if (!['brains.sync', 'context.flash.health'].includes(operation)) allowed.add('brainId');
24
+ if (operation === 'context.stream') for (const key of ['query', 'budget_tokens', 'concurrency', 'min_score']) allowed.add(key);
25
+ if (Object.keys(params).some(key => !allowed.has(key))) throw new InvokeError(400, 'unknown-parameter', 'Unsupported operation parameter.');
26
+ if (params.brainId !== undefined && (typeof params.brainId !== 'string' || !params.brainId || params.brainId.includes(','))) {
27
+ throw new InvokeError(400, 'invalid-brain', 'brainId must select one brain.');
28
+ }
29
+ if (params.brainId !== undefined && !['server', 'global'].includes(params.brainId) &&
30
+ !/^project:[^/\\\0,]+$/.test(params.brainId)) {
31
+ throw new InvokeError(400, 'invalid-brain', 'brainId must select the server or a registered project.');
32
+ }
33
+ if (operation === 'context.stream') {
34
+ if (typeof params.query !== 'string' || !params.query.trim()) throw new InvokeError(400, 'invalid-query', 'query is required.');
35
+ for (const [key, min, max] of [['budget_tokens', 1, 65536], ['concurrency', 1, 4], ['min_score', 0, 100]]) {
36
+ if (params[key] !== undefined && (!Number.isInteger(params[key]) || params[key] < min || params[key] > max)) {
37
+ throw new InvokeError(400, 'invalid-parameter', `Invalid ${key}.`);
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ function selectedBrain(brainId, brain, registryBrain) {
44
+ if (brainId === undefined || brainId === 'server' || brainId === 'global') return brain;
45
+ if (brainId.startsWith('project:')) {
46
+ const name = brainId.slice('project:'.length);
47
+ const project = readRegisteredProjects(registryBrain).find(row => row.name === name || row.project_id === name);
48
+ if (project?.brainDir && fs.existsSync(path.join(project.brainDir, 'memory-vault'))) return project.brainDir;
49
+ }
50
+ throw new InvokeError(404, 'brain-unavailable', 'Selected brain is unavailable.');
51
+ }
52
+
53
+ export async function executeCoreOperation(operation, params, { brain, registryBrain }) {
54
+ if (!Object.hasOwn(CORE_OPERATIONS, operation)) throw new InvokeError(404, 'unknown-operation', 'Unknown operation.');
55
+ validateCoreParams(operation, params);
56
+ const selected = selectedBrain(params.brainId, brain, registryBrain);
57
+ const vaultDir = path.join(selected, 'memory-vault');
58
+ switch (operation) {
59
+ case 'context.preview': {
60
+ const { previewContext } = await import('../core/context-compiler.mjs');
61
+ return previewContext({ vaultDir });
62
+ }
63
+ case 'context.stream': {
64
+ const { streamParallelContext } = await import('../core/parallel-context.mjs');
65
+ const result = await streamParallelContext({ query: params.query, vaultDir,
66
+ budgetTokens: params.budget_tokens, concurrency: params.concurrency, minScore: params.min_score });
67
+ // Provider diagnostics can contain a credential-bearing URL.
68
+ return { ...result, scored: result.scored.map(({ error, ...row }) => error ? { ...row, error: 'provider-failed' } : row) };
69
+ }
70
+ case 'context.flash.health': {
71
+ const { checkFlashHealth } = await import('../core/parallel-context.mjs');
72
+ const { error, ...result } = await checkFlashHealth();
73
+ return error ? { ...result, error: 'provider-unavailable' } : result;
74
+ }
75
+ case 'brains.sync': {
76
+ const { syncAllRepos } = await import('../core/repo-sync.mjs');
77
+ const result = await syncAllRepos({ registryBrain });
78
+ if (result.repos.some(row => row.errors)) throw new InvokeError(502, 'sync-failed', 'Some repositories failed to sync.');
79
+ return { ok: true, ...result };
80
+ }
81
+ case 'tasks.cleanup': {
82
+ const queue = path.join(selected, 'scheduler/queue');
83
+ let deleted = 0;
84
+ if (fs.existsSync(queue)) for (const entry of fs.readdirSync(queue, { withFileTypes: true })) {
85
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
86
+ const file = path.join(queue, entry.name);
87
+ const { data } = matter(fs.readFileSync(file, 'utf8'));
88
+ if (data.status === 'completed') { fs.unlinkSync(file); deleted++; }
89
+ }
90
+ return { deleted };
91
+ }
92
+ case 'tasks.failed': {
93
+ const { listTasks } = await import('../core/task-envelope.mjs');
94
+ const tasks = listTasks(path.join(selected, 'scheduler/queue'), { status: 'failed' });
95
+ return { total: tasks.length, tasks };
96
+ }
97
+ case 'vault.hash': {
98
+ const file = path.join(selected, 'memory-derived/vault-hash.txt');
99
+ return { vault_hash: fs.existsSync(file) ? fs.readFileSync(file, 'utf8').trim() : null, vault_dir: vaultDir };
100
+ }
101
+ }
102
+ }