total-recall-brain 3.41.0 → 3.41.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.41.0",
3
+ "version": "3.41.1",
4
4
  "description": "The Vibe Coding Brain & Autonomous Software Factory: memory, rules, secrets, and 33 visual plugins automatically managed across all your devices.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@ Total Recall connects to every major IDE, CLI harness, and local neural runtime:
23
23
  - **Lovable**: Repository root `AGENTS.md` and `CLAUDE.md`.
24
24
  - **DeepSeek Harness (`dsh`)**: `.dsh/memory/MEMORY.md` and `AGENTS.md`.
25
25
  - **OpenClaw**: `SOUL.md`, `AGENTS.md`, `USER.md`, and `MEMORY.md`.
26
- - **Hermes Agent**: `.hermes/memories/MEMORY.md`, `USER.md`, and `AGENTS.md`.
26
+ - **Hermes Agent**: `AGENTS.md` (loaded as a context file from the git root to cwd) and `.hermes/skills/`. `~/.hermes/memories/` is Hermes' own notes store and is never written.
27
27
  - **Cline**: `.clinerules/total-recall.md` and `.cline/skills/`.
28
28
  - **VS Code**: `.github/copilot-instructions.md` and `.vscode/copilot-instructions.md`.
29
29
 
package/src/cli/brain.mjs CHANGED
@@ -16,6 +16,10 @@ function printHelp() { console.log(`
16
16
  register <project-path> Track any project path (full brain + registry)
17
17
  unregister <project-path> Remove a project directory path from the registry
18
18
  ensure <project-path> Ensure a full project brain layout + register
19
+ import <vault-dir> --to <project-path> [--dry-run]
20
+ Import every memory node from another vault directory
21
+ (an archive or a retired brain) into that project's
22
+ brain through validated writes; existing slugs are kept
19
23
 
20
24
  Any repo works — pass absolute or relative paths you choose. No product defaults.
21
25
 
@@ -27,6 +31,7 @@ function printHelp() { console.log(`
27
31
  npx total-recall brain off hermes-agent
28
32
  npx total-recall brain on global
29
33
  npx total-recall brain unregister /path/to/project
34
+ npx total-recall brain import ~/Archive/old-brain/memory-vault --to ~/code/my-lib
30
35
 
31
36
  Skill multi-repo sync: registered projects + TR_SYNC_REPOS + skill track/--repo
32
37
  `);
@@ -58,6 +63,9 @@ export default async function brain(args) {
58
63
  case 'ensure':
59
64
  await handleEnsure(args.slice(1), globalBrainDir);
60
65
  break;
66
+ case 'import':
67
+ await handleImport(args.slice(1));
68
+ break;
61
69
  case 'on':
62
70
  case 'off':
63
71
  await handleToggle(subcommand.toLowerCase() === 'on', args.slice(1), globalBrainDir);
@@ -104,11 +112,14 @@ async function handleEnsure(targetArgs, globalBrainDir) {
104
112
  tags: ['project-brain', 'full-brain'],
105
113
  });
106
114
  const health = inspectProjectBrain(abs);
115
+ const { ensureProjectGitignore } = await import('./init.mjs');
116
+ const ignored = ensureProjectGitignore(abs);
107
117
  console.log(`\n ✅ Full project brain ready`);
108
118
  console.log(` Repo: ${result.repoRoot}`);
109
119
  console.log(` Brain: ${result.brainDir}`);
110
120
  console.log(` complete=${health.complete} full_brain=${health.full_brain}`);
111
121
  console.log(` directories created +${result.dirs_created}`);
122
+ if (ignored.length) console.log(` .gitignore +${ignored.length} brain-state exclusions`);
112
123
  console.log('');
113
124
  }
114
125
 
@@ -224,6 +235,11 @@ async function handleRegister(registryPath, targetArgs) {
224
235
  register: true,
225
236
  });
226
237
  console.log(` ✅ Full project brain ready at ${path.join(absoluteRoot, '.agent', 'skills', 'total-recall')}`);
238
+ // A registered repo carries a brain whose config holds a live PAT; keep it
239
+ // out of git the same way `init --project` does.
240
+ const { ensureProjectGitignore } = await import('./init.mjs');
241
+ const ignored = ensureProjectGitignore(absoluteRoot);
242
+ if (ignored.length) console.log(` ✅ .gitignore +${ignored.length} brain-state exclusions`);
227
243
  } catch (err) {
228
244
  console.error(` ⚠️ Could not fully provision brain layout: ${err.message}`);
229
245
  process.exitCode = 1;
@@ -300,6 +316,58 @@ async function handleUnregister(registryPath, targetArgs) {
300
316
  console.log(` ✅ Successfully unregistered project: "${absoluteRoot}"`);
301
317
  }
302
318
 
319
+ /**
320
+ * Import memory nodes from a vault directory into a project's brain.
321
+ *
322
+ * The source is read, never written: an archived brain, or the vault of a brain
323
+ * being retired. Every node goes through the validated SSSS write path at the
324
+ * same vault-relative path, so the target's event log records each one. A slug
325
+ * the target already holds is left alone, which makes a rerun safe.
326
+ */
327
+ async function handleImport(targetArgs) {
328
+ const toIdx = targetArgs.indexOf('--to');
329
+ const source = targetArgs.find((a, i) => !a.startsWith('-') && i !== toIdx + 1);
330
+ const target = toIdx !== -1 ? targetArgs[toIdx + 1] : null;
331
+ const dryRun = targetArgs.includes('--dry-run');
332
+ if (!source || !target) {
333
+ console.error(' ⚠️ Usage: npx total-recall brain import <vault-dir> --to <project-path> [--dry-run]');
334
+ process.exit(1);
335
+ }
336
+ const sourceDir = path.resolve(source);
337
+ const projectRoot = path.resolve(target);
338
+ if (!fs.existsSync(sourceDir) || !fs.statSync(sourceDir).isDirectory()) {
339
+ console.error(` ⚠️ Source vault not found: ${sourceDir}`);
340
+ process.exit(1);
341
+ }
342
+ const vaultDir = path.join(projectRoot, '.agent', 'skills', 'total-recall', 'memory-vault');
343
+ if (!fs.existsSync(vaultDir)) {
344
+ console.error(` ⚠️ No project brain at ${projectRoot}. Run: npx total-recall brain register ${projectRoot}`);
345
+ process.exit(1);
346
+ }
347
+ if (path.resolve(sourceDir) === path.resolve(vaultDir)) {
348
+ console.error(' ⚠️ Source and target are the same vault.');
349
+ process.exit(1);
350
+ }
351
+ const { loadNodes } = await import('../core/vault.mjs');
352
+ const { writeNodeValidatedAsync } = await import('../core/validated-write.mjs');
353
+ const existing = new Set(loadNodes(vaultDir).map(n => n.slug));
354
+ let imported = 0, skipped = 0;
355
+ const failed = [];
356
+ for (const node of loadNodes(sourceDir)) {
357
+ if (existing.has(node.slug)) { skipped++; continue; }
358
+ const rel = path.relative(sourceDir, node._filePath).split(path.sep).join('/');
359
+ if (dryRun) { imported++; continue; }
360
+ const { _filePath, _filepath, ...clean } = node;
361
+ const result = await writeNodeValidatedAsync(clean, vaultDir, { path: rel });
362
+ if (result.success) { imported++; existing.add(node.slug); }
363
+ else failed.push(`${rel}: ${result.validation?.errors?.join('; ') || result.error}`);
364
+ }
365
+ const verb = dryRun ? 'Would import' : 'Imported';
366
+ console.log(` ${failed.length ? '⚠️ ' : '✅'} ${verb} ${imported} node(s) into ${vaultDir}; ${skipped} already present`);
367
+ for (const line of failed) console.error(` ✗ ${line}`);
368
+ if (failed.length) process.exitCode = 1;
369
+ }
370
+
303
371
  function countMdFiles(dir) {
304
372
  if (!fs.existsSync(dir)) return 0;
305
373
  let count = 0;
@@ -25,16 +25,20 @@ export const CLIENTS = {
25
25
  },
26
26
  hermes: {
27
27
  label: 'Hermes Agent',
28
- mode: 'hermes',
29
- memoryPath: path.join(os.homedir(), '.hermes', 'memories', 'MEMORY.md'),
30
- userPath: path.join(os.homedir(), '.hermes', 'memories', 'USER.md'),
28
+ mode: 'symlink',
29
+ // Hermes loads the AGENTS.md chain (git root to cwd) as context files.
30
+ // ~/.hermes/memories/MEMORY.md is the agent's own notes store, read only
31
+ // from HERMES_HOME; Total Recall never writes it.
32
+ target: 'AGENTS.md',
31
33
  skillsProjection: { scope: 'project', dir: path.join('.hermes', 'skills') }
32
34
  },
33
35
  'hermes-agent': {
34
36
  label: 'Hermes Agent',
35
- mode: 'hermes',
36
- memoryPath: path.join(os.homedir(), '.hermes', 'memories', 'MEMORY.md'),
37
- userPath: path.join(os.homedir(), '.hermes', 'memories', 'USER.md'),
37
+ mode: 'symlink',
38
+ // Hermes loads the AGENTS.md chain (git root to cwd) as context files.
39
+ // ~/.hermes/memories/MEMORY.md is the agent's own notes store, read only
40
+ // from HERMES_HOME; Total Recall never writes it.
41
+ target: 'AGENTS.md',
38
42
  skillsProjection: { scope: 'project', dir: path.join('.hermes', 'skills') }
39
43
  },
40
44
  dsh: {
@@ -884,39 +888,6 @@ export default async function connect(args) {
884
888
  result.projection = { targetPath, action: 'symlinked' };
885
889
  }
886
890
  result.notes.push(` ~/.pi/agent/AGENTS.md → ${instructionsPath}`);
887
- } else if (preset.mode === 'hermes') {
888
- // Hermes: write compiled MEMORY.md to ~/.hermes/memories/MEMORY.md (max 2200 chars)
889
- // and project-local .hermes/memories/MEMORY.md if inside a project workspace
890
- const instructions = readInstructions(cwd, agentDir);
891
- if (!instructions) throw new Error('No INSTRUCTIONS.md found. Run npx total-recall init or compile first.');
892
- const homeDir = process.env.HOME || os.homedir();
893
- const globalMemPath = path.join(homeDir, '.hermes', 'memories', 'MEMORY.md');
894
- const capped = instructions.slice(0, 2200);
895
-
896
- let wroteGlobal = false;
897
- try {
898
- fs.mkdirSync(path.dirname(globalMemPath), { recursive: true });
899
- fs.writeFileSync(globalMemPath, capped, 'utf8');
900
- wroteGlobal = true;
901
- } catch {
902
- // Gracefully continue if global directory is unwritable in sandbox
903
- }
904
-
905
- let localMemPath = null;
906
- if (!isHome) {
907
- localMemPath = path.join(cwd, '.hermes', 'memories', 'MEMORY.md');
908
- fs.mkdirSync(path.dirname(localMemPath), { recursive: true });
909
- fs.writeFileSync(localMemPath, capped, 'utf8');
910
- }
911
-
912
- result.projection = { targetPath: localMemPath || globalMemPath, action: 'written' };
913
- if (wroteGlobal) {
914
- result.notes.push(` Hermes MEMORY.md (${capped.length} chars) → ${globalMemPath}`);
915
- }
916
- if (localMemPath) {
917
- result.notes.push(` Hermes local MEMORY.md → ${localMemPath}`);
918
- }
919
- result.notes.push(' Note: Hermes has a 2,200-char limit on MEMORY.md. Total Recall auto-caps this.');
920
891
  } else if (preset.mode === 'dsh') {
921
892
  // DeepSeek Harness: write global ~/.dsh/memory/MEMORY.md and symlink project AGENTS.md
922
893
  const instructions = readInstructions(cwd, agentDir);
@@ -567,8 +567,8 @@ const CLIENT_SHIMS = {
567
567
  vscode: ['.github/copilot-instructions.md', '.vscode/copilot-instructions.md'],
568
568
  githubCopilot: ['.github/copilot-instructions.md'],
569
569
  pi: ['AGENTS.md'],
570
- hermes: ['.hermes/memories/MEMORY.md'],
571
- 'hermes-agent': ['.hermes/memories/MEMORY.md'],
570
+ hermes: ['AGENTS.md'],
571
+ 'hermes-agent': ['AGENTS.md'],
572
572
  dsh: ['AGENTS.md'],
573
573
  'deepseek-harness': ['AGENTS.md'],
574
574
  openclaw: ['MEMORY.md', 'AGENTS.md']