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
@@ -1,9 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
- import { validatePluginManifest } from "../../core/plugin-loader.mjs";
4
+ import { validatePluginManifest, isCronExpression } from "../../core/plugin-loader.mjs";
5
5
  import { installPlugin } from "../../core/plugin-store.mjs";
6
- import { parseCron } from "../../core/plugin-cron.mjs";
7
6
 
8
7
  function toTitleCase(kebab) {
9
8
  return kebab
@@ -111,7 +110,7 @@ customElements.define('${id}-panel', ${panelClass});
111
110
  }
112
111
 
113
112
  export async function createPlugin(args = []) {
114
- const isGlobal = args.includes("--global") || args.includes("-g");
113
+ // Every plugin is installed in the shared folder; --global is accepted and changes nothing.
115
114
  let withCli = args.includes("--with-cli");
116
115
  const withGenerator = args.includes("--with-generator");
117
116
  // A card face is bespoke owner code, authored by hand once the plugin's
@@ -212,7 +211,7 @@ export async function createPlugin(args = []) {
212
211
 
213
212
  if (!id) {
214
213
  console.error("❌ Error: Missing plugin id.");
215
- console.error(" Usage: total-recall plugin create <id> [--from-skill <path>] [--capability] [--name <name>] [--description <desc>] [--category <cat>] [--global]\n");
214
+ console.error(" Usage: total-recall plugin create <id> [--from-skill <path>] [--capability] [--name <name>] [--description <desc>] [--category <cat>]\n");
216
215
  process.exit(1);
217
216
  }
218
217
 
@@ -226,7 +225,7 @@ export async function createPlugin(args = []) {
226
225
  if (!taskCommand || !/^[a-z][a-z0-9-]*$/.test(taskCommand) || !taskSchedule || !taskIntent) {
227
226
  throw new Error('--with-task requires --task-command <subcommand>, --task-schedule "<five-field cron>", and --task-intent <description>');
228
227
  }
229
- parseCron(taskSchedule);
228
+ if (!isCronExpression(taskSchedule)) throw new Error(`--task-schedule must be a five-field cron expression, got "${taskSchedule}"`);
230
229
  } else if (taskCommand || taskSchedule || taskIntent) {
231
230
  throw new Error('--task-* options require --with-task');
232
231
  }
@@ -589,7 +588,7 @@ test('every file the manifest references exists', () => {
589
588
  // ── Install: a link to the repo, so the repo is what runs ───────────────
590
589
  let installed = false;
591
590
  if (!skipInstall) {
592
- await installPlugin(pluginDir, { link: true, global: isGlobal, projectRoot: process.cwd() });
591
+ await installPlugin(pluginDir, { link: true, projectRoot: process.cwd() });
593
592
  installed = true;
594
593
  }
595
594
 
@@ -613,7 +612,7 @@ test('every file the manifest references exists', () => {
613
612
  if (withCli) {
614
613
  console.log(` CLI Handler: \x1b[32m${path.join(pluginDir, "cli.mjs")}\x1b[0m`);
615
614
  }
616
- console.log(` Installed: ${installed ? `linked into ${isGlobal ? 'the global' : 'this project\'s'} plugins` : 'no (--no-install)'}`);
615
+ console.log(` Installed: ${installed ? `linked into the shared plugin folder` : 'no (--no-install)'}`);
617
616
  console.log(` GitHub: ${githubUrl || (createGithub ? 'creation failed' : 'not created (re-run with --github, or: gh repo create tr-plugin-' + id + ' --private --source ' + pluginDir + ')')}`);
618
617
  console.log(`\nNext steps:`);
619
618
  console.log(` Test: \x1b[32mcd ${pluginDir} && npm test\x1b[0m`);
@@ -27,8 +27,11 @@ Commands:
27
27
  list Installed plugins (project & global) with source and sharing
28
28
  library [--plugin ID] Installed CLI operation library (--json for agents)
29
29
  refresh Rebuild installed command inventories and instruction surfaces
30
- copies [dir] [--apply] Audit repository copies of shared defaults. --apply deletes only
31
- bundled matches. Add --archive-stale to preserve older differing copies.
30
+ copies [dir] [--apply] Audit repository copies of shared defaults and shadow links. --apply
31
+ deletes bundled matches, unlinks shadows (on/off records are kept)
32
+ and removes install records of plugins installed nowhere.
33
+ Add --archive-stale to preserve older differing copies, and
34
+ --drop-repo-only to delete plugins that exist only in the repository.
32
35
  replacements [--prune] [--keep N] [--project] [--json]
33
36
  Copies kept by install --replace (default: shared folder). Reports
34
37
  the space; --prune keeps the newest N per plugin (default 1) and
@@ -49,7 +52,8 @@ Commands:
49
52
  <path> a local directory
50
53
  --link, -l Symlink a local directory instead of copying
51
54
  --replace Replace an install, retain previous source and restore on failure
52
- --global, -g Install for every project on this machine
55
+ Every plugin installs into the shared folder for all repositories;
56
+ a repository switches it with 'plugin on|off <id>'.
53
57
  update [--check] [--json] Reinstall every plugin whose git repo, mesh peer or source
54
58
  directory moved ahead (--check only reports; also runs hourly
55
59
  in 'update --automatic')
@@ -57,7 +61,7 @@ Commands:
57
61
  unshare <id> Stop offering it
58
62
  publish <id> Publish plugin to the Store and lock release (--no-lock)
59
63
  monetize <id> Configure plugin pricing, period, and payout recipient
60
- remove <id> Uninstall (--global for the machine-wide copy)
64
+ remove <id> Uninstall from the shared folder
61
65
  license <id> [cmd] Check or activate plugin license (status, activate, checkout, verify)
62
66
  info <id> Manifest, provenance, hash and task history
63
67
  vendor <source> [dir] Add a plugin to this project as a brick (default bricks/<id>); the
@@ -80,10 +84,10 @@ Commands:
80
84
 
81
85
  Examples:
82
86
  npx total-recall plugin available --use-case software-development
83
- npx total-recall plugin install git-sentinel
84
- npx total-recall plugin share git-sentinel
87
+ npx total-recall plugin install git
88
+ npx total-recall plugin share git
85
89
  npx total-recall plugin peers
86
- npx total-recall plugin install peer:mac-mini/git-sentinel
90
+ npx total-recall plugin install peer:mac-mini/git
87
91
  npx total-recall plugin create reading-list --use-case research --with-cli
88
92
  `);
89
93
  }
@@ -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;
@@ -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,9 +71,11 @@ 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
@@ -127,7 +130,7 @@ export default async function update(args = []) {
127
130
 
128
131
  if (opts.allBrains) {
129
132
  const summary = await updateAllBrains({ brainDir, roots: opts.repos,
130
- dryRun: !opts.apply || opts.dryRun, ensureDefaults: true });
133
+ dryRun: !opts.apply || opts.dryRun, ensureDefaults: true, installMissing: opts.installMissing === true });
131
134
  if (summary.failed) process.exitCode = 1;
132
135
  if (opts.apply && !opts.dryRun) {
133
136
  const hooks = await installHarnessHooks({ log: (line) => { if (!opts.json) console.log(line); } });
@@ -137,6 +140,7 @@ export default async function update(args = []) {
137
140
  if (opts.json) console.log(JSON.stringify(summary, null, 2));
138
141
  else {
139
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}`);
140
144
  if (summary.reason) console.log(` ${summary.reason}`);
141
145
  for (const row of summary.results) {
142
146
  console.log(` ${row.root}: ${row.status}`);