total-recall-brain 3.24.1 → 3.24.2

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.24.1",
3
+ "version": "3.24.2",
4
4
  "description": "Personal AI system — database-free, SSSS-powered intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -597,6 +597,19 @@ Current baseline: **288 spec files / 1493 tests, ~160 s** on the Mac Mini.
597
597
  >
598
598
  > A non-login `ssh` shell has no Homebrew on PATH — export it or use `bash -lc`,
599
599
  > otherwise the run dies with `command not found: npx` **and still exits 0**.
600
+ >
601
+ > **Never read a test result through a pipe.** `vitest run` exits 1 correctly on
602
+ > failure, but `npx vitest run | tail -20` reports *tail's* status — always 0 —
603
+ > so a red suite looks green. On 2026-08-19 this cost a full re-run and a wrong
604
+ > "the suite exits 0 on failure" claim about the repo; the defect was the
605
+ > pipeline, not vitest. Capture to a file and check the code separately:
606
+ >
607
+ > ```bash
608
+ > npx vitest run > /tmp/t.log 2>&1; echo "exit=$?"; tail -20 /tmp/t.log
609
+ > ```
610
+ >
611
+ > `check.mjs` is unaffected — it spawns without a shell and reads the real exit
612
+ > code — so prefer the gate over hand-rolled invocations.
600
613
 
601
614
  **Never skip tests** to make a release look clean, and never use `--force` on a
602
615
  checker. Audit and clean up any rows, files, or mock entries a suite leaves
@@ -3,6 +3,7 @@ type: memory
3
3
  slug: always-websearch-gap
4
4
  category: preferences
5
5
  title: "Always Perform Web Search for Cutoff Gap"
6
+ description: "Web-search to confirm any fact that falls in or past the training-cutoff gap, rather than answering from weights."
6
7
  status: active
7
8
  confidence: 1
8
9
  importance: 5
@@ -32,6 +33,7 @@ schema_version: 2
32
33
  x_temporal_context: "2026-05-25T22:36:21.376Z"
33
34
  priority: absolute
34
35
  immutable: true
36
+ timestamp: "2026-05-25T22:36:21.376Z"
35
37
  ---
36
38
 
37
39
  # Always Perform Web Search for Cutoff Gap
@@ -3,6 +3,7 @@ type: memory
3
3
  slug: topic-research-sop
4
4
  category: preferences
5
5
  title: "Standard Operating Procedure for Topic Research"
6
+ description: "How the agent handles a research request: queue it to the background daemon, keep one living scratch document, and compile so it is searchable."
6
7
  status: active
7
8
  confidence: 1
8
9
  importance: 5
@@ -32,6 +33,7 @@ schema_version: 2
32
33
  x_temporal_context: "2026-05-25T22:36:21.376Z"
33
34
  priority: absolute
34
35
  immutable: true
36
+ timestamp: "2026-05-25T22:36:21.376Z"
35
37
  ---
36
38
 
37
39
  # Standard Operating Procedure for Topic Research
@@ -1,83 +1,188 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sync-repo — refresh this brain's skill definitions from the upstream templates.
4
+ *
5
+ * Replaces a stub that printed "Synced and verified skill: <name>", "Merging core
6
+ * invariants non-destructively" and "Sync Completed Successfully!" without
7
+ * fetching, merging or writing anything — while the skill's Core Directive #4
8
+ * told agents to run it to keep skills current. It also probed
9
+ * <agent>/memory-vault, which is not where a brain lives, so on a correct
10
+ * install it aborted with a misleading "run init first".
11
+ *
12
+ * What it actually does now:
13
+ * - resolves a real template source (env override, installed package,
14
+ * running-from-source checkout, or a shallow clone of upstream)
15
+ * - copies skill files, PRESERVING each destination's compiled
16
+ * "<!-- BEGIN INJECTED MEMORY -->" block, which is per-repo and must not be
17
+ * overwritten by a template
18
+ * - never touches memory-vault/ — that is user memory, not a template
19
+ * - reports the files it really changed, and exits non-zero if it changed nothing
20
+ * because it could not find a source
21
+ *
22
+ * Usage: node sync-repo.mjs [--dry-run] [--source <dir>]
23
+ */
1
24
  import fs from 'node:fs';
2
25
  import path from 'node:path';
3
26
  import os from 'node:os';
27
+ import { execFileSync } from 'node:child_process';
4
28
  import { fileURLToPath } from 'node:url';
5
29
 
6
- // Resolve agent paths
30
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
31
+ const DRY = process.argv.includes('--dry-run');
32
+ const srcFlag = process.argv.indexOf('--source');
33
+ const SRC_OVERRIDE = srcFlag !== -1 ? process.argv[srcFlag + 1] : process.env.TR_SCAFFOLD_DIR;
34
+
7
35
  const home = os.homedir();
8
36
  const AGENT_DIR = process.env.AGENT_DIR || path.join(home, '.agent');
9
- const VAULT_DIR = path.join(AGENT_DIR, 'memory-vault');
37
+ // A brain is <agent>/skills/total-recall — memory-vault, config and the rest all
38
+ // live INSIDE it. The old code looked for <agent>/memory-vault and never found one.
39
+ const BRAIN_DIR = path.join(AGENT_DIR, 'skills', 'total-recall');
10
40
  const SKILLS_DIR = path.join(AGENT_DIR, 'skills');
11
41
 
12
- console.log('🔄 Starting Total Recall Upstream Skill Synchronization...');
13
- console.log(`📂 Local Agent Directory: ${AGENT_DIR}`);
42
+ const INJECT_START = '<!-- BEGIN INJECTED MEMORY';
43
+ const INJECT_END = '<!-- END INJECTED MEMORY -->';
44
+
45
+ function log(m) { console.log(m); }
46
+ function fail(m) { console.error(`❌ ${m}`); process.exit(1); }
47
+
48
+ /** Locate real template files. Returns { dir, origin } or null. */
49
+ function resolveTemplateSource() {
50
+ if (SRC_OVERRIDE) {
51
+ if (!fs.existsSync(SRC_OVERRIDE)) fail(`--source path does not exist: ${SRC_OVERRIDE}`);
52
+ return { dir: SRC_OVERRIDE, origin: `explicit: ${SRC_OVERRIDE}` };
53
+ }
54
+ // Walk up looking for the package root rather than counting '..' segments:
55
+ // this script legitimately runs from two different depths (a brain at
56
+ // <agent>/skills/total-recall/scripts, and the template copy at
57
+ // <repo>/scaffold/.agent/skills/total-recall/scripts). A fixed relative path
58
+ // is right for one and silently wrong for the other — which resolved to the
59
+ // INSTALLED (older) package and would have reverted newer local templates.
60
+ const fromCheckout = (() => {
61
+ let dir = __dirname;
62
+ for (let i = 0; i < 8; i += 1) {
63
+ const pkg = path.join(dir, 'package.json');
64
+ const scaffold = path.join(dir, 'scaffold', '.agent', 'skills');
65
+ if (fs.existsSync(pkg) && fs.existsSync(scaffold)) {
66
+ try {
67
+ if (JSON.parse(fs.readFileSync(pkg, 'utf8')).name === 'total-recall-brain') return scaffold;
68
+ } catch { /* keep walking */ }
69
+ }
70
+ const up = path.dirname(dir);
71
+ if (up === dir) break;
72
+ dir = up;
73
+ }
74
+ return null;
75
+ })();
14
76
 
15
- // 1. Resolve upstream config
16
- let upstreamUrl = 'https://github.com/gregiteen/total-recall';
17
- try {
18
- const brainConfigPath = path.join(AGENT_DIR, 'config', 'brain.json');
19
- if (fs.existsSync(brainConfigPath)) {
20
- const config = JSON.parse(fs.readFileSync(brainConfigPath, 'utf8'));
21
- if (config.upstream) upstreamUrl = config.upstream;
77
+ const candidates = [
78
+ [fromCheckout, 'source checkout'],
79
+ [path.join(process.cwd(), 'node_modules', 'total-recall-brain', 'scaffold', '.agent', 'skills'), 'installed package'],
80
+ [path.join(home, 'node_modules', 'total-recall-brain', 'scaffold', '.agent', 'skills'), 'installed package (home)'],
81
+ ];
82
+ for (const [dir, origin] of candidates) {
83
+ if (dir && fs.existsSync(dir) && fs.statSync(dir).isDirectory()) return { dir, origin };
22
84
  }
23
- } catch {
24
- // Use default
85
+ return null;
25
86
  }
26
- console.log(`🌐 Upstream Repository URL: ${upstreamUrl}`);
27
87
 
28
- // 2. Perform synchronization simulation / stub logic with absolute robustness
29
- async function runSync() {
88
+ /** Last resort: shallow-clone upstream into a temp dir. */
89
+ function cloneUpstream() {
90
+ let upstream = 'https://github.com/gregiteen/total-recall';
30
91
  try {
31
- // Check if network is available / validate environment
32
- console.log('🔎 Auditing local skill installations...');
33
-
34
- if (!fs.existsSync(VAULT_DIR) || !fs.existsSync(SKILLS_DIR)) {
35
- throw new Error('Total Recall directories are missing. Run "npx total-recall init" first.');
92
+ const cfg = path.join(BRAIN_DIR, 'config', 'brain.json');
93
+ if (fs.existsSync(cfg)) {
94
+ const parsed = JSON.parse(fs.readFileSync(cfg, 'utf8'));
95
+ if (parsed.upstream) upstream = parsed.upstream;
36
96
  }
97
+ } catch { /* default */ }
98
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tr-sync-'));
99
+ log(`🌐 No local templates — shallow-cloning ${upstream}`);
100
+ try {
101
+ execFileSync('git', ['clone', '--depth', '1', '--quiet', upstream, tmp], { stdio: 'pipe' });
102
+ } catch (err) {
103
+ fs.rmSync(tmp, { recursive: true, force: true });
104
+ fail(`could not clone upstream (${err.message.split('\n')[0]}). Pass --source <dir> to sync offline.`);
105
+ }
106
+ const dir = path.join(tmp, 'scaffold', '.agent', 'skills');
107
+ if (!fs.existsSync(dir)) {
108
+ fs.rmSync(tmp, { recursive: true, force: true });
109
+ fail('upstream clone has no scaffold/.agent/skills — cannot sync.');
110
+ }
111
+ return { dir, origin: `clone of ${upstream}`, cleanup: tmp };
112
+ }
37
113
 
38
- const localSkills = fs.readdirSync(SKILLS_DIR);
39
- console.log(`✅ Found ${localSkills.length} local skill packages: ${localSkills.join(', ')}`);
40
-
41
- // In a real implementation, this would git fetch or fetch zip bundle from upstreamUrl.
42
- // For local resilience, we perform a non-destructive merge of scaffolded presets.
43
- console.log('📥 Fetching upstream skill templates...');
44
-
45
- // Simulate fetching core templates (e.g. ssss, project-management, total-recall itself)
46
- const templates = ['ssss', 'project-management', 'total-recall'];
47
- let updatedCount = 0;
48
-
49
- for (const t of templates) {
50
- console.log(`📦 Synced and verified skill: ${t}`);
51
- updatedCount++;
114
+ /** Copy src over dst, carrying dst's compiled injected-memory block across. */
115
+ function writePreservingInjectedBlock(srcFile, dstFile) {
116
+ let next = fs.readFileSync(srcFile, 'utf8');
117
+ if (fs.existsSync(dstFile)) {
118
+ const cur = fs.readFileSync(dstFile, 'utf8');
119
+ const s = cur.indexOf(INJECT_START);
120
+ const e = cur.indexOf(INJECT_END);
121
+ if (s !== -1 && e !== -1 && e > s) {
122
+ const block = cur.slice(s, e + INJECT_END.length);
123
+ const ns = next.indexOf(INJECT_START);
124
+ const ne = next.indexOf(INJECT_END);
125
+ next = (ns !== -1 && ne !== -1 && ne > ns)
126
+ ? next.slice(0, ns) + block + next.slice(ne + INJECT_END.length)
127
+ : `${next.replace(/\s*$/, '')}\n\n${block}\n`;
52
128
  }
129
+ if (cur === next) return 'identical';
130
+ }
131
+ if (!DRY) {
132
+ fs.mkdirSync(path.dirname(dstFile), { recursive: true });
133
+ fs.writeFileSync(dstFile, next);
134
+ }
135
+ return 'updated';
136
+ }
53
137
 
54
- console.log('📝 Merging core invariants non-destructively...');
55
- console.log('🛡️ Preserving custom user-authored memory nodes inside memory-vault/');
56
-
57
- // Trigger local surface compile to apply all changes
58
- console.log('⚙️ Re-compiling system memory surfaces...');
59
- try {
60
- const { compileSurface } = await import('../../../src/core/surface.mjs');
61
- const instructionsFile = path.join(process.cwd(), 'INSTRUCTIONS.md');
62
- const derivedDir = path.join(AGENT_DIR, 'memory-derived');
63
-
64
- const result = await compileSurface({
65
- vaultDir: VAULT_DIR,
66
- skillsDir: SKILLS_DIR,
67
- derivedDir,
68
- instructionsFile
69
- });
70
-
71
- console.log(`🚀 Compilation Complete: Processed ${result.nodesProcessed} nodes, injected ${result.skillsInjected} skills.`);
72
- } catch (err) {
73
- console.warn(`⚠️ Compilation skipped or ran into error: ${err.message}`);
74
- }
138
+ function walk(dir) {
139
+ const out = [];
140
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
141
+ const p = path.join(dir, e.name);
142
+ if (e.isDirectory()) {
143
+ // memory-vault is the user's own memory. A template must never overwrite it.
144
+ if (e.name === 'memory-vault' || e.name === 'memory-derived' || e.name === 'sessions') continue;
145
+ if (e.name === '.git' || e.name === 'node_modules') continue;
146
+ out.push(...walk(p));
147
+ } else out.push(p);
148
+ }
149
+ return out;
150
+ }
75
151
 
76
- console.log('🎉 Total Recall Skill Sync Completed Successfully!');
77
- } catch (error) {
78
- console.error(`❌ Sync Failed: ${error.message}`);
79
- process.exit(1);
152
+ log('🔄 Total Recall — syncing skill definitions from upstream templates');
153
+ log(`📂 Brain: ${BRAIN_DIR}`);
154
+ if (DRY) log('🔍 DRY RUN — nothing will be written');
155
+
156
+ if (!fs.existsSync(BRAIN_DIR)) fail(`no brain at ${BRAIN_DIR}. Run "npx total-recall init" first.`);
157
+
158
+ let source = resolveTemplateSource();
159
+ if (!source) source = cloneUpstream();
160
+ log(`📦 Template source: ${source.origin}`);
161
+
162
+ const skills = fs.readdirSync(source.dir, { withFileTypes: true })
163
+ .filter((e) => e.isDirectory()).map((e) => e.name);
164
+ if (skills.length === 0) fail(`template source has no skills: ${source.dir}`);
165
+
166
+ let updated = 0, identical = 0, created = 0;
167
+ const touched = [];
168
+ for (const skill of skills) {
169
+ const from = path.join(source.dir, skill);
170
+ const to = path.join(SKILLS_DIR, skill);
171
+ const isNew = !fs.existsSync(to);
172
+ for (const f of walk(from)) {
173
+ const rel = path.relative(from, f);
174
+ const res = writePreservingInjectedBlock(f, path.join(to, rel));
175
+ if (res === 'updated') { updated++; touched.push(`${skill}/${rel}`); } else identical++;
80
176
  }
177
+ if (isNew) created++;
81
178
  }
82
179
 
83
- runSync();
180
+ if (source.cleanup) fs.rmSync(source.cleanup, { recursive: true, force: true });
181
+
182
+ log('');
183
+ log(`${DRY ? '🔍' : '✅'} ${updated} file(s) ${DRY ? 'would change' : 'updated'}, ${identical} already current, ${created} new skill(s)`);
184
+ for (const t of touched.slice(0, 20)) log(` ${DRY ? '~' : '↳'} ${t}`);
185
+ if (touched.length > 20) log(` … and ${touched.length - 20} more`);
186
+ if (updated === 0) log(' Nothing to do — every skill file already matches the templates.');
187
+ log('');
188
+ log(DRY ? 'Re-run without --dry-run to apply.' : 'Run "npx total-recall compile" to rebuild the instruction surfaces.');
@@ -116,6 +116,33 @@ function printHelp() {
116
116
  `);
117
117
  }
118
118
 
119
+ function ensureBrainGitignore(SKILL_DIR) {
120
+ const gitignorePath = path.join(SKILL_DIR, '.gitignore');
121
+ const entries = [
122
+ 'memory-derived/', 'sessions/', '*.backup', 'logs/', 'node_modules/',
123
+ 'memory-vault/.events/', 'browser-profile/', '.snapshots/', 'daemon.pid',
124
+ ];
125
+ let existing = '';
126
+ if (fs.existsSync(gitignorePath)) existing = fs.readFileSync(gitignorePath, 'utf8');
127
+ const missing = entries.filter((e) => !existing.split('\n').some((l) => l.trim() === e));
128
+ if (missing.length === 0) return 0;
129
+ fs.appendFileSync(
130
+ gitignorePath,
131
+ '\n# Auto-added by total-recall backup — runtime/derived/sensitive, never versioned\n'
132
+ + missing.join('\n') + '\n',
133
+ );
134
+ console.error(` \u2705 .gitignore: added ${missing.length} protected entr${missing.length === 1 ? 'y' : 'ies'}`);
135
+ // Anything already tracked must also be untracked, or the ignore is inert.
136
+ for (const e of missing) {
137
+ const tracked = git(SKILL_DIR, 'ls-files', e.replace(/\/$/, ''));
138
+ if (tracked.ok && tracked.stdout) {
139
+ git(SKILL_DIR, 'rm', '-r', '--cached', '-f', '-q', e.replace(/\/$/, ''));
140
+ console.error(` \u2705 untracked ${e} (kept on disk)`);
141
+ }
142
+ }
143
+ return missing.length;
144
+ }
145
+
119
146
  async function pushGitBackup(remote, layer = 'auto') {
120
147
  const SKILL_DIR = getSkillDir(layer);
121
148
  if (!commandExists('git')) {
@@ -129,6 +156,19 @@ async function pushGitBackup(remote, layer = 'auto') {
129
156
  process.exit(1);
130
157
  }
131
158
 
159
+ // A brain is memory, not runtime state. Three classes of file have each
160
+ // broken a real backup by being versioned:
161
+ // memory-vault/.events/ append-only logs; default.jsonl reached 306MB and
162
+ // GitHub hard-rejects >100MB, silently killing every
163
+ // global backup for 5 weeks (2026-07-14 -> 08-19).
164
+ // browser-profile/ the rotation Chromium profile — live Stripe/GitHub
165
+ // cookies and Login Data. Was tracked and one
166
+ // successful push from landing on GitHub.
167
+ // .snapshots/ ~46MB vault tarballs stored inside the vault.
168
+ // Enforced on every push, not just --install, so brains created before this
169
+ // rule are corrected on their next backup.
170
+ ensureBrainGitignore(SKILL_DIR);
171
+
132
172
  // Initialise the skill folder as a git repo if needed
133
173
  const gitDir = path.join(SKILL_DIR, '.git');
134
174
  if (!fs.existsSync(gitDir)) {
@@ -170,6 +210,29 @@ async function pushGitBackup(remote, layer = 'auto') {
170
210
  const stamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
171
211
 
172
212
  if (!status.stdout) {
213
+ // A clean working tree does NOT mean the remote is current. Two scheduled
214
+ // jobs pointing the shared "backup" remote at different URLs is enough to
215
+ // break this: whichever ran second re-pointed the remote, found the tree
216
+ // clean, and returned "nothing to push" — so that destination silently
217
+ // froze for months while every run reported success. Only skip when the
218
+ // remote actually has our HEAD.
219
+ const head = git(SKILL_DIR, 'rev-parse', 'HEAD');
220
+ const localSha = head.ok ? (head.stdout || '').trim() : '';
221
+ const remoteHead = git(SKILL_DIR, 'ls-remote', remote, 'main');
222
+ const remoteSha = remoteHead.ok ? (remoteHead.stdout || '').split(/\s+/)[0] : '';
223
+ // No local commit to compare, or the remote already has it: genuinely
224
+ // nothing to do. Only a KNOWN mismatch justifies pushing a clean tree.
225
+ if (!localSha || remoteSha === localSha) {
226
+ console.error(` ✅ Brain unchanged — nothing to push (${stamp})`);
227
+ return;
228
+ }
229
+ if (remoteSha) {
230
+ console.error(` 📤 Working tree clean but ${remote} is behind — pushing existing commits...`);
231
+ const p = git(SKILL_DIR, 'push', remote, 'HEAD:main');
232
+ if (p.ok) console.error(` ✅ Brain pushed to backup (${stamp})`);
233
+ else console.error(` ❌ Push failed: ${p.stderr || p.stdout}`);
234
+ return;
235
+ }
173
236
  console.error(` ✅ Brain unchanged — nothing to push (${stamp})`);
174
237
  return;
175
238
  }
@@ -103,6 +103,10 @@
103
103
  "method": "POST",
104
104
  "path": "/api/context/stream"
105
105
  },
106
+ {
107
+ "method": "POST",
108
+ "path": "/api/daemon/restart"
109
+ },
106
110
  {
107
111
  "method": "GET",
108
112
  "path": "/api/dashboard/instructions"