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.
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/bin/antigravity.mjs +212 -0
- package/bin/init.mjs +36 -0
- package/bin/oci-sniper.sh +69 -0
- package/bin/total-recall.mjs +229 -0
- package/docs/ARCHITECTURE.md +251 -0
- package/docs/IDE_INTEGRATION.md +112 -0
- package/package.json +71 -0
- package/scaffold/.agent/skills/total-recall/SKILL.md +195 -0
- package/scaffold/.agent/skills/total-recall/evals/evals.json +20 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +145 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +75 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +16 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +17 -0
- package/scaffold/.agent/skills/total-recall/references/api-reference.md +187 -0
- package/scaffold/.agent/skills/total-recall/references/architecture-reference.md +81 -0
- package/scaffold/.agent/skills/total-recall/references/cli-reference.md +103 -0
- package/scaffold/.agent/skills/total-recall/references/ssss-reference.md +88 -0
- package/scaffold/.agent/skills/total-recall/references/troubleshooting.md +68 -0
- package/scaffold/.agent/skills/total-recall/scripts/sync-repo.mjs +83 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/SKILL.md +122 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +27 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/antigravity.txt +20 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/claude.txt +58 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/codex.txt +81 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/gemini.txt +44 -0
- package/scaffold/.agent/skills/total-recall/skills/research/SKILL.md +337 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/SKILL.md +281 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/assets/skill-template/SKILL.md +20 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/evals/evals.json +20 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-add.sh +14 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-delete.sh +14 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/create-skill.sh +65 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/enforce-skill-optimization.mjs +135 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/find-skills.mjs +124 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/scan-skill.mjs +207 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/skill-watcher.mjs +64 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/subagents/audit-all-skills.md +62 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +27 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/evals/evals.json +17 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +117 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/scripts/validate-schema.mjs +54 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/subagents/audit-ssss-node.md +9 -0
- package/scaffold/.agent/skills/total-recall/subagents/total-recall-diagnostician.md +13 -0
- package/src/cli/agent-dir.mjs +105 -0
- package/src/cli/backup.mjs +313 -0
- package/src/cli/brain.mjs +224 -0
- package/src/cli/chat.mjs +74 -0
- package/src/cli/config.mjs +247 -0
- package/src/cli/connect.mjs +872 -0
- package/src/cli/daemon.mjs +228 -0
- package/src/cli/deploy-ui.mjs +1793 -0
- package/src/cli/deploy.mjs +1726 -0
- package/src/cli/doctor.mjs +254 -0
- package/src/cli/dream.mjs +80 -0
- package/src/cli/forget.mjs +92 -0
- package/src/cli/friction.mjs +21 -0
- package/src/cli/generate-pat.mjs +65 -0
- package/src/cli/hash-password.mjs +22 -0
- package/src/cli/help.mjs +153 -0
- package/src/cli/import-rules.mjs +102 -0
- package/src/cli/ingest/index.mjs +318 -0
- package/src/cli/ingest/parsers/chrome-bookmarks.mjs +238 -0
- package/src/cli/ingest/parsers/google-keep.mjs +147 -0
- package/src/cli/ingest/parsers/search-history.mjs +142 -0
- package/src/cli/ingest/parsers/youtube-history.mjs +181 -0
- package/src/cli/ingest/utils/dedup.mjs +69 -0
- package/src/cli/ingest/utils/takeout-walker.mjs +101 -0
- package/src/cli/ingest.mjs +102 -0
- package/src/cli/init.mjs +736 -0
- package/src/cli/integration-dispatcher.mjs +128 -0
- package/src/cli/lint.mjs +166 -0
- package/src/cli/map.mjs +194 -0
- package/src/cli/migrate.mjs +87 -0
- package/src/cli/rebuild.mjs +88 -0
- package/src/cli/recall.mjs +219 -0
- package/src/cli/relay.mjs +518 -0
- package/src/cli/remember.mjs +306 -0
- package/src/cli/research.mjs +381 -0
- package/src/cli/reset-password.mjs +75 -0
- package/src/cli/restore.mjs +135 -0
- package/src/cli/setup.mjs +616 -0
- package/src/cli/share.mjs +180 -0
- package/src/cli/skill.mjs +313 -0
- package/src/cli/snapshot.mjs +108 -0
- package/src/cli/start.mjs +63 -0
- package/src/cli/status.mjs +296 -0
- package/src/cli/sync.mjs +383 -0
- package/src/cli/uninstall.mjs +613 -0
- package/src/cli/upgrade.mjs +141 -0
- package/src/cli/wizard.html +3682 -0
- package/src/core/blackboard.mjs +54 -0
- package/src/core/clarity-rewriter.mjs +770 -0
- package/src/core/conclusion-writer.mjs +259 -0
- package/src/core/config.mjs +277 -0
- package/src/core/conflict-detector.mjs +536 -0
- package/src/core/crypto.mjs +107 -0
- package/src/core/daemon-control.mjs +158 -0
- package/src/core/daemon-loop.mjs +364 -0
- package/src/core/dream.mjs +347 -0
- package/src/core/drift-detector.mjs +143 -0
- package/src/core/embeddings.mjs +632 -0
- package/src/core/emergency-alerts.mjs +137 -0
- package/src/core/evolution.mjs +107 -0
- package/src/core/fact-seeker.mjs +1602 -0
- package/src/core/friction.mjs +95 -0
- package/src/core/import-rules.mjs +308 -0
- package/src/core/inference-engine.mjs +367 -0
- package/src/core/logger.mjs +75 -0
- package/src/core/memory-layers.mjs +96 -0
- package/src/core/migrate.mjs +116 -0
- package/src/core/notifications.mjs +77 -0
- package/src/core/operation-validator.mjs +272 -0
- package/src/core/optimizer.mjs +330 -0
- package/src/core/pattern_detector.mjs +113 -0
- package/src/core/post-mortem.mjs +413 -0
- package/src/core/protect-instructions.mjs +148 -0
- package/src/core/quick-capture.mjs +145 -0
- package/src/core/research-queue.mjs +431 -0
- package/src/core/research.mjs +461 -0
- package/src/core/runtime.mjs +515 -0
- package/src/core/sandbox.mjs +163 -0
- package/src/core/scheduler.mjs +569 -0
- package/src/core/schema.mjs +333 -0
- package/src/core/search.mjs +219 -0
- package/src/core/semantic-index.mjs +276 -0
- package/src/core/session-watcher.mjs +850 -0
- package/src/core/snapshot.mjs +133 -0
- package/src/core/source-adapters.mjs +1038 -0
- package/src/core/steering.mjs +137 -0
- package/src/core/surface.mjs +444 -0
- package/src/core/task_runner.mjs +125 -0
- package/src/core/tts.mjs +142 -0
- package/src/core/usage-tracker.mjs +349 -0
- package/src/core/vault-cache.mjs +76 -0
- package/src/core/vault.mjs +273 -0
- package/src/core/watchdog.mjs +236 -0
- package/src/server/api.mjs +944 -0
- package/src/server/auth.mjs +410 -0
- package/src/server/index.mjs +868 -0
- package/src/server/keys.mjs +181 -0
- package/src/server/rest.mjs +1652 -0
- package/src/server/route-manifest.json +77 -0
- package/src/server/routes/_shared.mjs +93 -0
- package/src/server/routes/keys.mjs +55 -0
- package/src/server/routes/memory.mjs +249 -0
- package/src/server/routes/sessions.mjs +336 -0
- package/src/server/routes/share.mjs +138 -0
- package/src/server/tools.mjs +618 -0
- package/templates/Caddyfile +37 -0
- package/templates/com.totalrecall.backup.plist +42 -0
- package/templates/com.totalrecall.daemon.plist +40 -0
- package/templates/com.totalrecall.duckdns.plist +23 -0
- package/templates/com.totalrecall.server.plist +46 -0
- package/templates/default-config/budget.yml +6 -0
- package/templates/default-config/runtime.yml +11 -0
- package/templates/default-config/security.yml +53 -0
- package/templates/default-config/voice.yml +34 -0
- package/templates/obsidian-queries/conflicts.md +26 -0
- package/templates/obsidian-queries/daily.md +17 -0
- package/templates/obsidian-queries/index.md +38 -0
- package/templates/obsidian-queries/skills.md +14 -0
- package/templates/onboarding-interview.md +72 -0
- package/templates/skill_engineering.md +40 -0
- package/templates/total-recall-daemon.service +21 -0
- 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
|
+
}
|
package/src/cli/lint.mjs
ADDED
|
@@ -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
|
+
}
|
package/src/cli/map.mjs
ADDED
|
@@ -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
|
+
}
|