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.
|
|
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
|
|
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;
|
package/src/cli/connect.mjs
CHANGED
|
@@ -25,16 +25,20 @@ export const CLIENTS = {
|
|
|
25
25
|
},
|
|
26
26
|
hermes: {
|
|
27
27
|
label: 'Hermes Agent',
|
|
28
|
-
mode: '
|
|
29
|
-
|
|
30
|
-
|
|
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: '
|
|
36
|
-
|
|
37
|
-
|
|
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);
|
package/src/core/surface.mjs
CHANGED
|
@@ -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: ['.
|
|
571
|
-
'hermes-agent': ['.
|
|
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']
|