total-recall-brain 3.1.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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/bin/antigravity.mjs +212 -0
  4. package/bin/init.mjs +36 -0
  5. package/bin/oci-sniper.sh +69 -0
  6. package/bin/total-recall.mjs +229 -0
  7. package/docs/ARCHITECTURE.md +251 -0
  8. package/docs/IDE_INTEGRATION.md +112 -0
  9. package/package.json +71 -0
  10. package/scaffold/.agent/skills/total-recall/SKILL.md +195 -0
  11. package/scaffold/.agent/skills/total-recall/evals/evals.json +20 -0
  12. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +145 -0
  13. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +75 -0
  14. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +16 -0
  15. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +17 -0
  16. package/scaffold/.agent/skills/total-recall/references/api-reference.md +187 -0
  17. package/scaffold/.agent/skills/total-recall/references/architecture-reference.md +81 -0
  18. package/scaffold/.agent/skills/total-recall/references/cli-reference.md +103 -0
  19. package/scaffold/.agent/skills/total-recall/references/ssss-reference.md +88 -0
  20. package/scaffold/.agent/skills/total-recall/references/troubleshooting.md +68 -0
  21. package/scaffold/.agent/skills/total-recall/scripts/sync-repo.mjs +83 -0
  22. package/scaffold/.agent/skills/total-recall/skills/cli-agents/SKILL.md +122 -0
  23. package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +27 -0
  24. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/antigravity.txt +20 -0
  25. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/claude.txt +58 -0
  26. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/codex.txt +81 -0
  27. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/gemini.txt +44 -0
  28. package/scaffold/.agent/skills/total-recall/skills/research/SKILL.md +337 -0
  29. package/scaffold/.agent/skills/total-recall/skills/skill/SKILL.md +281 -0
  30. package/scaffold/.agent/skills/total-recall/skills/skill/assets/skill-template/SKILL.md +20 -0
  31. package/scaffold/.agent/skills/total-recall/skills/skill/evals/evals.json +20 -0
  32. package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-add.sh +14 -0
  33. package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-delete.sh +14 -0
  34. package/scaffold/.agent/skills/total-recall/skills/skill/learnings/.gitkeep +0 -0
  35. package/scaffold/.agent/skills/total-recall/skills/skill/references/.gitkeep +0 -0
  36. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/.gitkeep +0 -0
  37. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/create-skill.sh +65 -0
  38. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/enforce-skill-optimization.mjs +135 -0
  39. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/find-skills.mjs +124 -0
  40. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/scan-skill.mjs +207 -0
  41. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/skill-watcher.mjs +64 -0
  42. package/scaffold/.agent/skills/total-recall/skills/skill/subagents/audit-all-skills.md +62 -0
  43. package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +27 -0
  44. package/scaffold/.agent/skills/total-recall/skills/ssss/evals/evals.json +17 -0
  45. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +117 -0
  46. package/scaffold/.agent/skills/total-recall/skills/ssss/scripts/validate-schema.mjs +54 -0
  47. package/scaffold/.agent/skills/total-recall/skills/ssss/subagents/audit-ssss-node.md +9 -0
  48. package/scaffold/.agent/skills/total-recall/subagents/total-recall-diagnostician.md +13 -0
  49. package/src/cli/agent-dir.mjs +105 -0
  50. package/src/cli/backup.mjs +313 -0
  51. package/src/cli/brain.mjs +224 -0
  52. package/src/cli/chat.mjs +74 -0
  53. package/src/cli/config.mjs +247 -0
  54. package/src/cli/connect.mjs +872 -0
  55. package/src/cli/daemon.mjs +228 -0
  56. package/src/cli/deploy-ui.mjs +1793 -0
  57. package/src/cli/deploy.mjs +1726 -0
  58. package/src/cli/doctor.mjs +254 -0
  59. package/src/cli/dream.mjs +80 -0
  60. package/src/cli/forget.mjs +92 -0
  61. package/src/cli/friction.mjs +21 -0
  62. package/src/cli/generate-pat.mjs +65 -0
  63. package/src/cli/hash-password.mjs +22 -0
  64. package/src/cli/help.mjs +153 -0
  65. package/src/cli/import-rules.mjs +102 -0
  66. package/src/cli/ingest/index.mjs +318 -0
  67. package/src/cli/ingest/parsers/chrome-bookmarks.mjs +238 -0
  68. package/src/cli/ingest/parsers/google-keep.mjs +147 -0
  69. package/src/cli/ingest/parsers/search-history.mjs +142 -0
  70. package/src/cli/ingest/parsers/youtube-history.mjs +181 -0
  71. package/src/cli/ingest/utils/dedup.mjs +69 -0
  72. package/src/cli/ingest/utils/takeout-walker.mjs +101 -0
  73. package/src/cli/ingest.mjs +102 -0
  74. package/src/cli/init.mjs +736 -0
  75. package/src/cli/integration-dispatcher.mjs +128 -0
  76. package/src/cli/lint.mjs +166 -0
  77. package/src/cli/map.mjs +194 -0
  78. package/src/cli/migrate.mjs +87 -0
  79. package/src/cli/rebuild.mjs +88 -0
  80. package/src/cli/recall.mjs +219 -0
  81. package/src/cli/relay.mjs +518 -0
  82. package/src/cli/remember.mjs +306 -0
  83. package/src/cli/research.mjs +381 -0
  84. package/src/cli/reset-password.mjs +75 -0
  85. package/src/cli/restore.mjs +135 -0
  86. package/src/cli/setup.mjs +616 -0
  87. package/src/cli/share.mjs +180 -0
  88. package/src/cli/skill.mjs +313 -0
  89. package/src/cli/snapshot.mjs +108 -0
  90. package/src/cli/start.mjs +63 -0
  91. package/src/cli/status.mjs +296 -0
  92. package/src/cli/sync.mjs +383 -0
  93. package/src/cli/uninstall.mjs +613 -0
  94. package/src/cli/upgrade.mjs +141 -0
  95. package/src/cli/wizard.html +3682 -0
  96. package/src/core/blackboard.mjs +54 -0
  97. package/src/core/clarity-rewriter.mjs +770 -0
  98. package/src/core/conclusion-writer.mjs +259 -0
  99. package/src/core/config.mjs +277 -0
  100. package/src/core/conflict-detector.mjs +536 -0
  101. package/src/core/crypto.mjs +107 -0
  102. package/src/core/daemon-control.mjs +158 -0
  103. package/src/core/daemon-loop.mjs +364 -0
  104. package/src/core/dream.mjs +347 -0
  105. package/src/core/drift-detector.mjs +143 -0
  106. package/src/core/embeddings.mjs +632 -0
  107. package/src/core/emergency-alerts.mjs +137 -0
  108. package/src/core/evolution.mjs +107 -0
  109. package/src/core/fact-seeker.mjs +1602 -0
  110. package/src/core/friction.mjs +95 -0
  111. package/src/core/import-rules.mjs +308 -0
  112. package/src/core/inference-engine.mjs +367 -0
  113. package/src/core/logger.mjs +75 -0
  114. package/src/core/memory-layers.mjs +96 -0
  115. package/src/core/migrate.mjs +116 -0
  116. package/src/core/notifications.mjs +77 -0
  117. package/src/core/operation-validator.mjs +272 -0
  118. package/src/core/optimizer.mjs +330 -0
  119. package/src/core/pattern_detector.mjs +113 -0
  120. package/src/core/post-mortem.mjs +413 -0
  121. package/src/core/protect-instructions.mjs +148 -0
  122. package/src/core/quick-capture.mjs +145 -0
  123. package/src/core/research-queue.mjs +431 -0
  124. package/src/core/research.mjs +461 -0
  125. package/src/core/runtime.mjs +515 -0
  126. package/src/core/sandbox.mjs +163 -0
  127. package/src/core/scheduler.mjs +569 -0
  128. package/src/core/schema.mjs +333 -0
  129. package/src/core/search.mjs +219 -0
  130. package/src/core/semantic-index.mjs +276 -0
  131. package/src/core/session-watcher.mjs +850 -0
  132. package/src/core/snapshot.mjs +133 -0
  133. package/src/core/source-adapters.mjs +1038 -0
  134. package/src/core/steering.mjs +137 -0
  135. package/src/core/surface.mjs +444 -0
  136. package/src/core/task_runner.mjs +125 -0
  137. package/src/core/tts.mjs +142 -0
  138. package/src/core/usage-tracker.mjs +349 -0
  139. package/src/core/vault-cache.mjs +76 -0
  140. package/src/core/vault.mjs +273 -0
  141. package/src/core/watchdog.mjs +236 -0
  142. package/src/server/api.mjs +944 -0
  143. package/src/server/auth.mjs +410 -0
  144. package/src/server/index.mjs +868 -0
  145. package/src/server/keys.mjs +181 -0
  146. package/src/server/rest.mjs +1652 -0
  147. package/src/server/route-manifest.json +77 -0
  148. package/src/server/routes/_shared.mjs +93 -0
  149. package/src/server/routes/keys.mjs +55 -0
  150. package/src/server/routes/memory.mjs +249 -0
  151. package/src/server/routes/sessions.mjs +336 -0
  152. package/src/server/routes/share.mjs +138 -0
  153. package/src/server/tools.mjs +618 -0
  154. package/templates/Caddyfile +37 -0
  155. package/templates/com.totalrecall.backup.plist +42 -0
  156. package/templates/com.totalrecall.daemon.plist +40 -0
  157. package/templates/com.totalrecall.duckdns.plist +23 -0
  158. package/templates/com.totalrecall.server.plist +46 -0
  159. package/templates/default-config/budget.yml +6 -0
  160. package/templates/default-config/runtime.yml +11 -0
  161. package/templates/default-config/security.yml +53 -0
  162. package/templates/default-config/voice.yml +34 -0
  163. package/templates/obsidian-queries/conflicts.md +26 -0
  164. package/templates/obsidian-queries/daily.md +17 -0
  165. package/templates/obsidian-queries/index.md +38 -0
  166. package/templates/obsidian-queries/skills.md +14 -0
  167. package/templates/onboarding-interview.md +72 -0
  168. package/templates/skill_engineering.md +40 -0
  169. package/templates/total-recall-daemon.service +21 -0
  170. package/templates/total-recall-server.service +20 -0
@@ -0,0 +1,128 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import matter from 'gray-matter';
4
+ import { agentDir } from '../core/config.mjs';
5
+ import { logger } from '../core/logger.mjs';
6
+
7
+ /**
8
+ * Dynamic SSSS-driven Integration Dispatcher.
9
+ * Exposes arbitrary third-party APIs as first-class CLI commands.
10
+ *
11
+ * @param {string} serviceName - e.g. "github", "slack", "linear"
12
+ * @param {string} actionName - e.g. "list_issues", "post_message"
13
+ * @param {object} cliArgs - Map of CLI arguments passed (e.g. { owner: "...", repo: "..." })
14
+ */
15
+ export default async function dispatchIntegrationCommand(serviceName, actionName, cliArgs) {
16
+ const resolvedAgentDir = process.env.AGENT_DIR || agentDir;
17
+ const localFilePath = path.join(resolvedAgentDir, 'skills', 'total-recall', 'integrations', `${serviceName}.md`);
18
+ if (!fs.existsSync(localFilePath)) {
19
+ throw new Error(`Integration "${serviceName}" is not configured in the VFS.`);
20
+ }
21
+
22
+ // 1. Read and parse SSSS YAML frontmatter
23
+ let parsed;
24
+ try {
25
+ parsed = matter(fs.readFileSync(localFilePath, 'utf8'));
26
+ } catch (err) {
27
+ throw new Error(`Failed to parse integration SSSS frontmatter: ${err.message}`);
28
+ }
29
+
30
+ const schema = parsed.data || {};
31
+ if (schema.type !== 'integration') {
32
+ throw new Error(`Invalid SSSS node type in "${serviceName}.md". Expected "integration", found "${schema.type}".`);
33
+ }
34
+
35
+ const endpoints = schema.endpoints || {};
36
+ const endpointTemplate = endpoints[actionName];
37
+ if (!endpointTemplate) {
38
+ throw new Error(`Action "${actionName}" not found for service "${serviceName}". Available actions: ${Object.keys(endpoints).join(', ')}`);
39
+ }
40
+
41
+ // 2. Resolve Method and Path template (e.g. "GET /repos/{owner}/{repo}/issues")
42
+ const parts = endpointTemplate.trim().split(/\s+/);
43
+ if (parts.length !== 2) {
44
+ throw new Error(`Malformed endpoint definition: "${endpointTemplate}". Expected format: "METHOD /path".`);
45
+ }
46
+ const [method, pathTemplate] = parts;
47
+
48
+ // 3. Bind Path variables (e.g. {owner} -> val)
49
+ let finalPath = pathTemplate;
50
+ const remainingArgs = { ...cliArgs };
51
+
52
+ const placeholders = pathTemplate.match(/\{([a-zA-Z0-9_-]+)\}/g) || [];
53
+ for (const placeholder of placeholders) {
54
+ const key = placeholder.slice(1, -1);
55
+ const value = cliArgs[key];
56
+ if (value === undefined) {
57
+ throw new Error(`Missing required path variable: --${key}`);
58
+ }
59
+ finalPath = finalPath.replace(placeholder, encodeURIComponent(String(value)));
60
+ delete remainingArgs[key]; // consumed by the path template
61
+ }
62
+
63
+ // 4. Resolve Auth Credential from Env
64
+ let token = '';
65
+ const envVars = schema.env_vars || [];
66
+ for (const envVar of envVars) {
67
+ if (process.env[envVar]) {
68
+ token = process.env[envVar];
69
+ break;
70
+ }
71
+ }
72
+
73
+ // 5. Populate and sign headers
74
+ const headers = {};
75
+ if (schema.headers) {
76
+ for (const [key, val] of Object.entries(schema.headers)) {
77
+ if (typeof val === 'string') {
78
+ // Substitute tokens dynamically
79
+ headers[key] = val
80
+ .replace('<GITHUB_TOKEN>', token)
81
+ .replace('<SLACK_BOT_TOKEN>', token)
82
+ .replace('<LINEAR_API_KEY>', token)
83
+ .replace('<API_KEY>', token);
84
+ } else {
85
+ headers[key] = String(val);
86
+ }
87
+ }
88
+ }
89
+
90
+ // 6. Build final URL and fetch configuration
91
+ const apiBaseUrl = (schema.api_base_url || '').replace(/\/$/, '');
92
+ const url = new URL(`${apiBaseUrl}${finalPath}`);
93
+ const fetchOpts = {
94
+ method,
95
+ headers,
96
+ signal: AbortSignal.timeout(30000)
97
+ };
98
+
99
+ if (method === 'GET' || method === 'DELETE') {
100
+ for (const [key, val] of Object.entries(remainingArgs)) {
101
+ if (val !== undefined && val !== null) {
102
+ url.searchParams.append(key, String(val));
103
+ }
104
+ }
105
+ } else {
106
+ // POST, PUT, PATCH
107
+ headers['Content-Type'] ??= 'application/json';
108
+ fetchOpts.body = JSON.stringify(remainingArgs);
109
+ }
110
+
111
+ logger.info({
112
+ subsystem: 'integration-dispatcher',
113
+ message: `Executing ${method} request to ${url.toString()}`
114
+ });
115
+
116
+ const res = await fetch(url.toString(), fetchOpts);
117
+ const responseText = await res.text();
118
+
119
+ if (!res.ok) {
120
+ throw new Error(`HTTP Error ${res.status} (${res.statusText}): ${responseText.slice(0, 1000)}`);
121
+ }
122
+
123
+ try {
124
+ return JSON.parse(responseText);
125
+ } catch {
126
+ return responseText;
127
+ }
128
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * total-recall lint
3
+ *
4
+ * Validate all vault nodes against SSSS schema v2.
5
+ * Reports invalid frontmatter, missing required fields, and type errors.
6
+ *
7
+ * Usage:
8
+ * npx total-recall lint [options]
9
+ *
10
+ * Options:
11
+ * --vault <path> Override vault directory (default: ~/.agent/memory-vault)
12
+ * --strict Treat warnings as errors (exit 1 on any issue)
13
+ * --json Output results as JSONL
14
+ * --help Show this help
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { resolveAgentDir, resolveBrainDir } from './agent-dir.mjs';
21
+
22
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
+ const AGENT_DIR = resolveAgentDir();
24
+ const BRAIN_DIR = resolveBrainDir();
25
+
26
+ function parseArgs(args) {
27
+ const opts = { vault: null, strict: false, json: false, help: false };
28
+ for (let i = 0; i < args.length; i++) {
29
+ switch (args[i]) {
30
+ case '--vault': opts.vault = args[++i]; break;
31
+ case '--strict': opts.strict = true; break;
32
+ case '--json': opts.json = true; break;
33
+ case '--help': case '-h': opts.help = true; break;
34
+ }
35
+ }
36
+ return opts;
37
+ }
38
+
39
+ function printHelp() {
40
+ console.log(`
41
+ total-recall lint โ€” Validate vault nodes against SSSS schema v2
42
+
43
+ Scans all .md files in the memory vault and validates their YAML
44
+ frontmatter against the Zod schemas defined in src/core/schema.mjs.
45
+
46
+ Usage: total-recall lint [options]
47
+
48
+ Options:
49
+ --vault <path> Override vault directory (default: ~/.agent/memory-vault)
50
+ --strict Treat warnings as errors (exit 1 on any issue)
51
+ --json Output results as JSONL
52
+ --help, -h Show this help
53
+
54
+ Exit codes:
55
+ 0 All nodes valid
56
+ 1 Validation errors found
57
+ `);
58
+ }
59
+
60
+ export default async function lint(args) {
61
+ const opts = parseArgs(args);
62
+ if (opts.help) { printHelp(); return; }
63
+
64
+ const vaultDir = opts.vault || path.join(BRAIN_DIR, 'memory-vault');
65
+
66
+ if (!opts.json) {
67
+ console.error(`\n ๐Ÿ” Linting vault: ${vaultDir}\n`);
68
+ }
69
+
70
+ let matter;
71
+ try {
72
+ matter = (await import('gray-matter')).default;
73
+ } catch {
74
+ console.error(' โŒ gray-matter not installed. Run: npm install gray-matter');
75
+ process.exit(1);
76
+ }
77
+
78
+ const { MemoryNodeSchema, TaskSchema, ConflictRecordSchema } = await import('../core/schema.mjs');
79
+ const { walkMd } = await import('../core/vault.mjs');
80
+
81
+ const files = walkMd(vaultDir);
82
+ let errors = 0;
83
+ let warnings = 0;
84
+ let valid = 0;
85
+
86
+ const schemaMap = {
87
+ memory: MemoryNodeSchema,
88
+ task: TaskSchema,
89
+ conflict: ConflictRecordSchema,
90
+ };
91
+
92
+ for (const file of files) {
93
+ const rel = path.relative(vaultDir, file);
94
+ try {
95
+ const raw = fs.readFileSync(file, 'utf8');
96
+ const { data } = matter(raw);
97
+
98
+ if (!data.type) {
99
+ if (opts.json) {
100
+ console.log(JSON.stringify({ file: rel, level: 'warning', message: 'Missing type field in frontmatter' }));
101
+ } else {
102
+ console.error(` โš ๏ธ ${rel}: Missing 'type' field in frontmatter`);
103
+ }
104
+ warnings++;
105
+ continue;
106
+ }
107
+
108
+ if (data.type === 'query') {
109
+ valid++;
110
+ continue;
111
+ }
112
+
113
+ const schema = schemaMap[data.type];
114
+ if (!schema) {
115
+ if (opts.json) {
116
+ console.log(JSON.stringify({ file: rel, level: 'warning', message: `Unknown type: ${data.type}` }));
117
+ } else {
118
+ console.error(` โš ๏ธ ${rel}: Unknown type '${data.type}' โ€” no schema to validate against`);
119
+ }
120
+ warnings++;
121
+ continue;
122
+ }
123
+
124
+ const result = schema.safeParse(data);
125
+ if (!result.success) {
126
+ const issues = result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`);
127
+ if (opts.json) {
128
+ console.log(JSON.stringify({ file: rel, level: 'error', type: data.type, issues }));
129
+ } else {
130
+ console.error(` โŒ ${rel} (${data.type}):`);
131
+ for (const issue of issues) {
132
+ console.error(` ${issue}`);
133
+ }
134
+ }
135
+ errors++;
136
+ } else {
137
+ valid++;
138
+ }
139
+ } catch (err) {
140
+ if (opts.json) {
141
+ console.log(JSON.stringify({ file: rel, level: 'error', message: `Parse error: ${err.message}` }));
142
+ } else {
143
+ console.error(` โŒ ${rel}: Parse error โ€” ${err.message}`);
144
+ }
145
+ errors++;
146
+ }
147
+ }
148
+
149
+ if (!opts.json) {
150
+ console.error(`\n โ”€โ”€ Results โ”€โ”€`);
151
+ console.error(` Files scanned: ${files.length}`);
152
+ console.error(` Valid: ${valid}`);
153
+ console.error(` Warnings: ${warnings}`);
154
+ console.error(` Errors: ${errors}`);
155
+
156
+ if (errors === 0 && (warnings === 0 || !opts.strict)) {
157
+ console.error(`\n โœ… All nodes valid\n`);
158
+ } else {
159
+ console.error(`\n โŒ Validation issues found\n`);
160
+ }
161
+ }
162
+
163
+ if (errors > 0 || (opts.strict && warnings > 0)) {
164
+ process.exit(1);
165
+ }
166
+ }
@@ -0,0 +1,194 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolveBrainDir, parseLayerFlag, getBothBrains } from './agent-dir.mjs';
4
+ import { loadNodes } from '../core/vault.mjs';
5
+
6
+ function printHelp() {
7
+ console.log(`
8
+ total-recall map โ€” Visualize memory vault structures and relationships
9
+
10
+ Usage: total-recall map [options]
11
+
12
+ Options:
13
+ --global Visualize global brain only
14
+ --project Visualize project brain only
15
+ --category, -cat <name> Visualize only nodes within a specific category
16
+ --relations, -r Visualize node-to-node relationships map
17
+
18
+ Examples:
19
+ npx total-recall map
20
+ npx total-recall map --project
21
+ npx total-recall map --category decisions
22
+ npx total-recall map --relations
23
+ `);
24
+ }
25
+
26
+ export default async function map(args) {
27
+ const { layer, remainingArgs } = parseLayerFlag(args);
28
+
29
+ let categoryFilter = null;
30
+ let showRelations = false;
31
+
32
+ for (let i = 0; i < remainingArgs.length; i++) {
33
+ const arg = remainingArgs[i];
34
+ const val = remainingArgs[i + 1];
35
+ if (arg === '--help' || arg === '-h') {
36
+ printHelp();
37
+ return;
38
+ } else if (arg === '--category' || arg === '-cat') {
39
+ if (val) {
40
+ categoryFilter = val.trim().toLowerCase();
41
+ i++;
42
+ }
43
+ } else if (arg === '--relations' || arg === '-r') {
44
+ showRelations = true;
45
+ }
46
+ }
47
+
48
+ const brains = getBothBrains();
49
+ const layersToProcess = [];
50
+
51
+ if ((layer === 'global' || layer === 'auto') && brains.global) {
52
+ layersToProcess.push({ name: 'global', path: brains.global.brainDir });
53
+ }
54
+ if ((layer === 'project' || layer === 'auto') && brains.project) {
55
+ layersToProcess.push({ name: 'project', path: brains.project.brainDir });
56
+ }
57
+
58
+ if (layersToProcess.length === 0) {
59
+ console.error(' โš ๏ธ No active brains resolved to visualize. Run total-recall init first.');
60
+ process.exit(1);
61
+ }
62
+
63
+ // Load nodes for each active layer
64
+ const loadedLayers = [];
65
+ let totalNodesCount = 0;
66
+
67
+ for (const l of layersToProcess) {
68
+ const vaultDir = path.join(l.path, 'memory-vault');
69
+ if (!fs.existsSync(vaultDir)) continue;
70
+
71
+ const nodes = loadNodes(vaultDir).map(n => ({ ...n, _layer: l.name }));
72
+ loadedLayers.push({ name: l.name, nodes });
73
+ totalNodesCount += nodes.length;
74
+ }
75
+
76
+ console.log(`\n ๐Ÿง  Total Recall Knowledge Map (Total: ${totalNodesCount} nodes)\n`);
77
+
78
+ if (showRelations) {
79
+ renderRelationsMap(loadedLayers);
80
+ } else {
81
+ renderCategoryTree(loadedLayers, categoryFilter);
82
+ }
83
+ }
84
+
85
+ function renderCategoryTree(loadedLayers, categoryFilter) {
86
+ const allCategories = [
87
+ 'invariants',
88
+ 'preferences',
89
+ 'anti-patterns',
90
+ 'patterns',
91
+ 'decisions',
92
+ 'concepts',
93
+ 'facts',
94
+ 'lore'
95
+ ];
96
+
97
+ for (const layer of loadedLayers) {
98
+ const layerEmoji = layer.name === 'global' ? '๐ŸŒ' : '๐Ÿ“';
99
+ const layerLabel = layer.name === 'global' ? 'Global Brain' : 'Project Brain';
100
+ console.log(` ${layerEmoji} ${layerLabel} (${layer.nodes.length} nodes)`);
101
+
102
+ // Group by category
103
+ const grouped = {};
104
+ for (const c of allCategories) grouped[c] = [];
105
+ for (const n of layer.nodes) {
106
+ const cat = (n.category || 'facts').toLowerCase();
107
+ if (!grouped[cat]) grouped[cat] = [];
108
+ grouped[cat].push(n);
109
+ }
110
+
111
+ const categoriesToRender = categoryFilter
112
+ ? allCategories.filter(c => c.includes(categoryFilter))
113
+ : allCategories;
114
+
115
+ const activeCategories = categoriesToRender.filter(cat => grouped[cat].length > 0);
116
+
117
+ if (activeCategories.length === 0) {
118
+ console.log(` โ””โ”€โ”€ (No nodes matching category filter)`);
119
+ console.log('');
120
+ continue;
121
+ }
122
+
123
+ for (let i = 0; i < activeCategories.length; i++) {
124
+ const cat = activeCategories[i];
125
+ const isLastCat = i === activeCategories.length - 1;
126
+ const catPrefix = isLastCat ? ' โ””โ”€โ”€' : ' โ”œโ”€โ”€';
127
+ const nodes = grouped[cat];
128
+
129
+ console.log(` ${catPrefix} ๐Ÿ“ ${cat} (${nodes.length})`);
130
+
131
+ for (let j = 0; j < nodes.length; j++) {
132
+ const node = nodes[j];
133
+ const isLastNode = j === nodes.length - 1;
134
+ const nodePrefix = isLastCat
135
+ ? (isLastNode ? ' โ””โ”€โ”€' : ' โ”œโ”€โ”€')
136
+ : (isLastNode ? ' โ”‚ โ””โ”€โ”€' : ' โ”‚ โ”œโ”€โ”€');
137
+
138
+ const importanceVal = parseInt(node.importance, 10) || 3;
139
+ const stars = 'โญ'.repeat(Math.max(1, Math.min(5, importanceVal)));
140
+ const modalityTag = node.modality ? ` [${node.modality.toUpperCase()}]` : '';
141
+
142
+ console.log(` ${nodePrefix} ๐Ÿ“„ [${node.slug}] โ€” ${node.title} (${stars}${modalityTag})`);
143
+ }
144
+ }
145
+ console.log('');
146
+ }
147
+ }
148
+
149
+ function renderRelationsMap(loadedLayers) {
150
+ // Build lookup map
151
+ const allNodes = {};
152
+ for (const layer of loadedLayers) {
153
+ for (const node of layer.nodes) {
154
+ allNodes[node.slug] = node;
155
+ }
156
+ }
157
+
158
+ console.log(' ๐Ÿ”— Semantic Node Relations Map:\n');
159
+ let hasRelations = false;
160
+
161
+ for (const layer of loadedLayers) {
162
+ const layerEmoji = layer.name === 'global' ? '๐ŸŒ' : '๐Ÿ“';
163
+ console.log(` ${layerEmoji} ${layer.name.toUpperCase()} LAYER RELATIONS:`);
164
+
165
+ let layerHasRelations = false;
166
+ for (const node of layer.nodes) {
167
+ const related = Array.isArray(node.related) ? node.related : [];
168
+ if (related.length === 0) continue;
169
+
170
+ layerHasRelations = true;
171
+ hasRelations = true;
172
+ console.log(` ๐Ÿ“„ [${node.slug}] "${node.title}"`);
173
+
174
+ for (let i = 0; i < related.length; i++) {
175
+ const targetSlug = related[i];
176
+ const isLast = i === related.length - 1;
177
+ const branch = isLast ? ' โ””โ”€โ”€' : ' โ”œโ”€โ”€';
178
+ const targetNode = allNodes[targetSlug];
179
+
180
+ if (targetNode) {
181
+ const targetLayer = targetNode._layer === node._layer ? '' : ` [${targetNode._layer}]`;
182
+ console.log(` ${branch} โ”€โ”€โ–บ [${targetSlug}] "${targetNode.title}"${targetLayer}`);
183
+ } else {
184
+ console.log(` ${branch} โ”€โ”€โ–บ [${targetSlug}] (unresolved reference)`);
185
+ }
186
+ }
187
+ console.log('');
188
+ }
189
+
190
+ if (!layerHasRelations) {
191
+ console.log(' (No outbound relations mapped)\n');
192
+ }
193
+ }
194
+ }
@@ -0,0 +1,87 @@
1
+ import path from 'path';
2
+ import { runMigration, testMigration } from '../core/migrate.mjs';
3
+ import { brainDir } from '../core/config.mjs';
4
+
5
+ const VAULT_DIR = path.join(brainDir, 'memory-vault');
6
+
7
+ export default async function migrateCommand(args) {
8
+ if (args[0] === '--help' || args[0] === '-h') {
9
+ console.log(`
10
+ total-recall migrate <test|apply> [options]
11
+
12
+ Run backwards-incompatible SSSS schema migrations.
13
+ Because migrations are destructive, 'apply' will automatically create a pre-migration safety snapshot.
14
+
15
+ Commands:
16
+ test <script.mjs> Run migration test harness (in-memory, no writes)
17
+ apply <script.mjs> Apply the migration over the active memory vault
18
+
19
+ Options:
20
+ --from <n> Starting schema version
21
+ --to <n> Target schema version
22
+ --desc "..." Description of the migration
23
+
24
+ Examples:
25
+ total-recall migrate test ./upgrade-v2-v3.mjs --to 3
26
+ total-recall migrate apply ./upgrade-v2-v3.mjs --from 2 --to 3 --desc "Add confidence decay to memories"
27
+ `);
28
+ process.exit(0);
29
+ }
30
+
31
+ const action = args[0];
32
+ const scriptPath = args[1];
33
+
34
+ if (!['test', 'apply'].includes(action)) {
35
+ console.error(`Invalid action. Use 'test' or 'apply'.`);
36
+ process.exit(1);
37
+ }
38
+
39
+ if (!scriptPath) {
40
+ console.error(`Please specify a migration script (e.g. ./upgrade.mjs).`);
41
+ process.exit(1);
42
+ }
43
+
44
+ // Parse args
45
+ let fromVersion = 0;
46
+ let toVersion = 0;
47
+ let desc = 'Schema migration';
48
+
49
+ for (let i = 2; i < args.length; i++) {
50
+ if (args[i] === '--from') fromVersion = parseInt(args[++i], 10);
51
+ else if (args[i] === '--to') toVersion = parseInt(args[++i], 10);
52
+ else if (args[i] === '--desc') desc = args[++i];
53
+ }
54
+
55
+ const absoluteScriptPath = path.resolve(process.cwd(), scriptPath);
56
+ let migrationModule;
57
+ try {
58
+ migrationModule = await import(absoluteScriptPath);
59
+ } catch (err) {
60
+ console.error(`Failed to load migration script at ${absoluteScriptPath}: ${err.message}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ const migrationFn = migrationModule.default || migrationModule.migrate;
65
+ if (typeof migrationFn !== 'function') {
66
+ console.error(`Migration script must export a default function or 'migrate' function.`);
67
+ process.exit(1);
68
+ }
69
+
70
+ if (action === 'test') {
71
+ const passed = await testMigration(VAULT_DIR, migrationFn, 'memory'); // Assuming 'memory' is the primary target
72
+ process.exit(passed ? 0 : 1);
73
+ }
74
+
75
+ if (action === 'apply') {
76
+ if (!fromVersion || !toVersion) {
77
+ console.error(`Both --from and --to versions are required for apply.`);
78
+ process.exit(1);
79
+ }
80
+
81
+ try {
82
+ await runMigration(VAULT_DIR, fromVersion, toVersion, migrationFn, desc);
83
+ } catch (err) {
84
+ process.exit(1);
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,88 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { compileSurface } from '../core/surface.mjs';
4
+ import { detectIndexDrift } from '../core/drift-detector.mjs';
5
+ import { resolveAgentDir, resolveBrainDir } from './agent-dir.mjs';
6
+ import { logger } from '../core/logger.mjs';
7
+
8
+ /**
9
+ * SSSS Projection Rebuild Command
10
+ *
11
+ * Implements the rebuild operation required by ยง10 of the SSSS spec.
12
+ * Discards derived indexes and rebuilds them deterministically from
13
+ * the canonical vault state.
14
+ */
15
+
16
+ export async function runRebuild(options = {}) {
17
+ const agentDir = resolveAgentDir();
18
+ const brainDir = resolveBrainDir();
19
+ const vaultDir = path.join(brainDir, 'memory-vault');
20
+ const skillsDir = path.join(agentDir, 'skills');
21
+ const derivedDir = path.join(brainDir, 'memory-derived');
22
+ const instructionsFile = path.join(agentDir, 'INSTRUCTIONS.md');
23
+
24
+
25
+
26
+ console.log('๐Ÿ”„ SSSS Projection Rebuild');
27
+ console.log('==========================');
28
+ console.log(`Vault: ${vaultDir}`);
29
+ console.log(`Derived: ${derivedDir}`);
30
+ console.log('--------------------------');
31
+
32
+ if (options.check) {
33
+ console.log('Detecting drift...');
34
+ const drift = detectIndexDrift(vaultDir, derivedDir);
35
+ if (!drift.drifted) {
36
+ console.log('โœ… Indexes are fully synchronized. No drift detected.');
37
+ return 0;
38
+ } else {
39
+ console.log('โš ๏ธ Drift detected between canonical vault and derived indexes:');
40
+ if (drift.missing_in_index.length > 0) {
41
+ console.log(` - ${drift.missing_in_index.length} nodes missing in index`);
42
+ }
43
+ if (drift.stale_in_index.length > 0) {
44
+ console.log(` - ${drift.stale_in_index.length} nodes out of sync in index`);
45
+ }
46
+ if (drift.missing_in_vault.length > 0) {
47
+ console.log(` - ${drift.missing_in_vault.length} ghost records in index (deleted from vault)`);
48
+ }
49
+ console.log('\nRun `total-recall rebuild` (without --check) to repair.');
50
+ return 1;
51
+ }
52
+ }
53
+
54
+ // Perform full rebuild
55
+ console.log('๐Ÿ—‘๏ธ Discarding existing derived indexes...');
56
+ if (fs.existsSync(derivedDir)) {
57
+ fs.rmSync(derivedDir, { recursive: true, force: true });
58
+ }
59
+
60
+ console.log('๐Ÿ—๏ธ Recompiling surface from canonical vault...');
61
+ try {
62
+ const stats = await compileSurface({ vaultDir, skillsDir, derivedDir, instructionsFile });
63
+ console.log(`โœ… Processed ${stats.nodesProcessed} canonical memory nodes.`);
64
+ console.log(`โœ… Injected memory into ${stats.skillsInjected} skill files.`);
65
+ console.log(`โœ… Rebuilt graph-index.jsonl, memory-layers.jsonl, and skill-routes.jsonl.`);
66
+
67
+ // Verify
68
+ const drift = detectIndexDrift(vaultDir, derivedDir);
69
+ if (!drift.drifted) {
70
+ console.log('โœ… Post-build verification passed: 0 drift.');
71
+ logger.info('rebuild', `Rebuild completed successfully. Processed ${stats.nodesProcessed} nodes.`);
72
+ return 0;
73
+ } else {
74
+ console.error('โŒ Post-build verification failed! Drift remains.');
75
+ return 1;
76
+ }
77
+ } catch (err) {
78
+ console.error(`โŒ Rebuild failed: ${err.message}`);
79
+ logger.error('rebuild', `Rebuild failed: ${err.message}`);
80
+ return 1;
81
+ }
82
+ }
83
+
84
+ export default async function cli(args) {
85
+ const options = { check: args.includes('--check') };
86
+ const code = await runRebuild(options);
87
+ process.exit(code);
88
+ }