total-recall-brain 3.43.3 → 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 (252) hide show
  1. package/bin/total-recall.mjs +6 -4
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +16 -9
  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 +3 -3
  9. package/scaffold/.agent/skills/plugin/SKILL.md +7 -2
  10. package/scaffold/.agent/skills/plugin/evals/README.md +5 -0
  11. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +1 -0
  12. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +1 -0
  13. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +1 -0
  14. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +1 -0
  15. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +1 -0
  16. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +1 -0
  17. package/scaffold/.agent/skills/plugin/evals/cases/07-missing-reuse.mjs +11 -0
  18. package/scaffold/.agent/skills/plugin/evals/cases/08-unresolved-owner-command.mjs +12 -0
  19. package/scaffold/.agent/skills/plugin/evals/cases/09-missing-brick.mjs +12 -0
  20. package/scaffold/.agent/skills/plugin/prompts/01-function.md +4 -3
  21. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +1 -0
  22. package/scaffold/.agent/skills/plugin/references/surfaces.md +12 -2
  23. package/scaffold/.agent/skills/plugin/scripts/record.mjs +59 -2
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +8 -0
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +15 -14
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -3
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-gh-login.mjs +102 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +38 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
  31. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
  33. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  34. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
  39. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  40. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
  49. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
  50. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +15 -14
  53. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -3
  54. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  55. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +15 -14
  56. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -3
  57. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  58. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +15 -14
  59. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -3
  60. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  61. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
  63. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +17 -16
  64. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +5 -4
  65. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
  87. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +15 -14
  88. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -3
  89. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  90. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +59 -10
  91. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  92. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  93. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +160 -29
  94. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +15 -14
  95. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +4 -4
  96. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +231 -16
  97. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +19 -4
  98. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  100. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +101 -16
  101. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +54 -21
  103. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +40 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +68 -13
  105. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  106. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +64 -15
  107. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  109. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +249 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +6 -12
  112. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +16 -8
  115. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +26 -2
  116. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +6 -1
  117. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +6 -2
  118. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  119. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +28 -12
  121. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
  122. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  129. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  130. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  131. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +110 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  133. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +597 -70
  134. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  135. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
  136. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
  137. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
  138. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +15 -14
  139. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -3
  140. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  141. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  142. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
  143. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +15 -14
  144. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -3
  145. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  146. package/scaffold/.agent/skills/total-recall/plugins/ssss/assets/scaffold.json +5 -5
  147. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +6 -6
  148. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -2
  149. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +1 -1
  150. package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
  151. package/src/cli/agent.mjs +1 -1
  152. package/src/cli/command.mjs +41 -2
  153. package/src/cli/connect.mjs +2 -29
  154. package/src/cli/context.mjs +1 -0
  155. package/src/cli/dream.mjs +93 -69
  156. package/src/cli/harness.mjs +4 -4
  157. package/src/cli/help.mjs +1 -1
  158. package/src/cli/ingest-okf.mjs +4 -4
  159. package/src/cli/init.mjs +0 -25
  160. package/src/cli/lessons.mjs +51 -0
  161. package/src/cli/memory-plugin.mjs +1 -1
  162. package/src/cli/plugin/copies.mjs +64 -14
  163. package/src/cli/plugin/create.mjs +6 -7
  164. package/src/cli/plugin/index.mjs +11 -7
  165. package/src/cli/plugin/install.mjs +10 -15
  166. package/src/cli/plugin/remove.mjs +1 -1
  167. package/src/cli/plugin/search.mjs +2 -2
  168. package/src/cli/preflight.mjs +19 -6
  169. package/src/cli/proposals.mjs +1 -1
  170. package/src/cli/recall.mjs +66 -23
  171. package/src/cli/release.mjs +4 -1
  172. package/src/cli/remember.mjs +3 -14
  173. package/src/cli/server.mjs +18 -0
  174. package/src/cli/startup.mjs +1 -0
  175. package/src/cli/task.mjs +9 -2
  176. package/src/cli/update.mjs +10 -3
  177. package/src/core/access-ledger.mjs +78 -0
  178. package/src/core/agent-manager.mjs +2 -2
  179. package/src/core/automatic-brain-updates.mjs +11 -6
  180. package/src/core/brain-locate.mjs +15 -0
  181. package/src/core/brain-updates.mjs +42 -12
  182. package/src/core/claude-hooks.mjs +3 -1
  183. package/src/core/cleanup-task.mjs +15 -0
  184. package/src/core/cli-commands.mjs +2 -0
  185. package/src/core/cli-maintenance.mjs +4 -1
  186. package/src/core/cli-semantic-index.mjs +23 -3
  187. package/src/core/context-compactor.mjs +7 -2
  188. package/src/core/context-compiler.mjs +26 -1
  189. package/src/core/context-policy.mjs +7 -3
  190. package/src/core/daemon-control.mjs +22 -0
  191. package/src/core/daemon-loop.mjs +39 -62
  192. package/src/core/default-plugins.mjs +1 -1
  193. package/src/core/dream.mjs +33 -105
  194. package/src/core/ensure-dependencies.mjs +148 -0
  195. package/src/core/fast-recall.mjs +2 -2
  196. package/src/core/field-evaluation.mjs +23 -0
  197. package/src/core/gate-mode.mjs +1 -1
  198. package/src/core/global-brain-updates.mjs +1 -1
  199. package/src/core/harness-hooks.mjs +14 -2
  200. package/src/core/lesson-hooks.mjs +74 -0
  201. package/src/core/lesson-memory.mjs +24 -0
  202. package/src/core/lesson-sessions.mjs +52 -0
  203. package/src/core/lessons.mjs +96 -0
  204. package/src/core/mesh-brain-updates.mjs +2 -1
  205. package/src/core/meta-harness.mjs +2 -2
  206. package/src/core/optimizer.mjs +66 -165
  207. package/src/core/plugin-contracts/card.mjs +3 -0
  208. package/src/core/plugin-loader.mjs +12 -3
  209. package/src/core/plugin-runtime.mjs +40 -1
  210. package/src/core/plugin-store.mjs +35 -23
  211. package/src/core/post-mortem.mjs +52 -36
  212. package/src/core/preflight-gate.mjs +23 -2
  213. package/src/core/proposal-applier.mjs +31 -2
  214. package/src/core/repo-sync.mjs +4 -6
  215. package/src/core/required-plugins.mjs +26 -4
  216. package/src/core/rule-length.mjs +23 -0
  217. package/src/core/runtime.mjs +1 -1
  218. package/src/core/scheduler.mjs +35 -43
  219. package/src/core/schema.mjs +1 -2
  220. package/src/core/search.mjs +4 -3
  221. package/src/core/server-restart.mjs +128 -7
  222. package/src/core/server-service.mjs +148 -0
  223. package/src/core/session-watcher.mjs +19 -1
  224. package/src/core/skill-gap.mjs +16 -0
  225. package/src/core/ssss-kernel-bridge.mjs +33 -6
  226. package/src/core/startup-health.mjs +6 -1
  227. package/src/core/surface.mjs +68 -57
  228. package/src/core/task-context.mjs +9 -8
  229. package/src/core/task-executors.mjs +54 -106
  230. package/src/core/validated-write.mjs +5 -0
  231. package/src/core/vault-files.mjs +8 -2
  232. package/src/core/vault.mjs +1 -1
  233. package/src/core/vector-field.mjs +134 -192
  234. package/src/server/index.mjs +3 -1
  235. package/src/server/invoke-core-child.mjs +24 -0
  236. package/src/server/invoke-core-operations.mjs +102 -0
  237. package/src/server/route-manifest.json +0 -56
  238. package/src/server/routes/brains.mjs +0 -18
  239. package/src/server/routes/context.mjs +0 -41
  240. package/src/server/routes/invoke.mjs +26 -1
  241. package/src/server/routes/ssss.mjs +0 -169
  242. package/src/server/routes/system.mjs +0 -15
  243. package/src/server/routes/tasks.mjs +0 -26
  244. package/src/server/routes/vault.mjs +2 -23
  245. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  246. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  247. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  248. package/src/core/append-log.mjs +0 -131
  249. package/src/core/plugin-tasks.mjs +0 -173
  250. package/templates/onboarding-interview.md +0 -82
  251. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
  252. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
@@ -2,18 +2,22 @@ import { installPlugin as install } from '../../core/plugin-distribution.mjs';
2
2
  import { syncPluginCommands } from '../command.mjs';
3
3
 
4
4
  export async function installPlugin(args = []) {
5
- const isGlobal = args.includes('--global') || args.includes('-g');
5
+ // Every plugin installs into the shared folder; `--global` is accepted and changes nothing.
6
6
  const isLink = args.includes('--link') || args.includes('-l');
7
7
  const source = args.find(a => !a.startsWith('-'));
8
8
 
9
+ if (args.includes('--project')) {
10
+ console.error("❌ Plugins are installed only in the shared folder. In a repository, switch one with 'total-recall plugin on|off <id>'.");
11
+ process.exit(1);
12
+ }
9
13
  if (!source) {
10
14
  console.error('❌ Error: Missing plugin source.');
11
- console.error(' Usage: total-recall plugin install <bundled-id | share-link | peer:<host>/<id> | git-url | path> [--link] [--global]\n');
15
+ console.error(' Usage: total-recall plugin install <bundled-id | share-link | peer:<host>/<id> | git-url | path> [--link]\n');
12
16
  process.exit(1);
13
17
  }
14
18
 
15
19
  try {
16
- const result = await install(source, { projectRoot: process.cwd(), global: isGlobal, link: isLink, replace:args.includes('--replace') });
20
+ const result = await install(source, { projectRoot: process.cwd(), link: isLink, replace:args.includes('--replace') });
17
21
  const from = result.source.kind === 'peer' ? `peer ${result.source.peer_hostname}` : result.source.kind;
18
22
  console.log(`\n✅ Installed '${result.plugin.name}' (id: ${result.plugin.id}, v${result.plugin.version}) from ${from}`);
19
23
  console.log(` Location: ${result.plugin.dir}`);
@@ -24,7 +28,7 @@ export async function installPlugin(args = []) {
24
28
  } else if (result.dependencies?.reason && !['no package.json', 'no dependencies declared'].includes(result.dependencies.reason)) {
25
29
  console.log(` Dependencies: NOT installed (${result.dependencies.reason})`);
26
30
  }
27
- const cmds = syncPluginCommands(result.plugin.dir, { global: isGlobal });
31
+ const cmds = syncPluginCommands(result.plugin.dir, { global: true });
28
32
  if (cmds.created.length) console.log(` Commands: ${cmds.created.map((c) => `total-recall ${c}`).join(', ')}`);
29
33
  for (const c of cmds.skipped) console.log(` Skipped command '${c.name}': ${c.reason}`);
30
34
  console.log('');
@@ -34,17 +38,8 @@ export async function installPlugin(args = []) {
34
38
  return;
35
39
  }
36
40
  const { autoCompileSurfaces } = await import('../../core/auto-compile.mjs');
37
- // Compile the layer the plugin actually landed in. `scope` is not enough on
38
- // its own: from a directory with no project brain, projectPluginsDir(cwd)
39
- // resolves to the global plugins dir, so a plugin installed into the global
40
- // brain still reports scope 'project'. Ask the resolver whether a project
41
- // brain exists at all, and otherwise fall back to the global layer — the one
42
- // that was really written. Assuming 'project' compiled nothing and printed
43
- // "Auto-compile failed: No project brain found" after a successful install.
44
- const { resolveBrainDir } = await import('../agent-dir.mjs');
45
- let targetLayer = 'global';
46
- try { resolveBrainDir('project'); targetLayer = 'project'; } catch { targetLayer = 'global'; }
47
- if (!await autoCompileSurfaces({ layer: targetLayer, semantic: false, force: true })) process.exitCode = 1;
41
+ // The plugin landed in the shared folder, so the global layer is the one to compile.
42
+ if (!await autoCompileSurfaces({ layer: 'global', semantic: false, force: true })) process.exitCode = 1;
48
43
  } catch (err) {
49
44
  console.error(`❌ ${err.message}`);
50
45
  process.exit(1);
@@ -8,7 +8,7 @@ export async function removePlugin(args = []) {
8
8
 
9
9
  if (!id) {
10
10
  console.error('❌ Error: Missing plugin ID to remove.');
11
- console.error(' Usage: total-recall plugin remove <id> [--global]\n');
11
+ console.error(' Usage: total-recall plugin remove <id>\n');
12
12
  process.exit(1);
13
13
  }
14
14
 
@@ -22,7 +22,7 @@ function parseArgs(args) {
22
22
  * `includes` makes a short query match inside unrelated words: searching "git"
23
23
  * returned the signing plugin, whose description begins "Digital agreement…".
24
24
  * A hit inside a word is only accepted at a word start so that real substrings
25
- * still work ("sent" finds "git-sentinel") without matching "digital".
25
+ * still work ("qual" finds "code-quality") without matching "digital".
26
26
  */
27
27
  function matches(p, query, useCase) {
28
28
  if (useCase && !(p.use_cases || []).includes(useCase)) return false;
@@ -32,7 +32,7 @@ function matches(p, query, useCase) {
32
32
  const words = query.split(/\s+/).filter(Boolean);
33
33
  return words.every((word) => {
34
34
  const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
35
- // Anchor at a word start so "sent" matches "git-sentinel" but "git" does
35
+ // Anchor at a word start so "qual" matches "code-quality" but "git" does
36
36
  // not match "digital".
37
37
  const bounded = new RegExp(`(^|[^a-z0-9])${escaped}`, 'i');
38
38
  return haystacks.some((h) => bounded.test(h) || h.startsWith(word));
@@ -1,16 +1,18 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { ensureDependencies } from '../core/ensure-dependencies.mjs';
4
5
  import { detectProjectBrain } from '../core/brain-locate.mjs';
5
6
  import { globalBrainDirFor, isBrainEnabled } from '../core/brain-registry.mjs';
6
7
  import { GATE_MODES, gateConfigPath, projectBrainDir, readGateLayers, resolveGateMode, writeGateMode } from '../core/gate-mode.mjs';
7
8
  import { installHarnessHooks, loadGateAdapters } from '../core/harness-hooks.mjs';
8
- import { ackDirectory, capsuleVerb, decide, isAckCommand, isCheckCommand, isRouteCommand, isSubagentPayload, nativeToolOf, sessionOf, SWEEP_CONSULT_MS, whenGateFails } from '../core/preflight-gate.mjs';
9
+ import { ackDirectory, capsuleVerb, decide, gatePackageBroken, isAckCommand, isCheckCommand, isRouteCommand, isSubagentPayload, nativeToolOf, sessionOf, SWEEP_CONSULT_MS, whenGateFails } from '../core/preflight-gate.mjs';
9
10
  import { ACK_COMMAND, acknowledge, checkAgentReceipt, checkAnyReceipt, findRoot, forgetCapsuleDeliveries, lastArrivalMs, lastCapsuleDelivery, lastConsultMs, lastRoutedMs, lastRouteWarnedMs, lastSessionAck, markArrival, markDelegated, markRouted, markRouteWarned, markSessionAck, noteCaller, recordDecision, sessionHasStanding, sessionKey, readDecisions, takeCaller } from '../core/preflight-receipt.mjs';
10
11
 
11
12
  const USAGE = `Usage: total-recall preflight <command>
12
13
  ack --task "<task>" [--action read,edit,test] [--json] Record a receipt after context ready:true and rules verify, and print the capsule.
13
14
  Prints one line instead when this session received the same rules and skills within 2 hours
15
+ Restores missing or stale locked dependencies for edit/test actions
14
16
  check [--json] Exit 1 unless a fresh receipt covers this agent, wherever it acknowledged.
15
17
  Run by hand, it reports this directory's receipt
16
18
  gate [--harness <id>] Hook entry: read a call on stdin, answer allow or deny (exit 2 on deny)
@@ -81,12 +83,23 @@ async function ack(args) {
81
83
  const task = flag(args, '--task') || '';
82
84
  const actions = flag(args, '--action') || 'read,edit,test';
83
85
  const result = acknowledge({ root, task, actions, maxAgeMs: await configuredMaxAgeMs(), session: takeCaller(root, 'ack', { task }) });
86
+ const needsDependencies = actions.split(',').some(action => ['edit', 'test'].includes(action.trim()));
87
+ const dependencies = result.ok && needsDependencies ? await ensureDependencies(root, { reason: `preflight:${actions}` }) : null;
88
+ const ok = result.ok && dependencies?.ok !== false;
84
89
  if (args.includes('--json')) {
85
- console.log(JSON.stringify({ ok: result.ok, problems: result.problems, receipt: result.file || null, capsule: result.capsule ?? null, unchanged: Boolean(result.unchanged) }));
86
- if (!result.ok) process.exitCode = 1;
90
+ console.log(JSON.stringify({ ok, problems: [...result.problems, ...(dependencies?.ok === false ? [`dependencies: ${dependencies.error}`] : [])], receipt: result.file || null, capsule: result.capsule ?? null, unchanged: Boolean(result.unchanged), dependencies }));
91
+ if (!ok) process.exitCode = 1;
87
92
  return;
88
93
  }
89
94
  if (!result.ok) return printFailure(result.problems);
95
+ if (dependencies) {
96
+ const detail = dependencies.ok ? (dependencies.command?.join(' ') || dependencies.trigger) : dependencies.error;
97
+ console.log(` dependencies: ${dependencies.status} — ${detail}`);
98
+ if (!dependencies.ok) {
99
+ console.error('✖ Dependency install failed. Repair dependencies before edit or test work. The instruction receipt is recorded.');
100
+ process.exitCode = 1;
101
+ }
102
+ }
90
103
  if (result.unchanged) {
91
104
  const age = result.unchanged.ageMs < 60000 ? 'less than a minute ago' : `${Math.round(result.unchanged.ageMs / 60000)} minutes ago`;
92
105
  console.log(`✔ tr-preflight recorded: rules and skills are unchanged since this session received the capsule ${age}, so there is nothing new to read. If your task is new, or you no longer have the capsule, run: total-recall context "<task>" --action ${actions}`);
@@ -398,6 +411,8 @@ function evaluate(call, options = {}) {
398
411
  }
399
412
 
400
413
  function judge(call, { harness, readablePaths, session = null, subagent = false, nativeTool = null } = {}) {
414
+ const broken = gatePackageBroken();
415
+ if (broken) return whenGateFails(call, new Error(broken));
401
416
  const root = findRoot(call.cwd || process.cwd());
402
417
  const limit = envMaxAgeMs();
403
418
  const declared = readGateDeclarations();
@@ -584,12 +599,10 @@ async function mode(args) {
584
599
 
585
600
  export default async function preflight(args = []) {
586
601
  const [first = '--help'] = args;
587
- if (first === '--help' || first === '-h' || first === 'help') return console.log(USAGE);
602
+ if (first === '--help' || first === '-h' || first === 'help' || args.includes('--help') || args.includes('-h')) return console.log(USAGE);
588
603
  // Original `tr-preflight` forms.
589
604
  if (first === '--ack' || first === 'ack') return ack(args);
590
605
  if (first === '--check' || first === 'check') return check(args);
591
- // Help never performs the subcommand: `install --help` once rewrote the harness settings (C-019).
592
- if (args.includes('--help') || args.includes('-h')) return console.log(USAGE);
593
606
  if (first === 'gate') return gate(args.slice(1));
594
607
  if (first === 'remind') return remind(args);
595
608
  if (first === 'session-start') return sessionStart();
@@ -208,7 +208,7 @@ export default async function proposals(args) {
208
208
  console.log(` ${String(days).padStart(4)}d [i${n.importance}] ${n.slug}`);
209
209
  }
210
210
  if (stale.length > 40) console.log(`\n … and ${stale.length - 40} more.`);
211
- console.log('\n The dream cycle queues these for research automatically.\n');
211
+ console.log('\n This query is read-only. Request research explicitly for a node that needs verification.\n');
212
212
  return;
213
213
  }
214
214
 
@@ -1,5 +1,22 @@
1
1
  import { resolveAgentDir, resolveBrainDir, parseLayerFlag, getBothBrains } from './agent-dir.mjs';
2
2
  import path from 'node:path';
3
+ import fs from 'node:fs';
4
+
5
+ /** One brain search shared by recall and paired evaluation. Does not record access. */
6
+ export async function searchRecallBrain(query, { semantic = false, forceFast = false, field = false, ...options } = {}) {
7
+ let rankResults = null;
8
+ if (field) {
9
+ const { loadField, rankWithField } = await import('../core/vector-field.mjs');
10
+ const compiled = loadField(options.derivedDir);
11
+ if (!compiled) throw new Error('Vector field missing or corrupt. Run total-recall recall --compile-field first.');
12
+ rankResults = results => rankWithField(results, { field: compiled, derivedDir: options.derivedDir });
13
+ }
14
+ const { fastSearch } = await import('../core/fast-recall.mjs');
15
+ const fastResults = fastSearch(query, { ...options, fullText: !forceFast, rankResults });
16
+ if (!semantic || fastResults.some(result => (result.base_score ?? result.score) === 1) || forceFast) return fastResults;
17
+ const { semanticSearch } = await import('../core/search.mjs');
18
+ return semanticSearch(query, { ...options, rankResults });
19
+ }
3
20
 
4
21
  function printHelp() {
5
22
  console.log(`
@@ -20,8 +37,18 @@ function printHelp() {
20
37
  --fast Force frontmatter-only search without semantic fallback
21
38
  --local Search indexed body text without a provider (default)
22
39
  --semantic Opt into semantic enrichment
40
+ --field Re-rank candidates with the compiled field (opt-in, local)
41
+ --compile-field Compile existing embeddings into the selected brain's field
42
+ --evaluate-field <file> Compare local recall with/without field on fixed JSON queries
23
43
  --timings Print local stage timings on stderr
24
44
 
45
+ Field operations (support --project/--global, --top-k, --format):
46
+ total-recall recall --compile-field --project
47
+ total-recall recall --evaluate-field queries.json --project --format json
48
+ Query file: [{ "query": "search terms", "relevant": ["expected-slug"] }]
49
+ Overlap@k = shared slugs / k. Hit rate@k = queries with a relevant slug / queries.
50
+ Evaluation does not record access. It uses local recall and requires a compiled field.
51
+
25
52
  Examples:
26
53
  npx total-recall recall "Never run tsc directly"
27
54
  npx total-recall recall "Express server port" --top-k 3
@@ -55,6 +82,7 @@ export default async function recall(args) {
55
82
  let forceFast = false;
56
83
  let semantic = false;
57
84
  let timings = false;
85
+ let field = false;
58
86
 
59
87
  for (let i = 1; i < remainingArgs.length; i++) {
60
88
  const arg = remainingArgs[i];
@@ -111,8 +139,11 @@ export default async function recall(args) {
111
139
  semantic = false;
112
140
  } else if (arg === '--timings') {
113
141
  timings = true;
142
+ } else if (arg === '--field') {
143
+ field = true;
114
144
  }
115
145
  }
146
+ top_k = Math.max(1, Math.min(20, top_k));
116
147
 
117
148
  // Determine which brains to search
118
149
  const brains = getBothBrains();
@@ -152,6 +183,26 @@ export default async function recall(args) {
152
183
  }
153
184
 
154
185
  try {
186
+ if (query === '--compile-field' || query === '--evaluate-field') {
187
+ const reports = [];
188
+ const cases = query === '--evaluate-field'
189
+ ? JSON.parse(fs.readFileSync(remainingArgs[1], 'utf8')) : null;
190
+ for (const target of searchTargets) {
191
+ const options = { vaultDir: path.join(target.brainDir, 'memory-vault'),
192
+ derivedDir: path.join(target.brainDir, 'memory-derived'), top_k, includeSessions: false };
193
+ if (query === '--evaluate-field') {
194
+ const { evaluateField } = await import('../core/field-evaluation.mjs');
195
+ reports.push({ brain: target.label, ...await evaluateField(cases, {
196
+ topK: top_k, search: (q, enabled) => searchRecallBrain(q, { ...options, field: enabled }),
197
+ }) });
198
+ } else {
199
+ const { compileField } = await import('../core/vector-field.mjs');
200
+ reports.push({ brain: target.label, ...(await compileField(options)).meta });
201
+ }
202
+ }
203
+ console.log(JSON.stringify(reports, null, 2));
204
+ return;
205
+ }
155
206
  // Search all target brains and merge results
156
207
  let allResults = [];
157
208
  let isDegraded = false;
@@ -164,8 +215,7 @@ export default async function recall(args) {
164
215
 
165
216
  try {
166
217
  let results = [];
167
- const { fastSearch } = await import('../core/fast-recall.mjs');
168
- const fastResults = fastSearch(query, {
218
+ results = await searchRecallBrain(query, {
169
219
  derivedDir,
170
220
  vaultDir,
171
221
  top_k,
@@ -174,28 +224,12 @@ export default async function recall(args) {
174
224
  modality,
175
225
  importance,
176
226
  priority,
177
- fullText: !forceFast,
227
+ forceFast, semantic, field,
228
+ includeSessions: includeSessions && target.label !== 'global',
178
229
  });
179
- if (timings) console.error(JSON.stringify({ brain: target.label, ...fastResults.stats }));
180
- if (['missing', 'corrupt', 'metadata-only'].includes(fastResults.stats.index) && !forceFast) {
181
- console.error(`Local full-text index for ${target.label}: ${fastResults.stats.index}. Run total-recall compile to rebuild; available metadata results may be incomplete.`);
182
- }
183
-
184
- if (!semantic || fastResults.some(result => result.score === 1) || forceFast) {
185
- results = fastResults;
186
- } else {
187
- const { semanticSearch } = await import('../core/search.mjs');
188
- results = await semanticSearch(query, {
189
- vaultDir,
190
- derivedDir,
191
- top_k,
192
- includeSessions: includeSessions && target.label !== 'global', // sessions only in project/local
193
- category,
194
- tags,
195
- modality,
196
- importance,
197
- priority
198
- });
230
+ if (timings) console.error(JSON.stringify({ brain: target.label, ...results.stats }));
231
+ if (['missing', 'corrupt', 'metadata-only'].includes(results.stats?.index) && !forceFast) {
232
+ console.error(`Local full-text index for ${target.label}: ${results.stats.index}. Run total-recall compile to rebuild; available metadata results may be incomplete.`);
199
233
  }
200
234
  if (results.degradedTextSearch) {
201
235
  isDegraded = true;
@@ -226,6 +260,15 @@ export default async function recall(args) {
226
260
  allResults.sort((a, b) => (b.score || 0) - (a.score || 0));
227
261
  allResults = allResults.slice(0, top_k);
228
262
 
263
+ // DECAY-01: a recalled memory is a used memory; record it in the brain it came from.
264
+ try {
265
+ const { recordAccess } = await import('../core/access-ledger.mjs');
266
+ for (const target of searchTargets) {
267
+ const slugs = allResults.filter(r => r._layer === target.label).map(r => r.slug || r.id).filter(Boolean);
268
+ recordAccess(path.join(target.brainDir, 'memory-derived'), slugs);
269
+ }
270
+ } catch { /* access accounting never breaks recall */ }
271
+
229
272
  if (format === 'json') {
230
273
  for (const error of searchErrors) console.error(`Search failed for ${error.label}: ${error.message.split('\n')[0]}`);
231
274
  if (searchErrors.length && !allResults.length) process.exitCode = 1;
@@ -141,7 +141,10 @@ export async function tarballServed(pkg, version, { exec = spawnSync, fetchImpl
141
141
  * Wait until a just-published version can be installed: listed as latest, and its tarball served.
142
142
  * 3.42.0 took about six minutes and 3.42.1 about eight, its listing four minutes ahead of its tarball.
143
143
  */
144
- export async function awaitPublished(pkg, version, { attempts = 45, delayMs = 20000, exec = spawnSync, sleep, tarball = tarballServed } = {}) {
144
+ /** npm has taken 8 and then 23 minutes to serve an accepted publish, so a shorter wait reports failure on a good release. */
145
+ export const REGISTRY_WAIT = Object.freeze({ attempts: 120, delayMs: 20000 });
146
+
147
+ export async function awaitPublished(pkg, version, { attempts = REGISTRY_WAIT.attempts, delayMs = REGISTRY_WAIT.delayMs, exec = spawnSync, sleep, tarball = tarballServed } = {}) {
145
148
  const pause = sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
146
149
  let last = null;
147
150
  for (let i = 0; i < attempts; i++) {
@@ -37,6 +37,7 @@ function printHelp() {
37
37
  --object <string> SPO Object (default: brain)
38
38
  --related <list> Comma-separated list of related slugs
39
39
  --expires <duration> TTL for temporary rules (e.g. "7d", "2w", "30d", "6h", "3m")
40
+ --allow-long Permit a rule above 800 characters (context:policy also permits it)
40
41
  --confined-fact Create a descriptive fact with generated identity; refresh local search without executable generators
41
42
 
42
43
  Examples:
@@ -74,9 +75,6 @@ function parseDuration(str) {
74
75
  return date;
75
76
  }
76
77
 
77
- const RULE_SOFT_LIMIT = 300;
78
- const RULE_HARD_LIMIT = 800;
79
-
80
78
  export default async function remember(args) {
81
79
  // Parse layer flag first
82
80
  const { layer: explicitLayer, remainingArgs: layerArgs } = parseLayerFlag(args);
@@ -244,16 +242,6 @@ export default async function remember(args) {
244
242
  }
245
243
  }
246
244
 
247
- // Rules load into every task capsule, so they must be brief.
248
- if (['invariants', 'preferences', 'anti-patterns'].includes(category) && !tags.includes('context:policy')) {
249
- const n = bodyContent.trim().length;
250
- if (n > RULE_HARD_LIMIT && !allowLong) {
251
- console.error(`❌ Rule is ${n} characters (limit ${RULE_HARD_LIMIT}). Rules must be brief: one or two sentences, no history, dates, quotes or incident detail (save those as a fact). Shorten it, or pass --allow-long.`);
252
- process.exit(1);
253
- }
254
- if (n > RULE_SOFT_LIMIT) console.error(`⚠️ Rule is ${n} characters. Keep rules brief (under ${RULE_SOFT_LIMIT}): state the constraint only, move history and examples to a fact. Shorten with: total-recall edit <slug> "<shorter>"`);
255
- }
256
-
257
245
  const resolvedAgentDir = resolveAgentDir(layer);
258
246
  const resolvedBrainDir = resolveBrainDir(layer);
259
247
  const skillsDir = path.join(resolvedAgentDir, 'skills');
@@ -344,7 +332,7 @@ export default async function remember(args) {
344
332
 
345
333
  // Saving memory never optimizes or archives other canonical documents.
346
334
 
347
- const vaultResult = await writeNodeValidatedAsync(node, vaultDir);
335
+ const vaultResult = await writeNodeValidatedAsync(node, vaultDir, { allowLong });
348
336
  if (!vaultResult.success) {
349
337
  console.error(` ❌ Validation failed: ${vaultResult.validation.errors.join('; ')}`);
350
338
  if (vaultResult.repair) {
@@ -355,6 +343,7 @@ export default async function remember(args) {
355
343
  }
356
344
  process.exit(1);
357
345
  }
346
+ for (const warning of vaultResult.validation?.warnings || []) console.error(` ⚠️ ${warning}`);
358
347
  console.log(` ✅ Permanent SSSS memory node created ${layerLabel} in vault: memory-vault/${category}/${finalSlug}.md`);
359
348
 
360
349
  // Automatically recompile surfaces and local indexes
@@ -0,0 +1,18 @@
1
+ import { ensureServerService } from '../core/server-service.mjs';
2
+
3
+ export default async function server(args = []) {
4
+ if (!args.length || args.includes('--help') || args.includes('-h')) {
5
+ console.log('Usage: total-recall server ensure [--dry-run]\nRepair this package\'s macOS LaunchAgent and verify its process. --dry-run prints the plist diff without changes. On systemd hosts, server-release owns the server.');
6
+ return;
7
+ }
8
+ if (args[0] !== 'ensure' || args.slice(1).some(arg => arg !== '--dry-run')) {
9
+ console.error('Usage: total-recall server ensure [--dry-run]');
10
+ process.exitCode = 2;
11
+ return;
12
+ }
13
+ try {
14
+ const result = await ensureServerService({ dryRun: args.includes('--dry-run') });
15
+ console.log(result.diff || result.reason || `Server verified: pid ${result.pid}, ${result.entry}`);
16
+ return result;
17
+ } catch (error) { console.error(error.message); process.exitCode = 1; }
18
+ }
@@ -36,6 +36,7 @@ export default async function startup(args = []) {
36
36
  if (args.includes('--json')) console.log(JSON.stringify(report, null, 2));
37
37
  else {
38
38
  console.log(`Startup: required plugins ${report.required_plugins?.status || 'unknown'}; server ${report.server?.status || 'unknown'}; brain ${report.brain?.status || 'unknown'}; daemon ${report.daemon?.status || 'unknown'}; SSSS tooling ${report.ssss?.status || 'unknown'}; app ${report.app?.status || 'unknown'}`);
39
+ if (report.server?.reason) console.log(`Server: ${report.server.reason}`);
39
40
  if (report.server?.stale) console.log(`Server: running ${report.server.stale.running} with ${report.server.stale.on_disk} on disk; it has not been replaced.`);
40
41
  for (const error of report.required_plugins?.errors || []) console.log(`Required plugin: ${error}`);
41
42
  const harnesses = await queryHarnessUsage().catch(() => []);
package/src/cli/task.mjs CHANGED
@@ -34,10 +34,11 @@ function printHelp() {
34
34
  show <slug> Show one task
35
35
  cancel <slug> Cancel a task, optionally with --expected-status pending
36
36
  executors List registered executor ids
37
+ idle Enqueue leader maintenance from an automations manifest task
37
38
 
38
39
  add options:
39
40
  --kind <k> memory | research | maintenance | system | custom (default: custom)
40
- --executor <id> dream | custom | research | session-ingest | surface-compile | prune
41
+ --executor <id> dream | custom | research | legacy
41
42
  --priority <p> high | normal | low | absolute | 0-100 (default: normal)
42
43
  --cap <list> Comma-separated capabilities (default: vault:read)
43
44
  Use vault:write to draft results into memory-inbox
@@ -58,7 +59,7 @@ function printHelp() {
58
59
 
59
60
  Policy:
60
61
  shell / net:post capabilities are denied by default.
61
- Proactive idle fill is off unless TR_IDLE_TASKS=1.
62
+ Empty queues stay empty. Automations owns the idle schedule.
62
63
  `);
63
64
  }
64
65
 
@@ -168,6 +169,12 @@ export default async function taskCli(argv) {
168
169
  const brainDir = resolveBrainDir(layer);
169
170
  const queueDir = resolveQueueDir(brainDir);
170
171
 
172
+ if (opts.command === 'idle') {
173
+ const { enqueueScheduledIdleTasks } = await import('../core/scheduler.mjs');
174
+ console.log(JSON.stringify(await enqueueScheduledIdleTasks({ queueDir, vaultDir: path.join(brainDir, 'memory-vault') })));
175
+ return;
176
+ }
177
+
171
178
  if (opts.command === 'executors') {
172
179
  console.log('\n Registered executors:\n');
173
180
  for (const id of listExecutorIds()) {
@@ -50,6 +50,7 @@ export function parseArgs(args) {
50
50
  else if (a === '--port' && args[i + 1]) opts.port = Number(args[++i]);
51
51
  else if (a === '--no-build') opts.noBuild = true;
52
52
  else if (a === '--all-brains') opts.allBrains = true;
53
+ else if (a === '--install-missing') opts.installMissing = true;
53
54
  else if (a === '--json') opts.json = true;
54
55
  else if (a === '--automatic') opts.automatic = true;
55
56
  else if (a === '--status') opts.status = true;
@@ -70,16 +71,20 @@ function printHelp() {
70
71
  total-recall update --apply --force Ignore throttle / disabled flag
71
72
  total-recall update --repo <path> Include extra root (repeatable)
72
73
  total-recall update --all-brains Plan SSSS + Total Recall updates, including plugins/nested packages
73
- total-recall update --all-brains --apply [--json]
74
+ total-recall update --all-brains --apply [--install-missing] [--json]
74
75
  Pin the same published versions, use npm/pnpm, replace shared dependency
75
76
  links with independent local installs, and verify every resolved version.
77
+ A checkout with no node_modules keeps none: only its pin is updated.
78
+ --install-missing installs those trees too (never done automatically).
76
79
  A plugin checkout linked into a brain but not registered as a project is
77
80
  only installed here: its dev dependencies are skipped (it runs under the
78
81
  shared install) and its runtime ones update with --omit=dev, without
79
82
  writing package.json or the lockfile. Register it to develop it here.
80
83
  total-recall update --automatic [--json]
81
84
  Daemon maintenance: update both packages, ensure defaults, update every
82
- installed plugin from its recorded source and refresh surfaces.
85
+ installed plugin from its recorded source and refresh surfaces. Where a
86
+ per-user service (LaunchAgent, systemd user unit) runs the server from
87
+ the global install, also restart a stale server and verify /health.
83
88
  total-recall update --status [--json]
84
89
  Read the actual last automatic maintenance report without starting work.
85
90
  Also reports what the server on this host runs against the release.
@@ -96,6 +101,7 @@ function printHelp() {
96
101
 
97
102
  Env:
98
103
  TR_AUTO_UPDATE_PACKAGE=0 Disable daemon cron auto-update (default: on)
104
+ TR_SERVER_AUTO_RESTART=0 Report a stale server instead of restarting it (default: on)
99
105
  TR_SYNC_REPOS=/a:/b Extra project roots
100
106
 
101
107
  Source monorepos named ${PACKAGE_NAME} are skipped (use git, not npm into self).
@@ -124,7 +130,7 @@ export default async function update(args = []) {
124
130
 
125
131
  if (opts.allBrains) {
126
132
  const summary = await updateAllBrains({ brainDir, roots: opts.repos,
127
- dryRun: !opts.apply || opts.dryRun, ensureDefaults: true });
133
+ dryRun: !opts.apply || opts.dryRun, ensureDefaults: true, installMissing: opts.installMissing === true });
128
134
  if (summary.failed) process.exitCode = 1;
129
135
  if (opts.apply && !opts.dryRun) {
130
136
  const hooks = await installHarnessHooks({ log: (line) => { if (!opts.json) console.log(line); } });
@@ -134,6 +140,7 @@ export default async function update(args = []) {
134
140
  if (opts.json) console.log(JSON.stringify(summary, null, 2));
135
141
  else {
136
142
  console.log(`\n Uniform brain updates: ${summary.consumer_count || 0} consumers`);
143
+ console.log(` Missing: ${summary.not_installed || 0}; current: ${summary.up_to_date || 0}; would update: ${summary.would_update || 0}; updated: ${summary.updated || 0}; failed: ${summary.failed || 0}`);
137
144
  if (summary.reason) console.log(` ${summary.reason}`);
138
145
  for (const row of summary.results) {
139
146
  console.log(` ${row.root}: ${row.status}`);
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Memory access ledger (DECAY-01).
3
+ *
4
+ * Records when a memory is actually used: returned by recall or included in a context
5
+ * capsule. Decay reads it to tell a memory nobody uses from one read every day. It lives in
6
+ * the brain's derived state, so a read never rewrites a vault file.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { atomicWrite } from './vault.mjs';
11
+
12
+ const LEDGER = 'access-ledger.json';
13
+
14
+ function ledgerFile(derivedDir) {
15
+ return path.join(derivedDir, LEDGER);
16
+ }
17
+
18
+ /** The ledger as `{ slug: { last_accessed, count } }`. A missing or damaged file is an empty ledger. */
19
+ export function readAccess(derivedDir) {
20
+ if (!derivedDir) return {};
21
+ try {
22
+ const value = JSON.parse(fs.readFileSync(ledgerFile(derivedDir), 'utf8'));
23
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ /** Reserved ledger key: the days on which this brain was used at all (YYYY-MM-DD, sorted). */
30
+ export const ACTIVE_DAYS = '_active_days';
31
+
32
+ /** The days this brain was used, oldest first. */
33
+ export function activeDays(ledger = {}) {
34
+ return Array.isArray(ledger[ACTIVE_DAYS]) ? ledger[ACTIVE_DAYS] : [];
35
+ }
36
+
37
+ /**
38
+ * How many of the brain's active days came after `sinceMs`. Decay counts these, never calendar
39
+ * days, so a brain nobody works in never decays (Greg, 2026-11-09).
40
+ */
41
+ export function activeDaysSince(ledger, sinceMs) {
42
+ const since = new Date(sinceMs).toISOString().slice(0, 10);
43
+ return activeDays(ledger).filter(day => day > since).length;
44
+ }
45
+
46
+ /** Record one use of each slug, and today as an active day. Never throws: a failed write must not break recall or preflight. */
47
+ export function recordAccess(derivedDir, slugs, now = new Date()) {
48
+ const unique = [...new Set((slugs || []).filter(slug => typeof slug === 'string' && slug && slug !== ACTIVE_DAYS))];
49
+ if (!derivedDir || unique.length === 0) return false;
50
+ try {
51
+ const ledger = readAccess(derivedDir);
52
+ const stamp = now.toISOString();
53
+ const today = stamp.slice(0, 10);
54
+ const days = activeDays(ledger);
55
+ if (!days.includes(today)) ledger[ACTIVE_DAYS] = [...days, today].sort();
56
+ for (const slug of unique) {
57
+ const entry = ledger[slug] || { count: 0 };
58
+ // Three active days are enough for the field's first and second derivatives.
59
+ const daily = { ...entry.daily_counts, [today]: (Number(entry.daily_counts?.[today]) || 0) + 1 };
60
+ const recentDays = activeDays(ledger).slice(-3);
61
+ ledger[slug] = { last_accessed: stamp, count: (Number(entry.count) || 0) + 1,
62
+ daily_counts: Object.fromEntries(Object.entries(daily).filter(([day]) => recentDays.includes(day))) };
63
+ }
64
+ fs.mkdirSync(derivedDir, { recursive: true });
65
+ atomicWrite(ledgerFile(derivedDir), JSON.stringify(ledger));
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /** The latest known use of a memory: the ledger, its own `last_accessed`, or its last update. */
73
+ export function lastUsedMs(node, ledger = {}) {
74
+ const times = [ledger[node.slug]?.last_accessed, node.last_accessed, node.updated, node.created]
75
+ .map(value => new Date(value || 0).getTime())
76
+ .filter(Number.isFinite);
77
+ return times.length ? Math.max(...times) : 0;
78
+ }
@@ -1,11 +1,11 @@
1
- /** Compatibility facade; process lifecycle, isolation, metadata and receipts belong to Meta Harness. */
1
+ /** Compatibility facade; process lifecycle, isolation, metadata and receipts belong to Metaharness. */
2
2
  import { loadPluginModule } from './plugin-runtime.mjs';
3
3
 
4
4
  let owner, ownerError;
5
5
  try { owner = await loadPluginModule('meta-harness', 'process-manager'); }
6
6
  catch (error) { ownerError = error; }
7
7
  function implementation() {
8
- if (!owner) throw new Error(`Meta Harness processes are unavailable: ${ownerError?.message || 'install the plugin'}`);
8
+ if (!owner) throw new Error(`Metaharness processes are unavailable: ${ownerError?.message || 'install the plugin'}`);
9
9
  return owner;
10
10
  }
11
11
  export const loadAgentState = (...args) => implementation().loadAgentState(...args);
@@ -13,6 +13,7 @@ import { updateMeshBrainPackages } from './mesh-brain-updates.mjs';
13
13
  import { updateSourceBrainPackages } from './source-brain-updates.mjs';
14
14
  import { updateInstalledPlugins } from './plugin-updates.mjs';
15
15
  import { runningServerStatus } from './running-server.mjs';
16
+ import { replaceStaleServer } from './server-restart.mjs';
16
17
 
17
18
  export function automaticUpdateStatus(brainDir) {
18
19
  let last;
@@ -37,7 +38,7 @@ export function startAutomaticBrainUpdates({brainDir,intervalMs=60*60*1000,run=r
37
38
  return {tick,stop(){stopped=true;clearInterval(timer);}};
38
39
  }
39
40
 
40
- export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false,skipThrottle=false,intervalMs=60*60*1000,update=updateAllBrains,compile=maintainCliSurfaces,globalUpdate=updateGlobalBrainPackages,meshUpdate=updateMeshBrainPackages,sourceUpdate=updateSourceBrainPackages,pluginUpdate=updateInstalledPlugins,skillMaintenance=maintainPortableSkills,serverCheck=runningServerStatus,now=Date.now}={}) {
41
+ export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false,skipThrottle=false,intervalMs=60*60*1000,update=updateAllBrains,compile=maintainCliSurfaces,globalUpdate=updateGlobalBrainPackages,meshUpdate=updateMeshBrainPackages,sourceUpdate=updateSourceBrainPackages,pluginUpdate=updateInstalledPlugins,skillMaintenance=maintainPortableSkills,serverCheck=runningServerStatus,replaceServer=replaceStaleServer,now=Date.now}={}) {
41
42
  if(!force && !isPackageAutoUpdateEnabled())return {skipped:true,reason:'disabled',results:[]};
42
43
  const file=path.join(brainDir,'memory-derived/automatic-brain-updates.json');
43
44
  let saved;try{saved=JSON.parse(fs.readFileSync(file,'utf8'));}catch{}
@@ -65,16 +66,20 @@ export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false
65
66
  const plugins=await pluginUpdate({roots:[path.resolve(brainDir,'../../..')]});
66
67
  const mesh=fleet && summary.versions?await meshUpdate({versions:summary.versions,version:summary.versions['total-recall-brain']}):{failed:0,results:[],scope:'local-only'};
67
68
  const changed=summary.updated>0 || plugins.updated>0 || global.results.some(row=>row.status==='updated') || sources.results.some(row=>row.status==='updated') || JSON.stringify(saved?.versions)!==JSON.stringify(summary.versions);
68
- // A lagging server is reported and not counted as a failure: maintenance cannot fix it, and
69
- // a failure would retry every five minutes. Replacing one in production is a deploy decision.
70
- const server=await serverCheck({brainDir,latest:summary.versions?.['total-recall-brain']});
71
- if(server.status==='stale' || server.status==='down')logger.error('automatic-brain-updates',`Running server: ${server.reason}`);
72
69
  const skills=await skillMaintenance({brainDir});
73
70
  const surfaces=await compile({brainDir,force:changed || skills.updated > 0});
71
+ // Last, so a replaced server starts on everything this run moved. Where maintenance cannot
72
+ // replace it, a lagging server is reported and not counted as a failure, which would retry
73
+ // every five minutes for nothing: in production that is a deploy decision. Where it can, a
74
+ // hold or a failed attempt is this machine's own failure, which is what makes a peer dispatch it again.
75
+ let server=await serverCheck({brainDir,latest:summary.versions?.['total-recall-brain']});
76
+ if(server.status==='stale')server=await replaceServer({brainDir,server,globalRoot:global.root,previous:saved?.server});
77
+ if(server.status==='stale' || server.status==='down')logger.error('automatic-brain-updates',`Running server: ${server.replacement?.reason || server.reason}`);
78
+ const serverFailed=['held','failed'].includes(server.replacement?.state)?1:0;
74
79
  // local_failed is this node's own maintenance. Peers judge a node by it, not by
75
80
  // `failed`, which also counts the node's view of its peers: judging by that made
76
81
  // every fleet pass mark every peer failed forever, each because the others had.
77
- const local_failed=(summary.failed || 0)+global.failed+sources.failed+plugins.failed+(skills.failed || 0)+(surfaces.ok?0:surfaces.results.filter(row=>row.status==='failed').length);
82
+ const local_failed=(summary.failed || 0)+global.failed+sources.failed+plugins.failed+(skills.failed || 0)+(surfaces.ok?0:surfaces.results.filter(row=>row.status==='failed').length)+serverFailed;
78
83
  report={...summary,global,sources,plugins,mesh,server,skills,surfaces,local_failed,failed:local_failed+mesh.failed,checkedAt:now()};
79
84
  }catch(error){report={failed:1,error:error.message,checkedAt:now(),results:[]};}
80
85
  const temporary=`${file}.${process.pid}.tmp`;fs.writeFileSync(temporary,JSON.stringify(report,null,2));fs.renameSync(temporary,file);