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
@@ -80,11 +80,10 @@ export const CLIENTS = {
80
80
  },
81
81
  openclaw: {
82
82
  label: 'OpenClaw',
83
- mode: 'openclaw',
83
+ mode: 'symlink',
84
84
  enforcement: 'enforced',
85
85
  enforcementReason: 'before_tool_call hook denies unreceipted tool calls with exit 2',
86
- memoryTarget: 'MEMORY.md',
87
- agentsTarget: 'AGENTS.md',
86
+ target: 'AGENTS.md',
88
87
  },
89
88
  cursor: {
90
89
  label: 'Cursor',
@@ -880,32 +879,6 @@ export default async function connect(args) {
880
879
  result.notes.push(` DeepSeek Harness MEMORY.md → ${globalMemPath}`);
881
880
  }
882
881
  result.notes.push(` DeepSeek Harness AGENTS.md → INSTRUCTIONS.md`);
883
- } else if (preset.mode === 'openclaw') {
884
- // OpenClaw: write MEMORY.md + symlink AGENTS.md in cwd
885
- const instructions = readInstructions(cwd, agentDir);
886
- if (!instructions) throw new Error('No INSTRUCTIONS.md found. Run npx total-recall init or compile first.');
887
- // MEMORY.md — curated facts extracted from instructions
888
- const memPath = path.join(cwd, preset.memoryTarget);
889
- if (!fs.existsSync(memPath) || opts.force) {
890
- fs.writeFileSync(memPath, instructions, 'utf8');
891
- }
892
- // AGENTS.md — symlink to INSTRUCTIONS.md
893
- const agentsPath = path.join(cwd, preset.agentsTarget);
894
- if (pathExists(agentsPath)) {
895
- if (opts.force) {
896
- try {
897
- fs.unlinkSync(agentsPath);
898
- } catch {
899
- fs.rmSync(agentsPath, { force: true, recursive: true });
900
- }
901
- fs.symlinkSync('INSTRUCTIONS.md', agentsPath);
902
- }
903
- } else {
904
- fs.symlinkSync('INSTRUCTIONS.md', agentsPath);
905
- }
906
- result.projection = { targetPath: memPath, action: 'written' };
907
- result.notes.push(` OpenClaw MEMORY.md written to ${memPath}`);
908
- result.notes.push(` OpenClaw AGENTS.md → INSTRUCTIONS.md`);
909
882
  } else if (preset.mode === 'vault') {
910
883
  result.projection = writeVaultProjection(agentDir, preset, opts);
911
884
  const folderName = result.projection.folderName || preset.folderName;
@@ -61,6 +61,7 @@ async function printCompacted({ query, actions, budget, format, version, rulesHa
61
61
  if (format !== 'json') return result.context;
62
62
  const payload = { ready: true, compacted: true, ranking: result.ranking, context: result.context, counts: result.counts,
63
63
  stats: { version, rules_hash: rulesHash, budget, retrieval: summarizeRetrieval(knowledge?.summary), token_measurement: 'estimated_chars_divided_by_four' } };
64
+ if (result.evidenceStart !== undefined) payload.evidence_start = result.evidenceStart;
64
65
  for (let index = 0; index < 10; index++) {
65
66
  const tokens = estimateTokens(`${JSON.stringify(payload)}\n`);
66
67
  if (tokens === payload.stats.total_tokens) break;
package/src/cli/dream.mjs CHANGED
@@ -1,84 +1,108 @@
1
- /**
2
- * total-recall dream
3
- *
4
- * Manually trigger a single dream cycle (Light → REM → Deep Sleep).
5
- *
6
- * Usage:
7
- * npx total-recall dream [options]
8
- *
9
- * Options:
10
- * --vault <path> Override vault directory (default: ~/.agent/memory-vault)
11
- * --help Show this help
12
- */
13
-
1
+ /** Run one dream cycle per selected brain. Scheduling belongs to automations. */
2
+ import fs from 'node:fs';
14
3
  import path from 'node:path';
15
- import { fileURLToPath } from 'node:url';
16
- import { resolveAgentDir, resolveBrainDir, parseLayerFlag, resolveSurfaceDir } from './agent-dir.mjs';
17
-
18
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
4
+ import { resolveAgentDir, resolveBrainDir, resolveSurfaceDir, getGlobalBrainDir, getGlobalAgentDir } from './agent-dir.mjs';
5
+ import { listUpdateRoots } from '../core/package-auto-update.mjs';
19
6
 
20
- function parseArgs(args) {
21
- const opts = { vault: null, help: false };
7
+ export function parseDreamArgs(args) {
8
+ const opts = { layer: 'global', allBrains: false, dryRun: false, json: false };
9
+ let explicitLayer;
22
10
  for (let i = 0; i < args.length; i++) {
23
- switch (args[i]) {
24
- case '--vault': opts.vault = args[++i]; break;
25
- case '--help': case '-h': opts.help = true; break;
26
- }
11
+ const arg = args[i];
12
+ if (arg === '--all-brains') opts.allBrains = true;
13
+ else if (arg === '--dry-run') opts.dryRun = true;
14
+ else if (arg === '--json') opts.json = true;
15
+ else if (arg === '--help' || arg === '-h') opts.help = true;
16
+ else if (arg === '--global' || arg === '--project') {
17
+ if (explicitLayer && explicitLayer !== arg) throw new Error('Choose --global or --project.');
18
+ explicitLayer = arg;
19
+ opts.layer = arg.slice(2);
20
+ } else if (arg === '--vault') {
21
+ if (!args[i + 1] || args[i + 1].startsWith('-')) throw new Error('--vault requires a path.');
22
+ opts.vault = path.resolve(args[++i]);
23
+ } else throw new Error(`Unknown dream option: ${arg}`);
27
24
  }
25
+ if (opts.allBrains && (opts.vault || explicitLayer)) throw new Error('--all-brains cannot be combined with --vault, --global or --project.');
28
26
  return opts;
29
27
  }
30
28
 
31
- function printHelp() {
32
- console.log(`
33
- total-recall dream — Manually trigger a dream cycle
29
+ function target(brainDir, agentDir, surfaceDir, root) {
30
+ return { root, brainDir, vaultDir: path.join(brainDir, 'memory-vault'),
31
+ derivedDir: path.join(brainDir, 'memory-derived'), skillsDir: path.join(agentDir, 'skills'),
32
+ conflictsDir: path.join(brainDir, 'memory-inbox', 'conflicts'),
33
+ instructionsFile: path.join(surfaceDir, 'INSTRUCTIONS.md') };
34
+ }
34
35
 
35
- The dream cycle runs three phases:
36
- 1. Light Sleep — Scan vault for recent modifications
37
- 2. REM — Score, conflict-check, and promote/decay candidates
38
- 3. Deep Sleep — Recompile the surface (indexes + INSTRUCTIONS.md)
36
+ export function discoverDreamBrains({ brainDir = getGlobalBrainDir(), agentDir = getGlobalAgentDir(), roots } = {}) {
37
+ const brains = [target(brainDir, agentDir, agentDir, agentDir)];
38
+ const seen = new Set([fs.existsSync(brainDir) ? fs.realpathSync(brainDir) : path.resolve(brainDir)]);
39
+ for (const { root } of listUpdateRoots({ brainDir, roots })) {
40
+ // Fleet selection is explicit. AGENT_DIR must not redirect project brains.
41
+ const projectAgent = ['.agent', '.agents'].map(name => path.join(root, name))
42
+ .find(dir => fs.existsSync(path.join(dir, 'skills/total-recall/memory-vault'))
43
+ || fs.existsSync(path.join(dir, 'skills/total-recall/SKILL.md')));
44
+ if (!projectAgent) continue;
45
+ const projectBrain = path.join(projectAgent, 'skills/total-recall');
46
+ const real = fs.realpathSync(projectBrain);
47
+ if (seen.has(real)) continue;
48
+ seen.add(real);
49
+ brains.push(target(projectBrain, projectAgent, root, root));
50
+ }
51
+ return brains;
52
+ }
39
53
 
40
- Usage: total-recall dream [options]
54
+ async function previewDecay({ vaultDir, derivedDir }) {
55
+ const [{ loadNodes }, { readAccess }, { decayCandidates }] = await Promise.all([
56
+ import('../core/vault.mjs'), import('../core/access-ledger.mjs'), import('../core/optimizer.mjs'),
57
+ ]);
58
+ return decayCandidates(loadNodes(vaultDir), readAccess(derivedDir)).map(node => node.slug);
59
+ }
41
60
 
42
- Options:
43
- --vault <path> Override vault directory
44
- --global Dream the global brain (default)
45
- --project Dream the project brain for the current repo
46
- --help, -h Show this help
47
- `);
61
+ function printHelp() {
62
+ console.log(`Usage: total-recall dream [--global | --project | --all-brains] [--vault <path>] [--dry-run] [--json]
63
+ Dream consolidates memory and runs phase 4 decay.
64
+ --all-brains Run the global brain and registered project brains on this node, one at a time.
65
+ --dry-run List brains and decay candidates without changes or runtime calls.
66
+ --json Print an array of per-brain results.
67
+ --help, -h Show this help.`);
48
68
  }
49
69
 
50
- export default async function dream(args) {
51
- const { layer, remainingArgs } = parseLayerFlag(args);
52
- const effectiveLayer = layer === 'auto' ? 'global' : layer;
53
- const opts = parseArgs(remainingArgs);
70
+ export default async function dream(args = [], dependencies = {}) {
71
+ let opts;
72
+ try { opts = parseDreamArgs(args); }
73
+ catch (error) { console.error(error.message); process.exitCode = 2; return; }
54
74
  if (opts.help) { printHelp(); return; }
55
-
56
- const agentDir = resolveAgentDir(effectiveLayer);
57
- const brainDir = resolveBrainDir(effectiveLayer);
58
- const vaultDir = opts.vault || path.join(brainDir, 'memory-vault');
59
- const skillsDir = path.join(agentDir, 'skills');
60
- const derivedDir = path.join(brainDir, 'memory-derived');
61
- const conflictsDir = path.join(brainDir, 'memory-inbox', 'conflicts');
62
- const instructionsFile = path.join(resolveSurfaceDir(effectiveLayer), 'INSTRUCTIONS.md');
63
-
64
- console.error('\n 🌙 Starting dream cycle...\n');
65
- const startTime = Date.now();
66
-
75
+ const results = [];
67
76
  try {
68
- const { runDreamCycle } = await import('../core/dream.mjs');
69
- const result = await runDreamCycle({
70
- vaultDir,
71
- skillsDir,
72
- derivedDir,
73
- conflictsDir,
74
- instructionsFile,
75
- });
76
-
77
- const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
78
- console.error(`\n ✅ Dream cycle complete (${elapsed}s) — status: ${result.status}`);
79
- } catch (err) {
80
- console.error(`\n ❌ Dream cycle failed: ${err.message}`);
81
- console.error(err.stack);
82
- process.exit(1);
83
- }
77
+ const brains = opts.allBrains ? (dependencies.discover || discoverDreamBrains)() : [
78
+ target(resolveBrainDir(opts.layer), resolveAgentDir(opts.layer), resolveSurfaceDir(opts.layer), resolveSurfaceDir(opts.layer)),
79
+ ];
80
+ if (opts.vault) {
81
+ brains[0].vaultDir = opts.vault;
82
+ brains[0].brainDir = path.dirname(opts.vault);
83
+ brains[0].derivedDir = path.join(brains[0].brainDir, 'memory-derived');
84
+ brains[0].conflictsDir = path.join(brains[0].brainDir, 'memory-inbox', 'conflicts');
85
+ }
86
+ const run = opts.dryRun ? null : (dependencies.run || (await import('../core/dream.mjs')).runDreamCycle);
87
+ for (const brain of brains) {
88
+ let row;
89
+ try {
90
+ if (!fs.existsSync(brain.vaultDir)) throw new Error(`Vault not found: ${brain.vaultDir}`);
91
+ const result = opts.dryRun
92
+ ? { status: 'dry_run', decay_candidates: await (dependencies.preview || previewDecay)(brain) }
93
+ : await run(brain);
94
+ row = { ...brain, ...result };
95
+ } catch (error) { row = { ...brain, status: 'failed', error: error.message }; }
96
+ results.push(row);
97
+ if (!opts.json) {
98
+ console.log(`${row.brainDir}: ${row.status}${row.error ? ` — ${row.error}` : ''}`);
99
+ if (row.decay_candidates) console.log(` Decay candidates: ${row.decay_candidates.join(', ') || 'none'}`);
100
+ for (const error of row.errors || []) console.error(` ${error}`);
101
+ }
102
+ }
103
+ } catch (error) { results.push({ status: 'failed', error: error.message }); }
104
+ if (opts.json) console.log(JSON.stringify(results, null, 2));
105
+ else if (results.length === 1 && results[0].error && !results[0].brainDir) console.error(results[0].error);
106
+ if (results.some(row => !['success', 'dry_run'].includes(row.status))) process.exitCode = 1;
107
+ return results;
84
108
  }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /** Legacy command door. Native harness behavior is owned by the Meta Harness plugin. */
2
+ /** Legacy command door. Native harness behavior is owned by the Metaharness plugin. */
3
3
  import { checkAnyReceipt, findRoot, ACK_COMMAND } from '../core/preflight-receipt.mjs';
4
4
  import path from 'node:path';
5
5
 
@@ -11,9 +11,9 @@ async function callPlugin(subcommand, args, options = {}) {
11
11
  const result = await runPluginCommand(plugin, { subcommand, args: [...args, '--json'], timeoutMs: 600000, ...options });
12
12
  let data;
13
13
  try { data = JSON.parse(result.output); }
14
- catch { throw new Error(`Meta Harness returned invalid JSON for ${subcommand}`); }
14
+ catch { throw new Error(`Metaharness returned invalid JSON for ${subcommand}`); }
15
15
  if (!result.ok || data?.ok === false) {
16
- const error = new Error(data?.error || `Meta Harness ${subcommand} failed`);
16
+ const error = new Error(data?.error || `Metaharness ${subcommand} failed`);
17
17
  error.result = data;
18
18
  throw error;
19
19
  }
@@ -32,7 +32,7 @@ export async function run(argv = []) {
32
32
  const asJson = args.includes('--json');
33
33
  try {
34
34
  if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
35
- console.log('Usage: total-recall harness list|usage [--node ID] [--json] | dispatch <id> <task> [--node ID --cwd /remote/workspace] [--write] [--allow-unknown-auth] [--json] | council <task> [--allow-unknown-auth] [--json]. Harness operations delegate to the Meta Harness plugin.');
35
+ console.log('Usage: total-recall harness list|usage [--node ID] [--json] | dispatch <id> <task> [--node ID --cwd /remote/workspace] [--write] [--allow-unknown-auth] [--json] | council <task> [--allow-unknown-auth] [--json]. Harness operations delegate to the Metaharness plugin.');
36
36
  return;
37
37
  }
38
38
  const command = args[0] || 'list', positional = [];
package/src/cli/help.mjs CHANGED
@@ -19,7 +19,7 @@ const ROOT = path.join(__dirname, '..', '..');
19
19
  export default async function run(argv) {
20
20
  const args = argv || [];
21
21
  const requestedTopic = args.find(arg => !arg.startsWith('-'))?.toLowerCase() || null;
22
- const topic = ({ server: 'start', 'startup-check': 'startup' })[requestedTopic] || requestedTopic;
22
+ const topic = ({ 'startup-check': 'startup' })[requestedTopic] || requestedTopic;
23
23
  const isJson = args.includes('--json') || args.includes('-j');
24
24
 
25
25
  const cliRefPath = path.join(ROOT, 'docs', 'reference', 'cli-reference.md');
@@ -11,7 +11,7 @@ export async function runOkfIngest(args) {
11
11
  dryRun: false,
12
12
  category: null,
13
13
  importance: null,
14
- onConflict: 'warn',
14
+ onConflict: 'skip',
15
15
  typeMap: null,
16
16
  help: false
17
17
  };
@@ -118,10 +118,10 @@ function printIngestOkfHelp() {
118
118
 
119
119
  Options:
120
120
  --dry-run Validate concepts and preview import without writing
121
- --category <name> Override and force category for all imported concepts
121
+ --category <name> Choose a descriptive category for imported concepts
122
122
  --importance <1-5> Override default importance (default: 3)
123
- --on-conflict <strategy> Strategy for duplicate slugs: 'skip' | 'warn' | 'overwrite' (default: warn)
124
- --type-map <mapping> Custom type mappings. Format: "Type A=facts,Type B=concepts"
123
+ --on-conflict <strategy> 'skip' (default), 'warn' (report and skip), or 'overwrite' (same imported concept)
124
+ --type-map <mapping> Map types to descriptive categories, e.g. "Type A=facts,Type B=concepts"
125
125
  --global Target the global brain
126
126
  --project Target the project brain (default)
127
127
  --help, -h Show this help
package/src/cli/init.mjs CHANGED
@@ -407,31 +407,6 @@ export default async function init(args) {
407
407
  logOk('Default memory vault seeded');
408
408
  }
409
409
 
410
- // ── Step 3.5: Seed the onboarding interview task ──
411
- logStep('3.5/4', 'Seeding onboarding interview into scheduler queue');
412
-
413
- const queueDir = path.join(brainDir, 'scheduler', 'queue');
414
- const interviewDest = path.join(queueDir, 'onboarding-interview.md');
415
- const interviewSrc = path.join(ROOT, 'templates', 'onboarding-interview.md');
416
-
417
- if (!fs.existsSync(interviewDest)) {
418
- if (!opts.dryRun) {
419
- fs.mkdirSync(queueDir, { recursive: true });
420
- if (fs.existsSync(interviewSrc)) {
421
- const now = new Date().toISOString();
422
- const content = fs.readFileSync(interviewSrc, 'utf8').replace(/\{\{CREATED_AT\}\}/g, now);
423
- fs.writeFileSync(interviewDest, content);
424
- logOk('Onboarding interview task queued — agent will conduct it on first chat');
425
- } else {
426
- logWarn('Interview template not found — skipping');
427
- }
428
- } else {
429
- log(` Would write ${path.relative(cwd, interviewDest)}`);
430
- }
431
- } else {
432
- logSkip('onboarding-interview.md already in queue');
433
- }
434
-
435
410
  // Seed non-credential settings. Auth owns password and signing state.
436
411
  if (!opts.dryRun) {
437
412
  const configDir = path.join(brainDir, 'config');
@@ -0,0 +1,51 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { atomicWrite } from '../core/vault.mjs';
5
+ import { parseLayerFlag, resolveBrainDir } from './agent-dir.mjs';
6
+ import { tickLessons, enqueueSessionReviews } from '../core/lessons.mjs';
7
+
8
+ export default async function lessons(args) {
9
+ const { layer, remainingArgs } = parseLayerFlag(args);
10
+ const [command, ...flags] = remainingArgs;
11
+ if (!command || flags.includes('--help') || ['--help', '-h'].includes(command)) {
12
+ console.log('Usage: total-recall lessons tick|session-end|scan [--session ID] [--transcript PATH] [--harness ID] [--interval N] [--hold]\nHooks pass JSON on stdin. Stop queues completed turn ranges; session-end queues the remaining turns. scan reviews locally ingested sessions.');
13
+ return;
14
+ }
15
+ if (!['tick', 'session-end', 'scan'].includes(command)) throw new Error(`Unknown lessons command: ${command}`);
16
+ const opts = {};
17
+ for (let i = 0; i < flags.length; i++) {
18
+ const flag = flags[i];
19
+ if (flag === '--hold') { opts.hold = true; continue; }
20
+ if (!['--session', '--transcript', '--harness', '--interval', '--event'].includes(flag) || !flags[i + 1]) throw new Error(`Invalid lessons option: ${flag}`);
21
+ opts[flag.slice(2)] = flags[++i];
22
+ }
23
+ const brainDir = resolveBrainDir(layer);
24
+ if (command === 'scan') {
25
+ console.log(JSON.stringify(enqueueSessionReviews({ brainDir, interval: opts.interval ? Number(opts.interval) : undefined, hold: opts.hold })));
26
+ return;
27
+ }
28
+ let input = {};
29
+ if (!process.stdin.isTTY && !opts.session) {
30
+ const raw = fs.readFileSync(0, 'utf8');
31
+ if (raw.trim()) input = JSON.parse(raw);
32
+ }
33
+ const history = input.entries || input.conversation_history || input.extra?.conversation_history;
34
+ if (!opts.transcript && !input.transcript_path && Array.isArray(history)) {
35
+ const id = input.session_id || input.sessionId;
36
+ if (!id) throw new Error('Lesson hook history requires a session id');
37
+ const key = crypto.createHash('sha256').update(`${opts.harness || 'unknown'}:${id}`).digest('hex').slice(0, 12);
38
+ const dir = path.join(brainDir, 'sessions');
39
+ fs.mkdirSync(dir, { recursive: true });
40
+ opts.transcript = path.join(dir, `${key}.jsonl`);
41
+ atomicWrite(opts.transcript, history.map(entry => JSON.stringify(entry)).join('\n') + '\n');
42
+ }
43
+ const result = tickLessons({ brainDir, sessionId: opts.session || input.session_id || input.sessionId || input.conversation_id,
44
+ transcriptPath: opts.transcript || input.transcript_path || input.transcriptPath,
45
+ harness: opts.harness || 'unknown', turnId: input.turn_id || input.prompt_id,
46
+ event: command === 'session-end' ? 'SessionEnd' : (opts.event || input.hook_event_name || input.event || 'Stop'),
47
+ interval: opts.interval ? Number(opts.interval) : undefined, hold: opts.hold });
48
+ // Hook JSON must not ask the harness to continue, stop, or approve a tool.
49
+ if (opts.session) console.log(JSON.stringify(result));
50
+ else console.log('{}');
51
+ }
@@ -26,6 +26,6 @@ export default async function plugin(args = []) {
26
26
  const result = await uninstallPlugin(value, { projectRoot: process.cwd(), global: global ? true : undefined });
27
27
  console.log(JSON.stringify(result, null, 2));
28
28
  } else if (['help', '--help', '-h'].includes(action)) {
29
- console.log('Usage: total-recall plugin list|info <id>|install <directory-or-git-url>|remove <id> [--global] [--link]');
29
+ console.log('Usage: total-recall plugin list|info <id>|install <directory-or-git-url>|remove <id> [--link]');
30
30
  } else throw new Error(`Unsupported plugin action '${action}'`);
31
31
  }
@@ -1,17 +1,21 @@
1
1
  /**
2
- * Audit and remove per-repository copies of the shared default plugins.
2
+ * Audit and remove plugin installs inside a repository.
3
3
  *
4
- * One shared plugin folder per machine is the design; copies inside repositories
5
- * are leftovers from an earlier release that put the nine defaults in every
6
- * brain. Only copies matching the bundled source can be deleted. The operator
7
- * can explicitly archive older differing copies to use the shared versions.
4
+ * Nothing is installed per repository: every plugin lives in the one shared
5
+ * folder and a repository only switches it on or off. Entries found in a
6
+ * repository are leftovers. Copies matching the bundled source are deleted;
7
+ * older differing copies can be archived on request; shadow links (to a plugin
8
+ * the shared folder installs, or any linked default) are removed as links only.
9
+ * A repository-only plugin is reported until it is installed in the shared folder.
8
10
  */
9
11
  import { randomUUID } from 'node:crypto';
10
12
  import fs from 'node:fs';
11
13
  import path from 'node:path';
12
14
  import { auditProjectPluginCopies } from '../../core/required-plugins.mjs';
13
- import { globalPluginsDir } from '../../core/plugin-loader.mjs';
14
- import { recompileAfterPluginMutation, replacementArchiveDir } from '../../core/plugin-store.mjs';
15
+ import { globalPluginsDir, projectPluginsDir, vaultForPluginsDir } from '../../core/plugin-loader.mjs';
16
+ import { recompileAfterPluginMutation, replacementArchiveDir, recordPath } from '../../core/plugin-store.mjs';
17
+ import { findVfsDocumentByPath } from '../../core/vfs-documents.mjs';
18
+ import { deleteVfsDocument } from '../../core/ssss-operation-service.mjs';
15
19
 
16
20
  export function pluginCopyRows(projectRoot = process.cwd(), { sharedDir = globalPluginsDir() } = {}) {
17
21
  const audit = auditProjectPluginCopies(projectRoot, { sharedDir });
@@ -19,7 +23,9 @@ export function pluginCopyRows(projectRoot = process.cwd(), { sharedDir = global
19
23
  root: projectRoot,
20
24
  sharedDir,
21
25
  count: audit.count,
22
- removable: audit.copies.filter(copy => copy.edited === false && copy.sharedDir && !copy.linked).map(copy => copy.id),
26
+ removable: audit.copies.filter(copy => (copy.edited === false && copy.sharedDir && !copy.linked) || copy.shadow).map(copy => copy.id),
27
+ shadows: audit.copies.filter(copy => copy.shadow).map(copy => copy.id),
28
+ repoOnly: audit.copies.filter(copy => copy.repoOnly).map(copy => copy.id),
23
29
  archivable: audit.copies.filter(copy => copy.stale && copy.edited !== false && !copy.linked).map(copy => copy.id),
24
30
  edited: audit.copies.filter(copy => copy.edited === true && copy.stale !== true).map(copy => copy.id),
25
31
  stale: audit.copies.filter(copy => copy.stale === true).map(copy => copy.id),
@@ -28,13 +34,49 @@ export function pluginCopyRows(projectRoot = process.cwd(), { sharedDir = global
28
34
  };
29
35
  }
30
36
 
37
+ /**
38
+ * Install records in this repository's brain for plugins that exist neither in
39
+ * the repository nor in the shared folder. They describe a repository install
40
+ * that is gone, so no on/off overlay is lost by removing them.
41
+ */
42
+ export function orphanRecordIds(projectRoot, { sharedDir = globalPluginsDir() } = {}) {
43
+ const vaultRoot = vaultForPluginsDir(projectPluginsDir(projectRoot));
44
+ const dir = path.join(vaultRoot, 'system', 'plugins');
45
+ let files = [];
46
+ try { files = fs.readdirSync(dir).filter(name => name.endsWith('.md')); } catch { return []; }
47
+ return files.map(name => name.slice(0, -3)).filter(id => {
48
+ if (fs.existsSync(path.join(projectPluginsDir(projectRoot), id))) return false;
49
+ if (fs.existsSync(path.join(sharedDir, id))) return false;
50
+ const record = findVfsDocumentByPath(recordPath(id), vaultRoot);
51
+ return record?.frontmatter?.type === 'plugin_record' && record.frontmatter.source?.kind !== 'shared';
52
+ });
53
+ }
54
+
55
+ async function removeOrphanRecords(projectRoot, ids) {
56
+ const vaultRoot = vaultForPluginsDir(projectPluginsDir(projectRoot));
57
+ const removed = [];
58
+ const failed = [];
59
+ for (const id of ids) {
60
+ try {
61
+ await deleteVfsDocument(recordPath(id), { actorRole: 'system', intent: `Remove repository install record ${id}`, vaultRoot });
62
+ removed.push(id);
63
+ } catch (error) {
64
+ failed.push({ id, error: error.message });
65
+ }
66
+ }
67
+ return { removed, failed };
68
+ }
69
+
31
70
  export function removePluginCopies(projectRoot, ids) {
32
71
  const removed = [];
33
72
  const failed = [];
34
73
  for (const id of ids) {
35
74
  const dir = path.join(projectRoot, '.agent', 'skills', 'total-recall', 'plugins', id);
36
75
  try {
37
- fs.rmSync(dir, { recursive: true, force: true });
76
+ // A link is removed by itself: its target checkout and this repository's
77
+ // plugin records (including on/off overlays) are left untouched.
78
+ if (fs.lstatSync(dir).isSymbolicLink()) fs.unlinkSync(dir);
79
+ else fs.rmSync(dir, { recursive: true, force: true });
38
80
  removed.push(id);
39
81
  } catch (error) {
40
82
  failed.push({ id, error: error.message });
@@ -65,28 +107,36 @@ export async function manageProjectPluginCopies(args = []) {
65
107
  const json = args.includes('--json');
66
108
  const apply = args.includes('--apply');
67
109
  const archiveStale = args.includes('--archive-stale');
110
+ const dropRepoOnly = args.includes('--drop-repo-only');
68
111
  const roots = args.filter(argument => !argument.startsWith('-'));
69
112
  const root = roots[0] || process.cwd();
70
113
  const report = pluginCopyRows(root);
71
114
  if (apply) {
72
- report.removal = removePluginCopies(root, report.removable);
115
+ report.removal = removePluginCopies(root, dropRepoOnly ? [...new Set([...report.removable, ...report.repoOnly])] : report.removable);
116
+ report.records = await removeOrphanRecords(root, orphanRecordIds(root, { sharedDir: report.sharedDir }));
73
117
  if (archiveStale) report.archival = archivePluginCopies(report.copies.filter(copy => report.archivable.includes(copy.id)));
74
- if (report.removal.removed.length || report.archival?.archived.length) {
118
+ if (report.removal.removed.length || report.records.removed.length || report.archival?.archived.length) {
75
119
  report.compilation = await recompileAfterPluginMutation(root);
76
120
  }
77
- if (report.removal.failed.length || report.archival?.failed.length || report.compilation?.ok === false) process.exitCode = 1;
121
+ if (report.removal.failed.length || report.records.failed.length || report.archival?.failed.length || report.compilation?.ok === false) process.exitCode = 1;
78
122
  }
123
+ if (!apply) report.orphanRecords = orphanRecordIds(root, { sharedDir: report.sharedDir });
79
124
  if (json) {
80
125
  console.log(JSON.stringify(report, null, 2));
81
126
  return;
82
127
  }
83
- console.log(`\n📦 Per-repository copies of shared plugins in ${report.root}\n`);
128
+ console.log(`\n📦 Plugin installs inside ${report.root} (all plugins belong in the shared folder)\n`);
129
+ const records = report.records?.removed || report.orphanRecords || [];
130
+ if (records.length) console.log(` ${report.records ? 'Removed' : 'Leftover'} install records for plugins installed nowhere: ${records.join(', ')}${report.records ? '' : ' (re-run with --apply)'}`);
131
+ if (report.records?.failed.length) console.error(` Could not remove records: ${report.records.failed.map(row => row.id).join(', ')}`);
84
132
  if (report.count === 0) {
85
133
  console.log(' None — this repository resolves the shared folder directly.\n');
86
134
  return;
87
135
  }
88
136
  for (const copy of report.copies) {
89
- const verdict = copy.linked ? 'linked checkout'
137
+ const verdict = copy.repoOnly ? `REPOSITORY-ONLY install${copy.target ? ` (link to ${copy.target})` : ''} — install it in the shared folder (plugin install <source>), or delete it with --apply --drop-repo-only`
138
+ : copy.shadow ? `SHADOW link to ${copy.target} — shadows the shared install`
139
+ : copy.linked ? 'linked checkout'
90
140
  : copy.stale === true ? 'STALE — older than the shared install and shadows it'
91
141
  : copy.edited === false ? 'identical to the bundled source'
92
142
  : copy.edited === true ? 'DIFFERS from the shared install' : 'no shared install to compare';
@@ -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
  }