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,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scan-skill.mjs — Static Security Scanner for Skill Scripts
|
|
3
|
+
*
|
|
4
|
+
* Performs pattern-based static analysis on skill files to detect
|
|
5
|
+
* potential security risks before installation. Catches:
|
|
6
|
+
*
|
|
7
|
+
* CRITICAL:
|
|
8
|
+
* - Dynamic command injection (exec/spawn with template literals/variables)
|
|
9
|
+
* - Unsafe code evaluation (eval, new Function, vm.runInNewContext)
|
|
10
|
+
*
|
|
11
|
+
* WARNING:
|
|
12
|
+
* - External HTTP/network calls (fetch, axios, http.get, XMLHttpRequest)
|
|
13
|
+
* - Filesystem access outside .agent/ scope
|
|
14
|
+
* - Environment variable exfiltration
|
|
15
|
+
*
|
|
16
|
+
* INFO:
|
|
17
|
+
* - Use of child_process with static args (acceptable but noted)
|
|
18
|
+
* - Crypto operations
|
|
19
|
+
*
|
|
20
|
+
* Each finding is: { severity, rule, line, snippet, file }
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
|
|
26
|
+
// ─── Detection Rules ────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const RULES = [
|
|
29
|
+
// ── CRITICAL: Command injection ─────────────────────────────────────────
|
|
30
|
+
{
|
|
31
|
+
severity: 'CRITICAL',
|
|
32
|
+
rule: 'Dynamic Command Injection',
|
|
33
|
+
// exec/execSync/spawn/spawnSync with template literal or variable interpolation
|
|
34
|
+
pattern: /\b(exec|execSync|execFile|execFileSync|spawn|spawnSync)\s*\(\s*(`[^`]*\$\{|[a-zA-Z_$][\w$]*\s*[+,])/,
|
|
35
|
+
description: 'Detects shell commands built with dynamic interpolation — high injection risk.',
|
|
36
|
+
},
|
|
37
|
+
// ── CRITICAL: Code evaluation ───────────────────────────────────────────
|
|
38
|
+
{
|
|
39
|
+
severity: 'CRITICAL',
|
|
40
|
+
rule: 'Unsafe Code Evaluation',
|
|
41
|
+
pattern: /\b(eval|Function)\s*\(/,
|
|
42
|
+
description: 'eval() or new Function() allows arbitrary code execution.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
severity: 'CRITICAL',
|
|
46
|
+
rule: 'Unsafe Code Evaluation',
|
|
47
|
+
pattern: /\bvm\.(runInNewContext|runInThisContext|compileFunction)\s*\(/,
|
|
48
|
+
description: 'Node.js vm module running dynamic code.',
|
|
49
|
+
},
|
|
50
|
+
// ── WARNING: Network access ─────────────────────────────────────────────
|
|
51
|
+
{
|
|
52
|
+
severity: 'WARNING',
|
|
53
|
+
rule: 'External HTTP/Network Call',
|
|
54
|
+
pattern: /\b(fetch|axios|got|node-fetch|http\.get|https\.get|http\.request|https\.request|XMLHttpRequest|WebSocket)\s*\(/,
|
|
55
|
+
description: 'Makes outbound network requests — verify destination.',
|
|
56
|
+
},
|
|
57
|
+
// ── WARNING: Broad filesystem access ────────────────────────────────────
|
|
58
|
+
{
|
|
59
|
+
severity: 'WARNING',
|
|
60
|
+
rule: 'Filesystem Traversal Risk',
|
|
61
|
+
pattern: /\.\.\//,
|
|
62
|
+
context: /fs\.(read|write|unlink|rmdir|rm|mkdir|access|stat|chmod|rename)/,
|
|
63
|
+
description: 'Path traversal near filesystem operations — verify scope.',
|
|
64
|
+
},
|
|
65
|
+
// ── WARNING: Environment leaks ──────────────────────────────────────────
|
|
66
|
+
{
|
|
67
|
+
severity: 'WARNING',
|
|
68
|
+
rule: 'Environment Variable Access',
|
|
69
|
+
pattern: /process\.env(?!\.(NODE_ENV|HOME|PATH|AGENT_DIR|USER|SHELL|TERM))/,
|
|
70
|
+
description: 'Accesses environment variables beyond standard set.',
|
|
71
|
+
},
|
|
72
|
+
// ── INFO: Static child_process usage (acceptable) ───────────────────────
|
|
73
|
+
{
|
|
74
|
+
severity: 'INFO',
|
|
75
|
+
rule: 'Static Command Execution',
|
|
76
|
+
pattern: /\b(exec|execSync|spawn|spawnSync)\s*\(\s*['"][^'"]*['"]/,
|
|
77
|
+
description: 'Runs a command with static string args — lower risk but noted.',
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
// ─── Scanner ────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Scan a single file for security risks.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} filePath - Absolute path to the file to scan
|
|
87
|
+
* @returns {Array<{ severity: string, rule: string, line: number, snippet: string, file: string }>}
|
|
88
|
+
* Array of findings, empty if the file is clean.
|
|
89
|
+
*/
|
|
90
|
+
export function scanFile(filePath) {
|
|
91
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
const findings = [];
|
|
94
|
+
const seen = new Set(); // deduplicate: one finding per rule per line
|
|
95
|
+
|
|
96
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
97
|
+
const line = lines[lineIdx];
|
|
98
|
+
const lineNum = lineIdx + 1;
|
|
99
|
+
|
|
100
|
+
// Skip comment lines
|
|
101
|
+
const trimmed = line.trim();
|
|
102
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
|
|
103
|
+
|
|
104
|
+
for (const rule of RULES) {
|
|
105
|
+
// If rule has a context pattern, both must match on the same line
|
|
106
|
+
if (rule.context && !rule.context.test(line)) continue;
|
|
107
|
+
|
|
108
|
+
if (rule.pattern.test(line)) {
|
|
109
|
+
const key = `${rule.rule}:${lineNum}`;
|
|
110
|
+
if (seen.has(key)) continue;
|
|
111
|
+
seen.add(key);
|
|
112
|
+
|
|
113
|
+
// Skip INFO-level "Static Command" if a CRITICAL "Dynamic Command Injection"
|
|
114
|
+
// was already found on the same line
|
|
115
|
+
if (rule.severity === 'INFO' && rule.rule === 'Static Command Execution') {
|
|
116
|
+
const hasCritical = findings.some(
|
|
117
|
+
f => f.line === lineNum && f.rule === 'Dynamic Command Injection'
|
|
118
|
+
);
|
|
119
|
+
if (hasCritical) continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
findings.push({
|
|
123
|
+
severity: rule.severity,
|
|
124
|
+
rule: rule.rule,
|
|
125
|
+
line: lineNum,
|
|
126
|
+
snippet: line.trim().slice(0, 120),
|
|
127
|
+
file: filePath,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return findings;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Scan all .mjs/.js/.cjs files in a directory recursively.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} dirPath - Root directory to scan
|
|
140
|
+
* @param {object} [options]
|
|
141
|
+
* @param {string[]} [options.extensions] - File extensions to scan (default: .mjs, .js, .cjs)
|
|
142
|
+
* @param {string[]} [options.ignore] - Directory names to skip
|
|
143
|
+
* @returns {Array<{ severity: string, rule: string, line: number, snippet: string, file: string }>}
|
|
144
|
+
*/
|
|
145
|
+
export function scanDirectory(dirPath, {
|
|
146
|
+
extensions = ['.mjs', '.js', '.cjs'],
|
|
147
|
+
ignore = ['node_modules', '.git', '.system_generated'],
|
|
148
|
+
} = {}) {
|
|
149
|
+
const allFindings = [];
|
|
150
|
+
|
|
151
|
+
function walk(dir) {
|
|
152
|
+
if (!fs.existsSync(dir)) return;
|
|
153
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
154
|
+
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
const full = path.join(dir, entry.name);
|
|
157
|
+
|
|
158
|
+
if (entry.isDirectory()) {
|
|
159
|
+
if (!ignore.includes(entry.name)) walk(full);
|
|
160
|
+
} else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
161
|
+
allFindings.push(...scanFile(full));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
walk(dirPath);
|
|
167
|
+
|
|
168
|
+
// Sort: CRITICAL first, then WARNING, then INFO
|
|
169
|
+
const severityOrder = { CRITICAL: 0, WARNING: 1, INFO: 2 };
|
|
170
|
+
allFindings.sort((a, b) =>
|
|
171
|
+
(severityOrder[a.severity] ?? 3) - (severityOrder[b.severity] ?? 3)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return allFindings;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Generate a human-readable security report.
|
|
179
|
+
*
|
|
180
|
+
* @param {Array<{ severity: string, rule: string, line: number, snippet: string, file: string }>} findings
|
|
181
|
+
* @returns {string} Formatted report
|
|
182
|
+
*/
|
|
183
|
+
export function formatReport(findings) {
|
|
184
|
+
if (findings.length === 0) return '✅ No security findings detected.\n';
|
|
185
|
+
|
|
186
|
+
const icons = { CRITICAL: '🚨', WARNING: '⚠️', INFO: 'ℹ️' };
|
|
187
|
+
const critCount = findings.filter(f => f.severity === 'CRITICAL').length;
|
|
188
|
+
const warnCount = findings.filter(f => f.severity === 'WARNING').length;
|
|
189
|
+
const infoCount = findings.filter(f => f.severity === 'INFO').length;
|
|
190
|
+
|
|
191
|
+
let report = `\n── Security Scan Report ──────────────────────────────────────\n`;
|
|
192
|
+
report += ` 🚨 ${critCount} critical ⚠️ ${warnCount} warning ℹ️ ${infoCount} info\n\n`;
|
|
193
|
+
|
|
194
|
+
for (const f of findings) {
|
|
195
|
+
const icon = icons[f.severity] || '❓';
|
|
196
|
+
const relFile = path.relative(process.cwd(), f.file);
|
|
197
|
+
report += `${icon} [${f.severity}] ${f.rule}\n`;
|
|
198
|
+
report += ` ${relFile}:${f.line}\n`;
|
|
199
|
+
report += ` │ ${f.snippet}\n\n`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (critCount > 0) {
|
|
203
|
+
report += `🚫 BLOCKED: ${critCount} CRITICAL finding(s) must be resolved before installation.\n`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return report;
|
|
207
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const ROOT = path.resolve(__dirname, '../../../../');
|
|
9
|
+
const SKILLS_DIR = path.join(ROOT, '.agent/skills');
|
|
10
|
+
|
|
11
|
+
const hooks = {
|
|
12
|
+
add: path.join(SKILLS_DIR, 'skill/hooks/on-skill-add.sh'),
|
|
13
|
+
delete: path.join(SKILLS_DIR, 'skill/hooks/on-skill-delete.sh'),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
console.log(`\n👁️ Skill Watcher Daemon Started`);
|
|
17
|
+
console.log(` Watching: ${SKILLS_DIR}/**/SKILL.md`);
|
|
18
|
+
|
|
19
|
+
// Debounce state
|
|
20
|
+
let timeoutId = null;
|
|
21
|
+
let pendingActions = new Set();
|
|
22
|
+
|
|
23
|
+
function executeHooks() {
|
|
24
|
+
const actions = Array.from(pendingActions);
|
|
25
|
+
pendingActions.clear();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (actions.includes('add') || actions.includes('change')) {
|
|
29
|
+
console.log(`\n[Watcher] Triggering on-skill-add.sh...`);
|
|
30
|
+
execSync(`bash ${hooks.add}`, { stdio: 'inherit' });
|
|
31
|
+
} else if (actions.includes('unlink')) {
|
|
32
|
+
console.log(`\n[Watcher] Triggering on-skill-delete.sh...`);
|
|
33
|
+
execSync(`bash ${hooks.delete}`, { stdio: 'inherit' });
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`\n[Watcher] Hook execution failed: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function queueAction(action) {
|
|
41
|
+
pendingActions.add(action);
|
|
42
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
43
|
+
// Debounce for 1.5 seconds to batch file saves
|
|
44
|
+
timeoutId = setTimeout(executeHooks, 1500);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
chokidar.watch(path.join(SKILLS_DIR, '**/SKILL.md'), {
|
|
48
|
+
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
|
49
|
+
persistent: true,
|
|
50
|
+
ignoreInitial: true // Do not trigger on initial scan
|
|
51
|
+
})
|
|
52
|
+
.on('add', filePath => {
|
|
53
|
+
console.log(`[Watcher] File added: ${path.relative(ROOT, filePath)}`);
|
|
54
|
+
queueAction('add');
|
|
55
|
+
})
|
|
56
|
+
.on('change', filePath => {
|
|
57
|
+
console.log(`[Watcher] File changed: ${path.relative(ROOT, filePath)}`);
|
|
58
|
+
queueAction('change');
|
|
59
|
+
})
|
|
60
|
+
.on('unlink', filePath => {
|
|
61
|
+
console.log(`[Watcher] File removed: ${path.relative(ROOT, filePath)}`);
|
|
62
|
+
queueAction('unlink');
|
|
63
|
+
})
|
|
64
|
+
.on('error', error => console.error(`[Watcher] error: ${error}`));
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Subagent: Audit All Dev Skills for Compliance
|
|
2
|
+
|
|
3
|
+
> Parallel worker prompt for auditing the entire `.agent/skills/` directory.
|
|
4
|
+
|
|
5
|
+
## Your Task
|
|
6
|
+
|
|
7
|
+
You are a specialized auditor. Your ONLY task is to verify that every Dev Skill in `.agent/skills/` complies with the AgentSkills.io standard.
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
1. List all directories in `.agent/skills/`
|
|
12
|
+
2. For each skill directory, check:
|
|
13
|
+
- [ ] `SKILL.md` exists
|
|
14
|
+
- [ ] YAML frontmatter contains `name` field
|
|
15
|
+
- [ ] YAML frontmatter contains `description` field
|
|
16
|
+
- [ ] Description includes WHAT (core capability statement)
|
|
17
|
+
- [ ] Description includes WHEN (trigger phrase)
|
|
18
|
+
- [ ] Description includes WHEN NOT (negative trigger, contains "Do NOT" or "NEVER")
|
|
19
|
+
- [ ] Description is under 1,024 characters
|
|
20
|
+
- [ ] `SKILL.md` body is under 500 lines
|
|
21
|
+
|
|
22
|
+
3. Return a JSON report in this exact format:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"total": 35,
|
|
27
|
+
"compliant": 30,
|
|
28
|
+
"non_compliant": 5,
|
|
29
|
+
"results": [
|
|
30
|
+
{
|
|
31
|
+
"name": "skill-name",
|
|
32
|
+
"dir": "skill-dir",
|
|
33
|
+
"compliant": true,
|
|
34
|
+
"issues": []
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "broken-skill",
|
|
38
|
+
"dir": "broken-dir",
|
|
39
|
+
"compliant": false,
|
|
40
|
+
"issues": ["Missing negative trigger in description", "SKILL.md exceeds 500 lines (612 lines)"]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Tools Available
|
|
47
|
+
|
|
48
|
+
- `view_file`: Read file contents
|
|
49
|
+
- `grep_search`: Search for patterns
|
|
50
|
+
- `run_command`: Execute shell commands
|
|
51
|
+
- `list_dir`: List directory contents
|
|
52
|
+
|
|
53
|
+
## Tools NOT Available
|
|
54
|
+
|
|
55
|
+
- `write_to_file`: You cannot modify files
|
|
56
|
+
- `replace_file_content`: You cannot edit files
|
|
57
|
+
|
|
58
|
+
## Important
|
|
59
|
+
|
|
60
|
+
- Do NOT fix anything. Only audit and report.
|
|
61
|
+
- Do NOT skip any skill directory.
|
|
62
|
+
- Report ALL issues found, even minor ones.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: skill
|
|
3
|
+
name: ssss
|
|
4
|
+
description: "Use this skill to inspect, validate, write, and manage SSSS structured semantic memory nodes and VFS specifications. Do NOT use for raw non-schema memory files."
|
|
5
|
+
schema_version: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# SSSS — Structured Semantic Syntax System Manager
|
|
9
|
+
|
|
10
|
+
Structured Semantic Syntax System (ssss) is the core operational substrate of the Total Recall Virtual File System (VFS). This skill guides agents in inspecting, reading, and constructing SSSS-compliant memory nodes, conflict models, and derived indices.
|
|
11
|
+
|
|
12
|
+
## Core Directives
|
|
13
|
+
|
|
14
|
+
1. **Schema Integrity:** Every SSSS node must strictly adhere to the frontmatter schema definitions (Modality, Category, Sentiment).
|
|
15
|
+
2. **Zero-Database Persistence:** Do not look for Postgres or SQLite. Everything lives as a git-versioned Markdown file.
|
|
16
|
+
3. **Decay and Drift Monitoring:** Use decay indicators to manage node half-lives dynamically in background ticks.
|
|
17
|
+
|
|
18
|
+
## Progressive Memory Disclosure
|
|
19
|
+
|
|
20
|
+
Memory flows through a 3-tier progressive hierarchy:
|
|
21
|
+
* **Tier 1 (Hot):** INSTRUCTIONS.md shims (Invariants only).
|
|
22
|
+
* **Tier 2 (Warm):** SKILL.md injects (Category-routed warm capsules).
|
|
23
|
+
* **Tier 3 (Cold):** Full memory vault (Search index files on demand).
|
|
24
|
+
|
|
25
|
+
## References
|
|
26
|
+
|
|
27
|
+
* Core SSSS specification: `references/ssss-spec.md`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "ssss-has-skill-file",
|
|
4
|
+
"assertion": "The skill directory contains a valid SKILL.md entrypoint",
|
|
5
|
+
"severity": "error"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"name": "ssss-has-spec-sheet",
|
|
9
|
+
"assertion": "The references directory contains ssss-spec.md",
|
|
10
|
+
"severity": "error"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "ssss-validates-successfully",
|
|
14
|
+
"assertion": "The validate-schema script parses frontmatter correctly",
|
|
15
|
+
"severity": "warning"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# SSSS — Structured Semantic Syntax System
|
|
2
|
+
|
|
3
|
+
> The schema specification that `total-recall` uses for all vault files, session logs, and derived indexes.
|
|
4
|
+
|
|
5
|
+
SSSS is a **database-free, Markdown-first schema** for AI agent memory. Every rule, fact, preference, and observation is a plain `.md` file with YAML frontmatter. There is no relational database, no binary format, and no proprietary lock-in.
|
|
6
|
+
|
|
7
|
+
`total-recall` implements SSSS as its storage layer. Any IDE plugin, CLI tool, or AI agent framework that can read Markdown can interoperate with a `total-recall` vault.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Core Mandate
|
|
12
|
+
|
|
13
|
+
| Principle | Rule |
|
|
14
|
+
|-----------|------|
|
|
15
|
+
| **No Relational Databases** | Workspace configuration must not live in Postgres or any external database. |
|
|
16
|
+
| **Markdown is Law** | Every memory primitive (rule, pattern, decision, concept) exists as a `.md` file. |
|
|
17
|
+
| **Semantic Frontmatter** | Every file MUST contain YAML frontmatter with a `type` field that identifies how engines interpret it. |
|
|
18
|
+
| **Disposable Indexes** | Derived JSONL indexes are ephemeral caches, fully rebuildable from the Markdown vault. Delete them freely. |
|
|
19
|
+
| **Git-Versioned** | The vault directory is version-controlled. History = provenance. |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 2. File Types
|
|
24
|
+
|
|
25
|
+
### 2.1 Memory Node (`type: memory`)
|
|
26
|
+
|
|
27
|
+
A single unit of agent knowledge. Lives in `.agent/memory-vault/<category>/<slug>.md`.
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
---
|
|
31
|
+
type: memory
|
|
32
|
+
slug: prefer-atomic-writes
|
|
33
|
+
category: patterns
|
|
34
|
+
title: "Always write files atomically (write-then-rename)"
|
|
35
|
+
status: active
|
|
36
|
+
confidence: 0.92
|
|
37
|
+
importance: 4
|
|
38
|
+
created: 2026-01-15T10:00:00Z
|
|
39
|
+
updated: 2026-05-01T14:03:00Z
|
|
40
|
+
last_accessed: 2026-05-10T09:55:00Z
|
|
41
|
+
source:
|
|
42
|
+
type: chat
|
|
43
|
+
session_id: abc123
|
|
44
|
+
agent: my-agent
|
|
45
|
+
evidence_count: 3
|
|
46
|
+
supersedes: []
|
|
47
|
+
superseded_by: null
|
|
48
|
+
contradicts: []
|
|
49
|
+
tags: [filesystem, reliability, writes]
|
|
50
|
+
related: [no-partial-files]
|
|
51
|
+
routes_to_skills: []
|
|
52
|
+
sentiment_polarity: directive_must
|
|
53
|
+
sentiment_target: file writes
|
|
54
|
+
modality: must
|
|
55
|
+
subject: agent
|
|
56
|
+
predicate: use_atomic_write
|
|
57
|
+
object: file_system
|
|
58
|
+
decay:
|
|
59
|
+
half_life_days: 180
|
|
60
|
+
access_count: 7
|
|
61
|
+
schema_version: 2
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
Always write to a `.tmp.<pid>` file first, then `fs.renameSync()` to the target path.
|
|
65
|
+
`rename()` is atomic on POSIX filesystems. Direct writes risk partial file corruption
|
|
66
|
+
on crash or concurrent access.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Category Taxonomy
|
|
70
|
+
|
|
71
|
+
| Category | Purpose | Example slugs |
|
|
72
|
+
|----------|---------|---------------|
|
|
73
|
+
| `invariants/` | Absolute rules — always enforced | `rule-zero-text-first`, `no-silent-push` |
|
|
74
|
+
| `patterns/` | "Always do X" best practices | `prefer-atomic-writes`, `use-pm2-reload` |
|
|
75
|
+
| `anti-patterns/` | "Never do X" negative rules | `no-hardcoded-secrets`, `no-raw-tsc` |
|
|
76
|
+
| `preferences/` | User or project style preferences | `prefer-kebab-case-slugs` |
|
|
77
|
+
| `decisions/` | One-time architectural decisions | `chose-sqlite-over-postgres` |
|
|
78
|
+
| `concepts/` | Domain knowledge and definitions | `what-is-bm25-scoring` |
|
|
79
|
+
| `facts/` | Evidence-backed knowledge acquired from research or observation | `stripe-connect-api-version` |
|
|
80
|
+
|
|
81
|
+
#### Required Fields (schema_version: 2)
|
|
82
|
+
|
|
83
|
+
| Field | Type | Description |
|
|
84
|
+
|-------|------|-------------|
|
|
85
|
+
| `type` | `"memory"` | Fixed value |
|
|
86
|
+
| `slug` | `string` | Globally unique, kebab-case, matches filename without `.md` |
|
|
87
|
+
| `category` | `string` | Must match parent directory name |
|
|
88
|
+
| `title` | `string` | One-line human-readable description |
|
|
89
|
+
| `status` | `active | superseded | deprecated | draft` | Lifecycle state |
|
|
90
|
+
| `confidence` | `0..1` | Adjusted by Dream Cycle decay/promotion |
|
|
91
|
+
| `importance` | `1..5` | Set by user or distillation |
|
|
92
|
+
| `modality` | `must | must_not | should | should_not` | Directive strength |
|
|
93
|
+
| `subject` | `string` | Who is constrained (usually `"agent"`) |
|
|
94
|
+
| `predicate` | `string` | What action, in snake_case verb form |
|
|
95
|
+
| `object` | `string` | What target the action applies to |
|
|
96
|
+
| `sentiment_polarity` | enum | `directive_must | directive_must_not | descriptive | preference` |
|
|
97
|
+
| `decay.half_life_days` | `number` | Days until confidence halves from disuse |
|
|
98
|
+
| `schema_version` | `2` | Must be `2` for v2 nodes |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 3. Three-Tier Memory Hierarchy
|
|
103
|
+
|
|
104
|
+
Memory nodes are surfaced to the AI through a progressive disclosure system.
|
|
105
|
+
Not all memories are relevant to every prompt.
|
|
106
|
+
|
|
107
|
+
| Tier | Location | Purpose | Size Limit | Latency |
|
|
108
|
+
|------|----------|---------|------------|---------|
|
|
109
|
+
| **Tier 1: Hot** | `INSTRUCTIONS.md` | Critical invariants, always in system prompt | < 1,000 tokens | 0ms |
|
|
110
|
+
| **Tier 2: Skills** | `SKILL.md` files | Curated rules injected by semantic relevance | ≤ 7 rules/skill | ~100ms |
|
|
111
|
+
| **Tier 3: Vault** | `.agent/memory-vault/` | Full knowledge graph, source of truth | Unlimited | ~500ms |
|
|
112
|
+
|
|
113
|
+
**How nodes move between tiers:**
|
|
114
|
+
- `priority: absolute` + `modality: must|must_not` → Tier 1 (always in system prompt)
|
|
115
|
+
- Active nodes with `confidence ≥ 0.35` → Tier 2 candidates (routed by `surface.mjs`)
|
|
116
|
+
- Everything else → Tier 3 (accessible on-demand via full-text search)
|
|
117
|
+
- The Dream Cycle continuously promotes/demotes based on access frequency and confidence
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validates a single SSSS Markdown file against schema rules
|
|
7
|
+
*/
|
|
8
|
+
export function validateNode(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
11
|
+
const { data } = matter(raw);
|
|
12
|
+
const errors = [];
|
|
13
|
+
|
|
14
|
+
if (!data.type) errors.push('Missing "type" property.');
|
|
15
|
+
if (!data.slug) errors.push('Missing "slug" property.');
|
|
16
|
+
if (!data.category) errors.push('Missing "category" property.');
|
|
17
|
+
if (!data.title) errors.push('Missing "title" property.');
|
|
18
|
+
|
|
19
|
+
if (data.schema_version !== 2) {
|
|
20
|
+
errors.push(`Expected schema_version: 2, got: ${data.schema_version}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
valid: errors.length === 0,
|
|
25
|
+
errors,
|
|
26
|
+
file: filePath
|
|
27
|
+
};
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return {
|
|
30
|
+
valid: false,
|
|
31
|
+
errors: [err.message],
|
|
32
|
+
file: filePath
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (import.meta.url.startsWith('file:')) {
|
|
38
|
+
const runDirectly = process.argv[1] && process.argv[1].endsWith('validate-schema.mjs');
|
|
39
|
+
if (runDirectly) {
|
|
40
|
+
const target = process.argv[2];
|
|
41
|
+
if (!target) {
|
|
42
|
+
console.log('No file specified to validate.');
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
const res = validateNode(path.resolve(target));
|
|
46
|
+
if (res.valid) {
|
|
47
|
+
console.log(`✅ SSSS node "${path.basename(target)}" is fully valid!`);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
} else {
|
|
50
|
+
console.error(`❌ Validation failed for "${path.basename(target)}":\n${res.errors.join('\n')}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Role: SSSS Schema Auditor
|
|
2
|
+
|
|
3
|
+
You are a specialized subagent designed to audit Structured Semantic Syntax System (SSSS) nodes.
|
|
4
|
+
|
|
5
|
+
## Directives
|
|
6
|
+
1. Open and parse the target `.md` memory node.
|
|
7
|
+
2. Confirm the presence of absolute frontmatter keys: `type`, `slug`, `category`, `title`, and `schema_version`.
|
|
8
|
+
3. Verify that `category` matches the parent directory.
|
|
9
|
+
4. Report any formatting anomalies.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Total Recall Diagnostician Subagent
|
|
2
|
+
|
|
3
|
+
You are a specialized diagnostic subagent focused on maintaining, troubleshooting, and self-healing the **Total Recall Sovereign AI OS** environment. Your objective is to ensure the local brain is fully reachable, the filesystem VFS conforms to the Structured Semantic Syntax System (SSSS) spec, and the IDE shims compile without errors.
|
|
4
|
+
|
|
5
|
+
## 🛠️ Directives and Tools
|
|
6
|
+
|
|
7
|
+
1. **Auditing System Health**:
|
|
8
|
+
* Query the `/health` and `/api/vault/status` endpoints to check database-free status and node counts.
|
|
9
|
+
* Read the troubleshooting guide at `../references/troubleshooting.md` for specific step-by-step recipes on how to solve conflicts.
|
|
10
|
+
2. **Validating SSSS Frontmatter**:
|
|
11
|
+
* Query the `/api/memory` and `/api/memory/:slug` REST endpoints to verify that newly created user nodes adhere to the schema in `../references/ssss-reference.md`.
|
|
12
|
+
3. **Executing Compiler Runs**:
|
|
13
|
+
* If shims are out of sync or modified, trigger a compiler run via `POST /api/vault/compile` or run `npx total-recall compile`.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `.agent/` directory resolver for CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Supports two brain layers:
|
|
5
|
+
* - Global brain: ~/.agent/skills/total-recall/ (identity, universal rules)
|
|
6
|
+
* - Project brain: <cwd>/.agent/skills/total-recall/ (project-specific knowledge)
|
|
7
|
+
*
|
|
8
|
+
* The `--global` / `--project` flags let users explicitly target a layer.
|
|
9
|
+
* Without flags, workspace-local wins over global when a project brain exists.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse --global / --project layer flags from CLI args.
|
|
18
|
+
* Returns { layer: 'global' | 'project' | 'auto', remainingArgs: string[] }
|
|
19
|
+
*/
|
|
20
|
+
export function parseLayerFlag(args) {
|
|
21
|
+
let layer = 'auto';
|
|
22
|
+
const remainingArgs = [];
|
|
23
|
+
for (const arg of args) {
|
|
24
|
+
if (arg === '--global') layer = 'global';
|
|
25
|
+
else if (arg === '--project') layer = 'project';
|
|
26
|
+
else remainingArgs.push(arg);
|
|
27
|
+
}
|
|
28
|
+
return { layer, remainingArgs };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Global agent directory — always at ~/.agent/ */
|
|
32
|
+
export function getGlobalAgentDir() {
|
|
33
|
+
return process.env.AGENT_DIR || path.join(os.homedir(), '.agent');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Global brain directory — ~/.agent/skills/total-recall/ */
|
|
37
|
+
export function getGlobalBrainDir() {
|
|
38
|
+
return path.join(getGlobalAgentDir(), 'skills', 'total-recall');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detect a project-level brain by checking <cwd>/.agent/skills/total-recall/.
|
|
43
|
+
* Returns { agentDir, brainDir } or null.
|
|
44
|
+
*/
|
|
45
|
+
export function detectProjectBrain() {
|
|
46
|
+
if (process.env._TR_TEST_AGENT_DIR) return null;
|
|
47
|
+
const cwd = process.cwd();
|
|
48
|
+
const homeDir = os.homedir();
|
|
49
|
+
if (cwd === homeDir) return null; // don't detect global as project
|
|
50
|
+
const localAgentDir = path.join(cwd, '.agent');
|
|
51
|
+
const localBrainDir = path.join(localAgentDir, 'skills', 'total-recall');
|
|
52
|
+
if (fs.existsSync(path.join(localBrainDir, 'memory-vault'))) {
|
|
53
|
+
return { agentDir: localAgentDir, brainDir: localBrainDir };
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve agent dir based on explicit layer or auto-detect.
|
|
60
|
+
* @param {'global' | 'project' | 'auto'} [layer='auto']
|
|
61
|
+
*/
|
|
62
|
+
export function resolveAgentDir(layer = 'auto') {
|
|
63
|
+
if (process.env.AGENT_DIR) {
|
|
64
|
+
return process.env.AGENT_DIR;
|
|
65
|
+
}
|
|
66
|
+
if (layer === 'global') return getGlobalAgentDir();
|
|
67
|
+
if (layer === 'project') {
|
|
68
|
+
const project = detectProjectBrain();
|
|
69
|
+
if (!project) throw new Error('No project brain found. Run `npx total-recall init --project` to create one.');
|
|
70
|
+
return project.agentDir;
|
|
71
|
+
}
|
|
72
|
+
// Auto: project-local wins if it exists
|
|
73
|
+
const project = detectProjectBrain();
|
|
74
|
+
return project ? project.agentDir : getGlobalAgentDir();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the brain directory for a specific layer.
|
|
79
|
+
* @param {'global' | 'project' | 'auto'} [layer='auto']
|
|
80
|
+
*/
|
|
81
|
+
export function resolveBrainDir(layer = 'auto') {
|
|
82
|
+
return path.join(resolveAgentDir(layer), 'skills', 'total-recall');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get both brain layers for the current context.
|
|
87
|
+
* @returns {{ global: { agentDir: string, brainDir: string }, project: { agentDir: string, brainDir: string } | null }}
|
|
88
|
+
*/
|
|
89
|
+
export function getBothBrains() {
|
|
90
|
+
const globalAgentDir = getGlobalAgentDir();
|
|
91
|
+
const globalBrainDir = getGlobalBrainDir();
|
|
92
|
+
const project = detectProjectBrain();
|
|
93
|
+
return {
|
|
94
|
+
global: { agentDir: globalAgentDir, brainDir: globalBrainDir },
|
|
95
|
+
project,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Determine the default layer for a memory category.
|
|
101
|
+
* Always defaults to project brain. Use --global to explicitly target global.
|
|
102
|
+
*/
|
|
103
|
+
export function defaultLayerForCategory(_category) {
|
|
104
|
+
return 'project';
|
|
105
|
+
}
|