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
@@ -496,13 +496,8 @@ function injectDirectives(fileContent, rulesBlock) {
496
496
  return prependDirectives(authored, rulesBlock);
497
497
  }
498
498
 
499
- let content = fileContent.trimEnd();
500
- const baseline = '# 🔴 MANDATORY FIRST STEP ON EVERY TURN: TOTAL RECALL FIRST\n1. Run `total-recall --help` (or `node bin/total-recall.mjs --help`) to discover all tools, plugins, and commands.\n2. Read and follow .agent/skills/total-recall/SKILL.md before doing ANYTHING.\n3. Run `total-recall context "<task>"` and `total-recall recall "<topic>" --local` before any decision or action.\nTotal Recall is the mandatory working memory and toolset across all tasks and harnesses. Never skip this.';
501
- if (!content.includes('total-recall/SKILL.md')) {
502
- content = baseline + '\n\n' + content;
503
- }
504
-
505
- return prependDirectives(content, rulesBlock);
499
+ // The managed block carries the start-here instructions; authored text stays as written.
500
+ return prependDirectives(fileContent.trimEnd(), rulesBlock);
506
501
  }
507
502
 
508
503
  function prependDirectives(authored, rulesBlock) {
@@ -516,14 +511,28 @@ function prependDirectives(authored, rulesBlock) {
516
511
  /**
517
512
  * Write or update a platform instruction shim with the pointer and active rules.
518
513
  */
514
+ /**
515
+ * The whole managed part of every client entry file (ON-DEMAND-CONTEXT-01). It is static: no rule
516
+ * bodies, CLI list, plugin table or command list, so a rule or plugin change rewrites no repository.
517
+ * The rules for a task are compiled when the agent acknowledges preflight, and commands are looked
518
+ * up when needed.
519
+ */
520
+ export const ENTRY_BLOCK = `# Total Recall: start here
521
+
522
+ This repository uses Total Recall. Its rules are not copied into this file: they are compiled for your task when you acknowledge preflight.
523
+
524
+ 1. Read \`.agent/skills/total-recall/SKILL.md\`.
525
+ 2. Run \`total-recall tr-preflight --ack --task "<task>" --action read,edit,test\` and read everything it prints. Those are the rules for this task. Until it passes, working tools are denied.
526
+ 3. Route the task through Metaharness with \`total-recall meta-harness route "<task>" --json\` and follow the route.
527
+ 4. Look commands up only when you need them: \`total-recall --help\`, \`total-recall <namespace> --help\`, \`total-recall plugin library\`.`;
528
+
519
529
  async function writeShim(shimPath, skillsDir, nodes = [], { vaultDir, derivedDir, force = false } = {}) {
520
530
  const shimDir = path.dirname(shimPath);
521
- const rulesBlock = await buildRulesBlock(skillsDir, nodes, { vaultDir, derivedDir, bootstrap: true });
522
- const baseline = '# 🔴 MANDATORY FIRST STEP ON EVERY TURN: TOTAL RECALL FIRST\n1. Run `total-recall --help` (or `node bin/total-recall.mjs --help`) to discover all tools, plugins, and commands.\n2. Read and follow .agent/skills/total-recall/SKILL.md before doing ANYTHING.\n3. Run `total-recall context "<task>"` and `total-recall recall "<topic>" --local` before any decision or action.\nTotal Recall is the mandatory working memory and toolset across all tasks and harnesses. Never skip this.\n';
531
+ const rulesBlock = ENTRY_BLOCK;
523
532
  const mdcHeader = shimPath.endsWith('.mdc')
524
- ? '---\ndescription: "Total Recall — Auto-generated behavioral memory surface."\nglobs:\nalwaysApply: true\n---\n\n'
533
+ ? '---\ndescription: "Total Recall — start here."\nglobs:\nalwaysApply: true\n---\n\n'
525
534
  : '';
526
- const fullContent = prependDirectives(`${mdcHeader}${baseline}`, rulesBlock);
535
+ const fullContent = prependDirectives(mdcHeader, rulesBlock);
527
536
 
528
537
  try {
529
538
  const normShim = path.normalize(shimPath);
@@ -620,7 +629,7 @@ const CLIENT_SHIMS = {
620
629
  'hermes-agent': ['AGENTS.md'],
621
630
  dsh: ['AGENTS.md'],
622
631
  'deepseek-harness': ['AGENTS.md'],
623
- openclaw: ['MEMORY.md', 'AGENTS.md']
632
+ openclaw: ['AGENTS.md']
624
633
  };
625
634
 
626
635
  /**
@@ -739,13 +748,13 @@ async function compilePointers(instructionsFile, skillsDir, nodes = [], { vaultD
739
748
  */
740
749
  /**
741
750
  * The rule nodes a project's instruction surfaces are built from: the global
742
- * brain's rules plus the project's own. A project node with the same slug
751
+ * brain's rules plus the project's own. A project rule with the same slug
743
752
  * wins, so a repo can override a global rule; everything else in the global
744
753
  * vault stays search-only. Without this every repo carried only its own vault,
745
754
  * and a rule saved with `remember --global` reached no repo at all.
746
755
  */
747
756
  export function mergeGlobalRuleNodes(projectNodes, globalNodes) {
748
- const own = new Set(projectNodes.map((n) => n.slug));
757
+ const own = new Set(projectNodes.filter((n) => RULE_CATEGORIES.has(n.category)).map((n) => n.slug));
749
758
  const inherited = globalNodes
750
759
  .filter((n) => RULE_CATEGORIES.has(n.category) && !own.has(n.slug))
751
760
  .map((n) => ({ ...n, _layer: 'global' }));
@@ -772,46 +781,50 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
772
781
  ? mergeGlobalRuleNodes(nodes, getNodes(globalVault))
773
782
  : nodes;
774
783
 
775
- // ── Incremental compilation: vault content hash check ──
776
- // The global rules hash lives in its own file: vault-hash.txt is served by
777
- // /api/vault/hash and must keep describing this vault alone.
784
+ // ── Each output has its own trigger (ON-DEMAND-CONTEXT-01) ──
785
+ // Entry files are static: they change only with the entry block or the client list.
786
+ // Indexes change only with this vault. The CLI index changes only with this brain's
787
+ // plugin catalog. A plugin or global-rule change therefore rewrites no repository's
788
+ // entry files and starts no embeddings pass; rules reach agents at preflight.
778
789
  const hashFile = path.join(derivedDir, 'vault-hash.txt');
779
790
  const currentHash = computeVaultHash(vaultDir);
780
- const globalHashFile = path.join(derivedDir, 'global-rules-hash.txt');
781
- const globalHash = globalVault && fs.existsSync(globalVault) ? computeVaultHash(globalVault) : '';
782
- // Commands and skills feed the surface too; a change to either must recompile
783
- // even when no memory node changed.
784
791
  const inputsHashFile = path.join(derivedDir, 'surface-inputs-hash.txt');
792
+ const cliHashFile = path.join(derivedDir, 'cli-index-hash.txt');
785
793
  const files = instructionFiles(instructionsFile, skillsDir);
786
794
  const surfaceDir = path.dirname(instructionsFile);
787
795
  const artifactsFile = path.join(derivedDir, 'surface-artifacts.json');
788
- const inputsHash = sourceHash(SURFACE_SOURCE) + catalog.fingerprint + JSON.stringify(files) + surfaceInputsHash({ commandDirs: commandDirsFor(skillsDir), skillsDir });
789
-
790
- if (!force && fs.existsSync(hashFile)) {
791
- const storedHash = fs.readFileSync(hashFile, 'utf8').trim();
792
- const storedGlobal = fs.existsSync(globalHashFile) ? fs.readFileSync(globalHashFile, 'utf8').trim() : '';
793
- const storedInputs = fs.existsSync(inputsHashFile) ? fs.readFileSync(inputsHashFile, 'utf8').trim() : '';
794
- if (storedHash === currentHash && storedGlobal === globalHash && storedInputs === inputsHash &&
795
- instructionArtifactsMatch(files, surfaceDir, artifactsFile)) {
796
- return {
797
- nodesProcessed: nodes.length,
798
- skillsInjected: 0,
799
- semanticIndexed: 0,
800
- semanticUnavailable: false,
801
- skipped: true,
802
- reason: 'vault-hash-unchanged'
803
- };
804
- }
796
+ const inputsHash = crypto.createHash('sha256').update(ENTRY_BLOCK).update(JSON.stringify(files.map(file => path.relative(surfaceDir, file)))).digest('hex');
797
+ const stored = file => (fs.existsSync(file) ? fs.readFileSync(file, 'utf8').trim() : null);
798
+ const entryCurrent = !force && stored(inputsHashFile) === inputsHash && instructionArtifactsMatch(files, surfaceDir, artifactsFile);
799
+ const vaultCurrent = !force && stored(hashFile) === currentHash;
800
+ const cliCurrent = !force && stored(cliHashFile) === catalog.fingerprint;
801
+
802
+ if (entryCurrent && vaultCurrent && cliCurrent) {
803
+ return {
804
+ nodesProcessed: nodes.length,
805
+ skillsInjected: 0,
806
+ semanticIndexed: 0,
807
+ semanticUnavailable: false,
808
+ skipped: true,
809
+ reason: 'vault-hash-unchanged'
810
+ };
805
811
  }
806
812
 
807
- // 1. Write pointer and active rules to all instruction shims
808
- const skillsInjected = await compilePointers(instructionsFile, skillsDir, ruleNodes, { vaultDir, derivedDir, force, files });
809
-
810
- // 2. Build derived indexes (powers semantic search API)
811
813
  if (!fs.existsSync(derivedDir)) {
812
814
  fs.mkdirSync(derivedDir, { recursive: true });
813
815
  }
814
816
 
817
+ // 1. Entry files, only when the entry block or client list changed (or a file drifted).
818
+ const skillsInjected = entryCurrent ? 0 : await compilePointers(instructionsFile, skillsDir, ruleNodes, { vaultDir, derivedDir, force, files });
819
+ if (!entryCurrent) {
820
+ atomicWrite(inputsHashFile, inputsHash);
821
+ atomicWrite(artifactsFile, JSON.stringify({ version: 1, files: files.map(file => describeInstruction(file, surfaceDir)) }, null, 2));
822
+ }
823
+
824
+ // 2. Derived indexes, only when this vault changed.
825
+ let semanticResult = { indexed: 0, skipped: nodes.length, unavailable: false };
826
+ if (!vaultCurrent) {
827
+
815
828
  const graphIndex = nodes.map(n => ({
816
829
  slug: n.slug,
817
830
  title: n.title,
@@ -844,7 +857,7 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
844
857
  // caller could detect any of it.
845
858
  // Awaiting is cheap in steady state: buildEmbeddingsIndex skips nodes whose
846
859
  // content hash is unchanged, so only genuinely new or edited nodes cost a call.
847
- let semanticResult = { indexed: 0, skipped: nodes.length, unavailable: true };
860
+ semanticResult = { indexed: 0, skipped: nodes.length, unavailable: true };
848
861
  if (!semantic || process.env.TR_EMBEDDINGS_DISABLED === '1') {
849
862
  logger.info('surface', 'Semantic embeddings explicitly disabled; local indexes updated.', { derivedDir });
850
863
  } else try {
@@ -869,9 +882,18 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
869
882
  semanticResult = { indexed: 0, skipped: nodes.length, unavailable: true, error: err.message };
870
883
  }
871
884
 
872
- // 3b. Build or update the CLI semantic index.
873
- let cliIndexResult = { built: 0, skipped: 0, failed: 0 };
885
+ // 4. Generate Obsidian Canvas
874
886
  try {
887
+ generateCanvas(nodes, vaultDir);
888
+ } catch { /* non-fatal */ }
889
+
890
+ atomicWrite(hashFile, currentHash);
891
+ writeProjectionManifest(derivedDir, currentHash);
892
+ }
893
+
894
+ // 3. The CLI semantic index, only when this brain's plugin catalog changed.
895
+ let cliIndexResult = { built: 0, skipped: 0, failed: 0 };
896
+ if (!cliCurrent) try {
875
897
  const { buildCliSemanticIndex } = await import('./cli-semantic-index.mjs');
876
898
  const projectRoot = skillsDir ? path.dirname(path.dirname(skillsDir)) : process.cwd();
877
899
  cliIndexResult = await buildCliSemanticIndex({
@@ -884,22 +906,11 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
884
906
  if (cliIndexResult.built > 0) {
885
907
  logger.info('surface', `CLI semantic index: ${cliIndexResult.built} indexed, ${cliIndexResult.skipped} skipped.`, { derivedDir });
886
908
  }
909
+ atomicWrite(cliHashFile, catalog.fingerprint);
887
910
  } catch (err) {
888
911
  logger.warn('surface', `CLI semantic index build non-fatal warning: ${err.message}`, { derivedDir });
889
912
  }
890
913
 
891
- // 4. Generate Obsidian Canvas
892
- try {
893
- generateCanvas(nodes, vaultDir);
894
- } catch { /* non-fatal */ }
895
-
896
- // 5. Write vault hash + projection manifest
897
- atomicWrite(hashFile, currentHash);
898
- atomicWrite(globalHashFile, globalHash);
899
- atomicWrite(inputsHashFile, inputsHash);
900
- atomicWrite(artifactsFile, JSON.stringify({ version: 1, files: files.map(file => describeInstruction(file, surfaceDir)) }, null, 2));
901
- writeProjectionManifest(derivedDir, currentHash);
902
-
903
914
  return {
904
915
  nodesProcessed: nodes.length,
905
916
  skillsInjected,
@@ -9,7 +9,9 @@ import fs from 'node:fs';
9
9
  const hash = text => crypto.createHash('sha256').update(String(text)).digest('hex');
10
10
  const STOP = new Set('a an and are as at be before by can do for from how i in is it of on or our should so that the their this to use we what when which with you'.split(' '));
11
11
  export const taskTerms = task => [...new Set((String(task).toLowerCase().match(/[\p{L}\p{N}_-]{3,}/gu) || []).filter(term => !STOP.has(term)))];
12
- const overlap = (terms, text) => terms.filter(term => String(text).toLowerCase().includes(term)).length / Math.max(1, terms.length);
12
+ // Task terms contain only letters, digits, underscores and hyphens. Hyphens also delimit source-reference segments.
13
+ const taskMatchers = task => taskTerms(task).map(term => new RegExp(`(?<![\\p{L}\\p{N}_])${term}(?![\\p{L}\\p{N}_])`, 'iu'));
14
+ const overlap = (matchers, text) => matchers.filter(matcher => matcher.test(text)).length / Math.max(1, matchers.length);
13
15
  export const summarizeRetrieval = summary => summary ? { mode: summary.mode, selected: summary.selected,
14
16
  candidates: summary.candidates || 0, providers: (summary.coverage || []).map(row => row.provider),
15
17
  ...(summary.duplicatePassages ? { duplicatePassages: summary.duplicatePassages } : {}),
@@ -18,11 +20,11 @@ export const summarizeRetrieval = summary => summary ? { mode: summary.mode, sel
18
20
 
19
21
  /** Preserve the best matching line and bounded neighboring text, including a match late in a long body. */
20
22
  export function extractTaskPassage(text, task, { maxChars = 1000, anchorLine } = {}) {
21
- const lines = String(text || '').split('\n'), terms = taskTerms(task);
23
+ const lines = String(text || '').split('\n'), matchers = taskMatchers(task);
22
24
  let anchor = Number.isInteger(anchorLine) ? Math.max(0, Math.min(lines.length - 1, anchorLine - 1)) : 0;
23
25
  if (!Number.isInteger(anchorLine)) {
24
26
  let best = -1;
25
- lines.forEach((line, index) => { const score = overlap(terms, line); if (score > best) { best = score; anchor = index; } });
27
+ lines.forEach((line, index) => { const score = overlap(matchers, line); if (score > best) { best = score; anchor = index; } });
26
28
  }
27
29
  let start = Math.max(0, anchor - 2), end = Math.min(lines.length, anchor + 3);
28
30
  while (lines.slice(start, end).join('\n').length > maxChars && (start < anchor || end > anchor + 1)) {
@@ -32,8 +34,7 @@ export function extractTaskPassage(text, task, { maxChars = 1000, anchorLine } =
32
34
  let passage = lines.slice(start, end).join('\n');
33
35
  let startColumn = 1;
34
36
  if (passage.length > maxChars) {
35
- const lower = passage.toLowerCase();
36
- const hits = terms.map(term => lower.indexOf(term)).filter(index => index >= 0);
37
+ const hits = matchers.map(matcher => passage.search(matcher)).filter(index => index >= 0);
37
38
  const focus = hits.length ? Math.min(...hits) : 0;
38
39
  const offset = Math.max(0, Math.min(passage.length - maxChars, focus - Math.floor(maxChars / 3)));
39
40
  passage = passage.slice(offset, offset + maxChars); startColumn += offset;
@@ -50,8 +51,8 @@ function renderEvidence(item) {
50
51
  export async function retrieveTaskKnowledge({ query, nodes = [], derivedDir, indexScopes,
51
52
  projectRoot = process.cwd(), actions = [], semantic = true, budget = 1200, limit = 6,
52
53
  embedding = getEmbedding, loadIndex = loadEmbeddingsIndex, providers = retrievePluginContext } = {}) {
53
- const terms = taskTerms(query);
54
- if (!terms.length) return { contributions: [], summary: { mode: 'none', selected: 0, diagnostics: [], coverage: [] }, queryVector: null };
54
+ const matchers = taskMatchers(query);
55
+ if (!matchers.length) return { contributions: [], summary: { mode: 'none', selected: 0, diagnostics: [], coverage: [] }, queryVector: null };
55
56
  const diagnostics = [], indexes = new Map();
56
57
  const scopes = indexScopes || (derivedDir ? [{ layer: '*', derivedDir }] : []);
57
58
  for (const scope of scopes) {
@@ -77,7 +78,7 @@ export async function retrieveTaskKnowledge({ query, nodes = [], derivedDir, ind
77
78
  if (stale) staleVectors++;
78
79
  const valid = !stale && Array.isArray(entry?.embedding) && Array.isArray(queryVector) && entry.embedding.length === queryVector.length;
79
80
  if (valid) vectors++;
80
- const lexical = overlap(terms, `${node.title || ''}\n${body}`);
81
+ const lexical = overlap(matchers, `${node.title || ''}\n${body}`);
81
82
  const similarity = valid ? cosineSimilarity(queryVector, entry.embedding) : 0;
82
83
  const score = valid ? Math.max(0, similarity) * .75 + lexical * .25 : lexical * .7;
83
84
  return { node, layer, body, score, relevant: lexical > 0 || similarity >= .35 };
@@ -1,4 +1,3 @@
1
- import { loadPluginModule as __loadOwnedPlugin } from './plugin-runtime.mjs';
2
1
  /**
3
2
  * Executor registry for daemon tasks.
4
3
  *
@@ -41,43 +40,18 @@ function policyCheck(task) {
41
40
 
42
41
  async function runDream(task, ctx) {
43
42
  const { runDreamCycle } = await import('./dream.mjs');
44
- await runDreamCycle({
43
+ const result = await runDreamCycle({
45
44
  vaultDir: ctx.vaultDir,
46
45
  skillsDir: ctx.skillsDir,
47
46
  derivedDir: ctx.derivedDir,
48
47
  conflictsDir: ctx.conflictsDir,
49
48
  instructionsFile: ctx.instructionsFile,
49
+ runtimeConfig: ctx.runtimeConfig,
50
50
  });
51
+ if (result?.status !== 'success') return { success: false, error: result?.errors?.join('; ') || 'unsupported', executor: 'dream' };
51
52
  return { success: true, output: 'Dream cycle complete', executor: 'dream' };
52
53
  }
53
54
 
54
- async function runSessionIngest(task, ctx) {
55
- const { scanAndIngest } = await import('./session-watcher.mjs');
56
- const result = scanAndIngest(ctx.sessionsDir);
57
- return {
58
- success: true,
59
- output: `Ingested ${result.ingested} new sessions`,
60
- executor: 'session-ingest',
61
- };
62
- }
63
-
64
- async function runSurfaceCompile(task, ctx) {
65
- const { compileSurface } = await import('./surface.mjs');
66
- await compileSurface({
67
- vaultDir: ctx.vaultDir,
68
- skillsDir: ctx.skillsDir,
69
- derivedDir: ctx.derivedDir,
70
- instructionsFile: ctx.instructionsFile,
71
- });
72
- return { success: true, output: 'Surface recompiled', executor: 'surface-compile' };
73
- }
74
-
75
- async function runPrune(task, ctx) {
76
- const { autoPruneStorage } = await import('./dream.mjs');
77
- autoPruneStorage(ctx.brainDir, ctx.vaultDir, ctx.conflictsDir);
78
- return { success: true, output: 'Storage prune complete', executor: 'prune' };
79
- }
80
-
81
55
  async function runCustom(task, ctx) {
82
56
  const land = task.result?.land || 'inbox';
83
57
  const intent = task.intent || task.body || task.reason || task.slug;
@@ -89,8 +63,8 @@ async function runCustom(task, ctx) {
89
63
  message: `Custom task (log only): ${task.slug} — ${intent}`,
90
64
  });
91
65
  return {
92
- success: true,
93
- output: `Custom task recorded (no vault write): ${intent}`.slice(0, 500),
66
+ success: false,
67
+ error: 'unsupported',
94
68
  executor: 'custom',
95
69
  };
96
70
  }
@@ -207,7 +181,7 @@ async function runLegacyCategoryDispatch(task, ctx) {
207
181
  }
208
182
 
209
183
  if (category === 'system2-deliberation') {
210
- if (process.env.TR_POWER_EXECUTORS !== '1' && !task._research_id) {
184
+ if (process.env.TR_POWER_EXECUTORS !== '1' && !task._research_id && !(task.created_by === 'automations' && task.payload?.scheduledIdle)) {
211
185
  return {
212
186
  success: false,
213
187
  error: 'system2-deliberation requires TR_POWER_EXECUTORS=1 (or research-queue phase)',
@@ -221,6 +195,7 @@ async function runLegacyCategoryDispatch(task, ctx) {
221
195
  inboxDir: path.join(BRAIN_DIR, 'memory-inbox', 'pending'),
222
196
  runtimeConfig,
223
197
  });
198
+ if (result.error || result.success === false) return { success: false, error: result.error || 'Inference failed', executor: 'system2' };
224
199
  return {
225
200
  success: true,
226
201
  output: `Inference complete: ${result.conclusions?.length || 0} conclusions`,
@@ -253,7 +228,9 @@ async function runLegacyCategoryDispatch(task, ctx) {
253
228
  vaultDir: VAULT_DIR,
254
229
  inboxDir: path.join(BRAIN_DIR, 'memory-inbox', 'pending'),
255
230
  runtimeConfig,
231
+ fromTurn: 1,
256
232
  });
233
+ if (result.error) return { success: false, error: result.error, executor: 'post-mortem' };
257
234
  return {
258
235
  success: true,
259
236
  output: `Post-mortem: ${result.nodesCreated || 0} nodes`,
@@ -280,6 +257,7 @@ async function runLegacyCategoryDispatch(task, ctx) {
280
257
  inboxDir: path.join(BRAIN_DIR, 'memory-inbox', 'pending'),
281
258
  runtimeConfig,
282
259
  });
260
+ if (result.error || result.success === false) return { success: false, error: result.error || 'Clarity review failed', executor: 'clarity' };
283
261
  return {
284
262
  success: true,
285
263
  output: `Clarity review: rewrote=${result.rewrote}`,
@@ -287,17 +265,8 @@ async function runLegacyCategoryDispatch(task, ctx) {
287
265
  };
288
266
  }
289
267
  if (String(task.slug || '').startsWith('memory-compaction-')) {
290
- const { runMemoryCompaction } = await import('./fact-seeker.mjs');
291
- const result = await runMemoryCompaction({
292
- vaultDir: VAULT_DIR,
293
- inboxDir: path.join(BRAIN_DIR, 'memory-inbox', 'pending'),
294
- runtimeConfig,
295
- });
296
- return {
297
- success: true,
298
- output: `Compaction: ${result.consolidatedCount || 0} nodes`,
299
- executor: 'compaction',
300
- };
268
+ const { fileCleanupProposals } = await import('./cleanup-task.mjs');
269
+ return fileCleanupProposals(VAULT_DIR);
301
270
  }
302
271
  // generic maintenance: lease vacuum
303
272
  try {
@@ -324,10 +293,10 @@ async function runLegacyCategoryDispatch(task, ctx) {
324
293
  return { success: true, output: 'Maintenance complete', executor: 'maintenance' };
325
294
  }
326
295
 
327
- if (category === 'exploration' || category === 'self-evaluation' || category === 'skill-engineering') {
296
+ if (category === 'self-evaluation' || category === 'skill-engineering') {
328
297
  return {
329
- success: true,
330
- output: `Acknowledged category ${category} (no-op in core mode)`,
298
+ success: false,
299
+ error: 'unsupported',
331
300
  executor: category,
332
301
  };
333
302
  }
@@ -335,7 +304,7 @@ async function runLegacyCategoryDispatch(task, ctx) {
335
304
  // Unknown — fail loud
336
305
  return {
337
306
  success: false,
338
- error: `Unknown task executor/category: executor=${task.executor || 'null'} category=${category || 'null'} kind=${task.kind || 'null'} slug=${task.slug}`,
307
+ error: 'unsupported',
339
308
  executor: 'unknown',
340
309
  };
341
310
  }
@@ -375,7 +344,7 @@ async function runResearchLegacy(task, ctx) {
375
344
  // researched — so it is now a no-op (RESEARCH_SYSTEM2).
376
345
  const topic = String(task.target || '').trim();
377
346
  if (!topic || topic === 'global') {
378
- return { success: true, output: 'No research topic on this task — nothing to research.', executor: 'research' };
347
+ return { success: false, error: 'unsupported', executor: 'research' };
379
348
  }
380
349
 
381
350
  // Prefer deep multi-source research when this came from the research queue
@@ -514,7 +483,7 @@ async function runResearchLegacy(task, ctx) {
514
483
  quarantineDir: path.join(BRAIN_DIR, 'memory-inbox', 'quarantine'),
515
484
  });
516
485
  if (result.skipped) {
517
- return { success: true, output: `Conclusion writer: ${result.skipped}`, executor: 'research' };
486
+ return { success: false, error: 'unsupported', executor: 'research' };
518
487
  }
519
488
  return {
520
489
  success: true,
@@ -525,7 +494,7 @@ async function runResearchLegacy(task, ctx) {
525
494
 
526
495
  return {
527
496
  success: false,
528
- error: `No research handler matched slug=${task.slug} category=${task.category}`,
497
+ error: 'unsupported',
529
498
  executor: 'research',
530
499
  };
531
500
  }
@@ -571,47 +540,39 @@ function findLatestResearchDraftSlug(inboxDir, topic) {
571
540
 
572
541
  // ─── Registry ───────────────────────────────────────────────────────────────────
573
542
 
574
- async function runSecretsRotationCheck(task, ctx) {
575
- const { runSecretsRotationCheck: run } = await __loadOwnedPlugin('auth', 'secrets-rotate');
576
- return run(ctx);
577
- }
578
-
579
- async function runSecretsExportEnv(task, ctx) {
580
- const { runSecretsExportAll } = await __loadOwnedPlugin('auth', 'secrets-rotate');
581
- return runSecretsExportAll(ctx);
543
+ async function runSkillGap(task, ctx) {
544
+ const { requestSkillGap } = await import('./skill-gap.mjs');
545
+ const topic = task.payload?.topic || task.target || /topic "([^"]+)"/.exec(task.reason || '')?.[1];
546
+ const session = task.source_session || task.origin?.session_id;
547
+ return requestSkillGap(topic, task.payload?.evidence ||
548
+ (session ? `session://${session}` : null), { projectRoot: ctx.projectRoot || process.cwd() });
582
549
  }
583
550
 
584
- async function runSecretsRotationAssist(task, ctx) {
585
- // Supervised: ensure prompt is present; does not auto-browser without agent session
586
- const key = task.payload?.secret_key || task.slug?.replace(/^secret-rotate-/, '');
587
- if (!key) {
588
- return { success: false, error: 'missing secret_key', executor: 'secrets-rotation-assist' };
551
+ async function runLessonReview(task, ctx) {
552
+ const { runPostMortem } = await import('./post-mortem.mjs');
553
+ const sessionPath = task.payload?.sessionPath || (task.target && path.join(ctx.sessionsDir, task.target));
554
+ if (!sessionPath) return { success: false, error: 'Lesson review requires a session transcript and turn range' };
555
+ const result = await runPostMortem(sessionPath, {
556
+ vaultDir: ctx.vaultDir, inboxDir: path.join(ctx.brainDir, 'memory-inbox', 'pending'), runtimeConfig: ctx.runtimeConfig,
557
+ // Scheduled snapshots already contain exactly the requested range.
558
+ fromTurn: task.payload?.sessionPath ? 1 : task.payload?.fromTurn,
559
+ toTurn: task.payload?.sessionPath ? Infinity : task.payload?.toTurn,
560
+ sourceRef: task.payload?.sessionId ? `${task.payload.sessionId}/turns/${task.payload.fromTurn}-${task.payload.toTurn}` : task.slug,
561
+ hold: task.payload?.hold === true,
562
+ });
563
+ if (result.error) return { success: false, error: result.error, executor: 'lesson-review' };
564
+ if (task.slug?.startsWith('lesson-review-')) {
565
+ const dir = path.join(ctx.brainDir, 'lesson-reviews');
566
+ fs.mkdirSync(dir, { recursive: true });
567
+ atomicWrite(path.join(dir, `${task.slug}.done`), new Date().toISOString());
589
568
  }
590
- const { getBrowserRotateAssist } = await __loadOwnedPlugin('auth', 'secrets-rotate');
591
- const assist = await getBrowserRotateAssist(ctx.brainDir, key);
592
- return {
593
- success: true,
594
- output: `Browser rotation assist ready for ${key}. Console: ${assist.console_url || 'n/a'}. Use browser tools with human 2FA; then secret rotate --export-env.`,
595
- executor: 'secrets-rotation-assist',
596
- assist: {
597
- key: assist.key,
598
- console_url: assist.console_url,
599
- docs_url: assist.docs_url,
600
- overdue: assist.overdue,
601
- // prompt available to agent; do not echo secrets
602
- prompt: assist.prompt,
603
- },
604
- };
569
+ return { success: true, output: `Lesson review: ${result.patterns?.length || 0} patterns, ${result.facts?.length || 0} facts, ${result.skill_gaps?.length || 0} skill gaps, ${result.violations?.length || 0} violations`, executor: 'lesson-review' };
605
570
  }
606
571
 
607
572
  const EXECUTORS = {
608
- 'secrets-rotation-check': { id: 'secrets-rotation-check', run: runSecretsRotationCheck },
609
- 'secrets-export-env': { id: 'secrets-export-env', run: runSecretsExportEnv },
610
- 'secrets-rotation-assist': { id: 'secrets-rotation-assist', run: runSecretsRotationAssist },
573
+ 'lesson-review': { id: 'lesson-review', run: runLessonReview },
574
+ 'skill-manager': { id: 'skill-manager', run: runSkillGap },
611
575
  dream: { id: 'dream', run: runDream },
612
- 'session-ingest': { id: 'session-ingest', run: runSessionIngest },
613
- 'surface-compile': { id: 'surface-compile', run: runSurfaceCompile },
614
- prune: { id: 'prune', run: runPrune },
615
576
  custom: { id: 'custom', run: runCustom },
616
577
  research: { id: 'research', run: runResearchLegacy },
617
578
  legacy: { id: 'legacy', run: runLegacyDispatch },
@@ -623,31 +584,16 @@ const EXECUTORS = {
623
584
  export function resolveExecutor(task) {
624
585
  const t = normalizeTask(task);
625
586
 
626
- if (t.executor && EXECUTORS[t.executor]) {
627
- return EXECUTORS[t.executor];
628
- }
587
+ if (t.category === 'skill-engineering') return EXECUTORS['skill-manager'];
588
+ if (t.category === 'lesson-review' || t.category === 'self-evaluation') return EXECUTORS['lesson-review'];
589
+
590
+ // An explicit unsupported executor must never fall through to kind/category.
591
+ if (t.executor) return typeof t.executor === 'string' && Object.hasOwn(EXECUTORS, t.executor) ? EXECUTORS[t.executor] : null;
629
592
 
630
593
  // System / named slugs
631
594
  if (t.slug === 'dream' || String(t.slug || '').startsWith('dream-') || t.executor === 'dream') {
632
595
  return EXECUTORS.dream;
633
596
  }
634
- if (String(t.slug || '').startsWith('session-ingest')) return EXECUTORS['session-ingest'];
635
- if (String(t.slug || '').startsWith('surface-compile')) return EXECUTORS['surface-compile'];
636
- if (String(t.slug || '').startsWith('prune-') || t.slug === 'prune') return EXECUTORS.prune;
637
- if (String(t.slug || '').startsWith('secret-rotate-') || t.category === 'secrets-rotation') {
638
- return EXECUTORS['secrets-rotation-assist'];
639
- }
640
- if (String(t.slug || '').startsWith('secrets-rotation-check') || t.executor === 'secrets-rotation-check') {
641
- return EXECUTORS['secrets-rotation-check'];
642
- }
643
- if (String(t.slug || '').startsWith('secrets-export') || t.executor === 'secrets-export-env') {
644
- return EXECUTORS['secrets-export-env'];
645
- }
646
-
647
- if (t.kind === 'custom' || t.category === 'custom' || t.executor === 'custom') {
648
- return EXECUTORS.custom;
649
- }
650
-
651
597
  // Known legacy categories still registered
652
598
  const legacyCategories = new Set([
653
599
  'conscious-enforcement',
@@ -658,13 +604,14 @@ export function resolveExecutor(task) {
658
604
  'proactive-research',
659
605
  'skill-engineering',
660
606
  'self-evaluation',
661
- 'exploration',
662
607
  ]);
663
608
 
664
609
  if (legacyCategories.has(t.category) || t._research_id || t._is_remote_vault_sync) {
665
610
  return EXECUTORS.legacy;
666
611
  }
667
612
 
613
+ if (t.category === 'custom' || (t.kind === 'custom' && !t.category)) return EXECUTORS.custom;
614
+
668
615
  if (t.kind === 'research') return EXECUTORS.research;
669
616
  if (t.kind === 'system' || t.kind === 'maintenance' || t.kind === 'memory') {
670
617
  return EXECUTORS.legacy;
@@ -691,11 +638,12 @@ export async function dispatchTask(task, ctx) {
691
638
  `Unknown task executor for slug=${normalized.slug} ` +
692
639
  `category=${normalized.category} kind=${normalized.kind} executor=${normalized.executor}`;
693
640
  logger.info({ subsystem: 'task-executors', message: msg });
694
- return { success: false, error: msg, executor: 'unknown' };
641
+ return { success: false, error: 'unsupported', executor: 'unknown' };
695
642
  }
696
643
 
697
644
  try {
698
645
  const result = await executor.run(normalized, ctx);
646
+ if (!result || typeof result.success !== 'boolean') return { success: false, error: 'unsupported', executor: executor.id };
699
647
  return { ...result, executor: result.executor || executor.id };
700
648
  } catch (err) {
701
649
  return {
@@ -15,6 +15,8 @@ import { processOperationAsync } from './operation-validator.mjs';
15
15
  import { safeStringify, isSafeVaultName } from './vault.mjs';
16
16
  import { invalidate } from './vault-cache.mjs';
17
17
 
18
+ export const supportsExpectedBeforeHash = true;
19
+
18
20
  /**
19
21
  * Normalize a vault node so package universal frontmatter + TR memory v2 pass.
20
22
  */
@@ -101,6 +103,7 @@ function buildEnvelope(node, options = {}) {
101
103
  const {
102
104
  workspaceId = 'default',
103
105
  idempotencyKey,
106
+ expectedBeforeHash,
104
107
  dryRun = false,
105
108
  path: pathOverride,
106
109
  } = options;
@@ -124,6 +127,7 @@ function buildEnvelope(node, options = {}) {
124
127
  path: vfsPath,
125
128
  workspace_id: workspaceId,
126
129
  content,
130
+ ...(expectedBeforeHash === undefined ? {} : { expected_before_hash: expectedBeforeHash }),
127
131
  dry_run: !!dryRun,
128
132
  actor: { role: options.agentRole || 'system' },
129
133
  };
@@ -152,6 +156,7 @@ export async function writeNodeValidatedAsync(node, vaultDir, options = {}) {
152
156
  agentRole,
153
157
  leaseStore,
154
158
  eventLogDir,
159
+ allowLong: options.allowLong === true,
155
160
  });
156
161
 
157
162
  if (result.success && !dryRun) {
@@ -8,6 +8,12 @@ export function isSafeVaultName(name) {
8
8
 
9
9
  export function atomicWrite(filePath, content) {
10
10
  const tmpPath = `${filePath}.tmp.${process.pid}`;
11
- fs.writeFileSync(tmpPath, content, 'utf8');
12
- fs.renameSync(tmpPath, filePath);
11
+ try {
12
+ fs.writeFileSync(tmpPath, content, 'utf8');
13
+ fs.renameSync(tmpPath, filePath);
14
+ } catch (error) {
15
+ // A leftover copy is untracked and unignored, so a commit of everything would publish it.
16
+ fs.rmSync(tmpPath, { force: true });
17
+ throw error;
18
+ }
13
19
  }
@@ -33,7 +33,7 @@ function stripUndefined(obj) {
33
33
  * @returns {string} The serialized markdown with frontmatter
34
34
  */
35
35
  export function safeStringify(content, data) {
36
- return matter.stringify(content ?? '', stripUndefined(data ?? {}));
36
+ return matter.stringify({ content: content ?? '', data: stripUndefined(data ?? {}) });
37
37
  }
38
38
 
39
39
  /**