total-recall-brain 3.44.0 → 3.45.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/bin/total-recall.mjs +6 -4
- package/docs/ARCHITECTURE.md +10 -7
- package/docs/reference/cli-reference.md +13 -8
- package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +2 -2
- package/frontend/src/pages/PluginsPage.tsx +1 -1
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
- package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
- package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
- package/src/cli/agent.mjs +1 -1
- package/src/cli/command.mjs +41 -2
- package/src/cli/dream.mjs +93 -69
- package/src/cli/harness.mjs +4 -4
- package/src/cli/help.mjs +1 -1
- package/src/cli/init.mjs +0 -25
- package/src/cli/lessons.mjs +51 -0
- package/src/cli/memory-plugin.mjs +1 -1
- package/src/cli/plugin/copies.mjs +64 -14
- package/src/cli/plugin/create.mjs +6 -7
- package/src/cli/plugin/index.mjs +11 -7
- package/src/cli/plugin/install.mjs +10 -15
- package/src/cli/plugin/remove.mjs +1 -1
- package/src/cli/plugin/search.mjs +2 -2
- package/src/cli/preflight.mjs +19 -6
- package/src/cli/proposals.mjs +1 -1
- package/src/cli/recall.mjs +66 -23
- package/src/cli/remember.mjs +3 -14
- package/src/cli/server.mjs +18 -0
- package/src/cli/startup.mjs +1 -0
- package/src/cli/task.mjs +9 -2
- package/src/cli/update.mjs +6 -2
- package/src/core/access-ledger.mjs +78 -0
- package/src/core/agent-manager.mjs +2 -2
- package/src/core/brain-locate.mjs +15 -0
- package/src/core/brain-updates.mjs +31 -11
- package/src/core/claude-hooks.mjs +3 -1
- package/src/core/cleanup-task.mjs +15 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-maintenance.mjs +4 -1
- package/src/core/cli-semantic-index.mjs +23 -3
- package/src/core/context-compiler.mjs +20 -0
- package/src/core/daemon-loop.mjs +39 -62
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/dream.mjs +33 -105
- package/src/core/ensure-dependencies.mjs +148 -0
- package/src/core/fast-recall.mjs +2 -2
- package/src/core/field-evaluation.mjs +23 -0
- package/src/core/gate-mode.mjs +1 -1
- package/src/core/harness-hooks.mjs +14 -2
- package/src/core/lesson-hooks.mjs +74 -0
- package/src/core/lesson-memory.mjs +24 -0
- package/src/core/lesson-sessions.mjs +52 -0
- package/src/core/lessons.mjs +96 -0
- package/src/core/meta-harness.mjs +2 -2
- package/src/core/optimizer.mjs +66 -165
- package/src/core/plugin-contracts/card.mjs +3 -0
- package/src/core/plugin-loader.mjs +12 -3
- package/src/core/plugin-runtime.mjs +40 -1
- package/src/core/plugin-store.mjs +35 -23
- package/src/core/post-mortem.mjs +52 -36
- package/src/core/preflight-gate.mjs +23 -2
- package/src/core/proposal-applier.mjs +31 -2
- package/src/core/repo-sync.mjs +4 -6
- package/src/core/required-plugins.mjs +26 -4
- package/src/core/rule-length.mjs +23 -0
- package/src/core/runtime.mjs +1 -1
- package/src/core/scheduler.mjs +35 -43
- package/src/core/schema.mjs +1 -2
- package/src/core/search.mjs +4 -3
- package/src/core/server-service.mjs +148 -0
- package/src/core/session-watcher.mjs +19 -1
- package/src/core/skill-gap.mjs +16 -0
- package/src/core/ssss-kernel-bridge.mjs +32 -4
- package/src/core/startup-health.mjs +6 -1
- package/src/core/surface.mjs +65 -54
- package/src/core/task-executors.mjs +54 -106
- package/src/core/validated-write.mjs +1 -0
- package/src/core/vector-field.mjs +134 -192
- package/src/server/invoke-core-child.mjs +24 -0
- package/src/server/invoke-core-operations.mjs +102 -0
- package/src/server/route-manifest.json +0 -56
- package/src/server/routes/brains.mjs +0 -18
- package/src/server/routes/context.mjs +0 -41
- package/src/server/routes/invoke.mjs +26 -1
- package/src/server/routes/ssss.mjs +0 -169
- package/src/server/routes/system.mjs +0 -15
- package/src/server/routes/tasks.mjs +0 -26
- package/src/server/routes/vault.mjs +2 -23
- package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
- package/src/core/append-log.mjs +0 -131
- package/src/core/plugin-tasks.mjs +0 -173
- package/templates/onboarding-interview.md +0 -82
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** Repair this installation's macOS server LaunchAgent. Never manages systemd. */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { processIdentity } from './startup-health.mjs';
|
|
8
|
+
|
|
9
|
+
const PACKAGE_ROOT = fileURLToPath(new URL('../..', import.meta.url));
|
|
10
|
+
const LABEL = 'com.totalrecall.server';
|
|
11
|
+
const corePathEnv = new Set(['AGENT_DIR', 'TR_SECRETS_BRAIN', 'TR_SERVER_PID_FILE']);
|
|
12
|
+
const inside = (root, value) => value === root || value.startsWith(root + path.sep);
|
|
13
|
+
const real = value => { try { return fs.realpathSync(value); } catch { return path.resolve(value); } };
|
|
14
|
+
|
|
15
|
+
export function stableNodePath(env = process.env) {
|
|
16
|
+
for (const dir of (env.PATH || '').split(path.delimiter)) {
|
|
17
|
+
if (!dir || !path.isAbsolute(dir)) continue;
|
|
18
|
+
const candidate = path.join(dir, 'node');
|
|
19
|
+
if (candidate.split(path.sep).includes('Cellar')) continue;
|
|
20
|
+
try { fs.accessSync(candidate, fs.constants.X_OK); if (fs.statSync(candidate).isFile()) return candidate; } catch {}
|
|
21
|
+
}
|
|
22
|
+
throw new Error('No stable node executable on PATH. Add the node symlink directory to PATH.');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function convert(input, format, exec) {
|
|
26
|
+
const result = exec('plutil', ['-convert', format, '-o', '-', '-'], { input, encoding: 'utf8', timeout: 5000 });
|
|
27
|
+
if (result.status !== 0 || result.error) throw new Error(`Cannot convert server plist to ${format}.`);
|
|
28
|
+
return result.stdout;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Detect foreign installations without naming any operator repository. */
|
|
32
|
+
function checkoutFor(value, oldRoot, packageRoot) {
|
|
33
|
+
if (inside(packageRoot, value) || inside(real(packageRoot), value)) return null;
|
|
34
|
+
if (oldRoot && real(oldRoot) !== real(packageRoot) && inside(oldRoot, value)) return oldRoot;
|
|
35
|
+
for (let dir = value; ; dir = path.dirname(dir)) {
|
|
36
|
+
try {
|
|
37
|
+
if (JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).name === 'total-recall-brain'
|
|
38
|
+
&& real(dir) !== real(packageRoot)) return dir;
|
|
39
|
+
} catch {}
|
|
40
|
+
if (dir === path.dirname(dir)) return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function serverEnvironment(existing, { packageRoot, oldRoot }) {
|
|
45
|
+
const env = {};
|
|
46
|
+
for (const [key, value] of Object.entries(existing || {})) {
|
|
47
|
+
if (typeof value !== 'string') throw new Error(`Server environment ${key} must be a string.`);
|
|
48
|
+
if (key === 'PATH') {
|
|
49
|
+
env[key] = value.split(path.delimiter).map(part => {
|
|
50
|
+
const foreign = path.isAbsolute(part) && checkoutFor(part, oldRoot, packageRoot);
|
|
51
|
+
return foreign ? path.join(packageRoot, path.relative(foreign, part)) : part;
|
|
52
|
+
}).join(path.delimiter);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const foreign = path.isAbsolute(value) && checkoutFor(value, oldRoot, packageRoot);
|
|
56
|
+
if (!foreign) env[key] = value;
|
|
57
|
+
else if (corePathEnv.has(key)) env[key] = path.join(packageRoot, path.relative(foreign, value));
|
|
58
|
+
// Obsolete settings must not retain a path into another installation.
|
|
59
|
+
}
|
|
60
|
+
return env;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function publicPlist(plist) {
|
|
64
|
+
const copy = structuredClone(plist);
|
|
65
|
+
for (const key of Object.keys(copy.EnvironmentVariables || {})) {
|
|
66
|
+
if (/password|secret(?!s_brain)|token|api.?key|credential/i.test(key)) copy.EnvironmentVariables[key] = '[redacted]';
|
|
67
|
+
}
|
|
68
|
+
return copy;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function plistDiff(before, after) {
|
|
72
|
+
const lines = [];
|
|
73
|
+
const walk = (a, b, key) => {
|
|
74
|
+
if (JSON.stringify(a) === JSON.stringify(b)) return;
|
|
75
|
+
if (a && b && !Array.isArray(a) && !Array.isArray(b) && typeof a === 'object' && typeof b === 'object') {
|
|
76
|
+
for (const part of new Set([...Object.keys(a), ...Object.keys(b)])) walk(a[part], b[part], key ? `${key}.${part}` : part);
|
|
77
|
+
} else {
|
|
78
|
+
if (a !== undefined) lines.push(`- ${key}: ${JSON.stringify(a)}`);
|
|
79
|
+
if (b !== undefined) lines.push(`+ ${key}: ${JSON.stringify(b)}`);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
walk(publicPlist(before), publicPlist(after), '');
|
|
83
|
+
return lines.join('\n') || 'No plist changes.';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function ensureServerService({ dryRun = false, platform = process.platform, home = os.homedir(),
|
|
87
|
+
packageRoot = PACKAGE_ROOT, env = process.env, exec = spawnSync, identity = processIdentity,
|
|
88
|
+
uid = process.getuid?.(), sleep = ms => new Promise(resolve => setTimeout(resolve, ms)), attempts = 40 } = {}) {
|
|
89
|
+
if (platform !== 'darwin') return { status: 'unchanged', reason: 'server-release owns the server on systemd hosts; no changes made.' };
|
|
90
|
+
packageRoot = path.resolve(packageRoot);
|
|
91
|
+
const plistPath = path.join(home, 'Library/LaunchAgents', `${LABEL}.plist`);
|
|
92
|
+
const oldBytes = fs.existsSync(plistPath) ? fs.readFileSync(plistPath) : null;
|
|
93
|
+
const existing = oldBytes ? JSON.parse(convert(oldBytes, 'json', exec)) : {};
|
|
94
|
+
const template = fs.readFileSync(path.join(packageRoot, 'templates', `${LABEL}.plist`), 'utf8');
|
|
95
|
+
const desired = JSON.parse(convert(template, 'json', exec));
|
|
96
|
+
const substitute = value => typeof value === 'string' ? value.replaceAll('__ROOT__', packageRoot).replaceAll('__HOME__', home)
|
|
97
|
+
: Array.isArray(value) ? value.map(substitute) : value && typeof value === 'object'
|
|
98
|
+
? Object.fromEntries(Object.entries(value).map(([key, val]) => [key, substitute(val)])) : value;
|
|
99
|
+
const plist = substitute(desired);
|
|
100
|
+
plist.WorkingDirectory = packageRoot;
|
|
101
|
+
plist.ProgramArguments[0] = stableNodePath(env);
|
|
102
|
+
plist.EnvironmentVariables = { ...plist.EnvironmentVariables,
|
|
103
|
+
...serverEnvironment(existing.EnvironmentVariables, { packageRoot, oldRoot: existing.WorkingDirectory }) };
|
|
104
|
+
const diff = plistDiff(existing, plist);
|
|
105
|
+
if (dryRun) return { status: 'dry_run', plistPath, diff };
|
|
106
|
+
|
|
107
|
+
const xml = convert(JSON.stringify(plist), 'xml1', exec);
|
|
108
|
+
const domain = `gui/${uid}`;
|
|
109
|
+
const service = `${domain}/${LABEL}`;
|
|
110
|
+
const run = args => exec('launchctl', args, { encoding: 'utf8', timeout: 10000 });
|
|
111
|
+
const ok = result => result.status === 0 && !result.error;
|
|
112
|
+
const must = args => { if (!ok(run(args))) throw new Error(`launchctl ${args[0]} failed for ${service}.`); };
|
|
113
|
+
const agentDir = plist.EnvironmentVariables.AGENT_DIR || path.join(home, '.agent');
|
|
114
|
+
const pidFile = plist.EnvironmentVariables.TR_SERVER_PID_FILE || path.join(agentDir, 'skills/total-recall/server.pid');
|
|
115
|
+
const entry = path.join(packageRoot, 'src/server/index.mjs');
|
|
116
|
+
const previous = identity(pidFile, entry);
|
|
117
|
+
if (previous.status === 'unknown') throw new Error('Server process identity is unknown. Nothing changed.');
|
|
118
|
+
const loaded = ok(run(['print', service]));
|
|
119
|
+
if (!loaded && ['running', 'conflict'].includes(previous.status)) throw new Error('A server runs outside this LaunchAgent. Nothing changed.');
|
|
120
|
+
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
|
121
|
+
for (const log of [plist.StandardOutPath, plist.StandardErrorPath]) if (log) fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
122
|
+
const pending = `${plistPath}.${process.pid}.tmp`;
|
|
123
|
+
try {
|
|
124
|
+
fs.writeFileSync(pending, xml, { mode: 0o600 });
|
|
125
|
+
fs.renameSync(pending, plistPath);
|
|
126
|
+
if (loaded) {
|
|
127
|
+
must(['bootout', service]);
|
|
128
|
+
let retired = false;
|
|
129
|
+
for (let i = 0; i < attempts; i++) {
|
|
130
|
+
if (!ok(run(['print', service]))) { retired = true; break; }
|
|
131
|
+
await sleep(250);
|
|
132
|
+
}
|
|
133
|
+
if (!retired) throw new Error('LaunchAgent did not retire.');
|
|
134
|
+
}
|
|
135
|
+
must(['bootstrap', domain, plistPath]);
|
|
136
|
+
for (let i = 0; i < attempts; i++) {
|
|
137
|
+
const observed = identity(pidFile, entry);
|
|
138
|
+
if (observed.status === 'running' && (!loaded || observed.pid !== previous.pid)) return { status: 'ready', plistPath, pid: observed.pid, entry: observed.entry };
|
|
139
|
+
await sleep(250);
|
|
140
|
+
}
|
|
141
|
+
throw new Error('Server process verification failed after launchd reload.');
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// Keep the previous definition available for operator recovery. Never report success.
|
|
144
|
+
if (oldBytes !== null) fs.writeFileSync(plistPath, oldBytes, { mode: 0o600 });
|
|
145
|
+
else fs.rmSync(plistPath, { force: true });
|
|
146
|
+
throw error;
|
|
147
|
+
} finally { fs.rmSync(pending, { force: true }); }
|
|
148
|
+
}
|
|
@@ -4,6 +4,7 @@ import os from 'os';
|
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import { atomicWrite } from './vault.mjs';
|
|
6
6
|
import { logger } from './logger.mjs';
|
|
7
|
+
import { readLessonEntries } from './lesson-sessions.mjs';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Total Recall Session Watcher
|
|
@@ -30,6 +31,12 @@ const HOME = os.homedir();
|
|
|
30
31
|
* of unified SSSS session entries.
|
|
31
32
|
*/
|
|
32
33
|
const SOURCES = [
|
|
34
|
+
{
|
|
35
|
+
name: 'openclaw',
|
|
36
|
+
root: path.join(HOME, '.openclaw', 'agents'),
|
|
37
|
+
filter: filename => filename.endsWith('.jsonl'),
|
|
38
|
+
adapter: file => parseClaudeCode(file).map(entry => ({ ...entry, source: 'openclaw' })),
|
|
39
|
+
},
|
|
33
40
|
{
|
|
34
41
|
name: 'claude-code',
|
|
35
42
|
root: path.join(HOME, '.claude', 'projects'),
|
|
@@ -248,6 +255,15 @@ export function parseClaudeCode(filePath) {
|
|
|
248
255
|
*/
|
|
249
256
|
export function parseCodex(filePath) {
|
|
250
257
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
258
|
+
if (/"type"\s*:\s*"(?:response_item|event_msg)"/.test(raw)) {
|
|
259
|
+
let parentId = null;
|
|
260
|
+
return readLessonEntries(filePath).map(message => {
|
|
261
|
+
const entry = createSessionEntry({ ...message, parentId, source: 'codex',
|
|
262
|
+
type: message.role === 'user' ? 'task' : message.role === 'tool' ? 'tool_call' : 'observation' });
|
|
263
|
+
parentId = entry.id;
|
|
264
|
+
return entry;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
251
267
|
const lines = raw.split('\n').filter(Boolean);
|
|
252
268
|
const entries = [];
|
|
253
269
|
let prevId = null;
|
|
@@ -661,7 +677,9 @@ export function writeSession(entries, sessionsDir, sourceFile) {
|
|
|
661
677
|
// Skip if already ingested and unchanged
|
|
662
678
|
if (fs.existsSync(sessionFile)) {
|
|
663
679
|
try {
|
|
664
|
-
|
|
680
|
+
const previous = fs.readFileSync(sessionFile, 'utf8');
|
|
681
|
+
const semantic = rows => JSON.stringify(rows.map(({ role, type, content, source, cwd }) => ({ role, type, content, source, cwd })));
|
|
682
|
+
if (previous === jsonl || semantic(previous.trim().split('\n').filter(Boolean).map(line => JSON.parse(line))) === semantic(entries)) {
|
|
665
683
|
return null;
|
|
666
684
|
}
|
|
667
685
|
} catch { /* ignore */ }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { discoverPlugins } from './plugin-loader.mjs';
|
|
2
|
+
import { runPluginCommand } from './plugin-runner.mjs';
|
|
3
|
+
|
|
4
|
+
/** Skill engineering belongs to skill-manager. Missing operations fail closed. */
|
|
5
|
+
export async function requestSkillGap(topic, evidence, { projectRoot = process.cwd(), discover = discoverPlugins, run = runPluginCommand } = {}) {
|
|
6
|
+
if (typeof topic !== 'string' || !topic.trim() || typeof evidence !== 'string' || !evidence.trim()) {
|
|
7
|
+
throw new Error('Skill gap requires a topic and an evidence reference');
|
|
8
|
+
}
|
|
9
|
+
const plugin = discover(projectRoot).find(item => item.id === 'skill-manager' && item.valid);
|
|
10
|
+
if (!plugin?.manifest.cli?.subcommands?.some(item => item.name === 'gap')) {
|
|
11
|
+
throw new Error('Skill-manager gap is unavailable. Install an owner version that provides: total-recall skill-manager gap "<topic>" --evidence <ref>');
|
|
12
|
+
}
|
|
13
|
+
const result = await run(plugin, { subcommand: 'gap', args: [topic.trim(), '--evidence', evidence], cwd: projectRoot });
|
|
14
|
+
if (!result.ok || result.truncated) throw new Error(`Skill-manager gap failed: ${result.stderr || result.output || result.exitCode}`);
|
|
15
|
+
return { success: true, output: result.stdout || result.output, executor: 'skill-manager' };
|
|
16
|
+
}
|
|
@@ -12,7 +12,8 @@ import fs from 'node:fs';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import crypto from 'node:crypto';
|
|
14
14
|
import matter from './frontmatter.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
16
|
+
import { createEngine, resolveContainedPath } from '@ssss/cli/engine';
|
|
16
17
|
import { createValidator } from '@ssss/cli/validator';
|
|
17
18
|
import {
|
|
18
19
|
composeRegistryLayers,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
resolvePrimitiveDefinition,
|
|
21
22
|
} from '@ssss/cli/registry';
|
|
22
23
|
import { detectDirectWrites } from '@ssss/cli/guard';
|
|
24
|
+
import { checkRuleLength } from './rule-length.mjs';
|
|
23
25
|
import { validateMemoryNode } from './total-recall-memory-validator.mjs';
|
|
24
26
|
import {
|
|
25
27
|
TOTAL_RECALL_HOST_EXTENSION,
|
|
@@ -66,6 +68,7 @@ const DIRECT_WRITE_APPROVED = [
|
|
|
66
68
|
|
|
67
69
|
let cachedEngine = null;
|
|
68
70
|
let cachedRegistryKey = '';
|
|
71
|
+
const writePolicy = new AsyncLocalStorage();
|
|
69
72
|
|
|
70
73
|
export function getKernelMode() {
|
|
71
74
|
// Default to package-kernel cutover for core types (memory/workflow/low-risk).
|
|
@@ -187,7 +190,27 @@ export function getTotalRecallEngine(options = {}) {
|
|
|
187
190
|
const registrySet = createTotalRecallRegistrySet(options);
|
|
188
191
|
const key = `${[...registrySet.types.keys()].sort().join(',')}|${options.leaseStore || ''}`;
|
|
189
192
|
if (!cachedEngine || cachedRegistryKey !== key || options.forceNew) {
|
|
190
|
-
const
|
|
193
|
+
const baseValidator = createValidator({ registrySet });
|
|
194
|
+
const validator = {
|
|
195
|
+
...baseValidator,
|
|
196
|
+
validateData(data, callOptions) {
|
|
197
|
+
const result = baseValidator.validateData(data, callOptions);
|
|
198
|
+
const context = writePolicy.getStore();
|
|
199
|
+
if (context?.envelope.type !== 'patch') return result;
|
|
200
|
+
// The kernel calls validateData with the merged frontmatter. Check the
|
|
201
|
+
// replacement body against that category and those exception tags.
|
|
202
|
+
// A metadata-only patch keeps the current document body.
|
|
203
|
+
const { envelope, vaultRoot, options } = context;
|
|
204
|
+
const body = envelope.patches.__body__ === undefined
|
|
205
|
+
? matter(fs.readFileSync(resolveContainedPath(vaultRoot, envelope.path), 'utf8')).content
|
|
206
|
+
: String(envelope.patches.__body__);
|
|
207
|
+
const policy = checkRuleLength(data, body, options);
|
|
208
|
+
context.warnings.push(...policy.warnings);
|
|
209
|
+
return { ...result, valid: result.valid && !policy.errors.length,
|
|
210
|
+
errors: [...result.errors, ...policy.errors],
|
|
211
|
+
field_errors: [...result.field_errors, ...policy.errors.map(issue => ({ field: '__body__', issue }))] };
|
|
212
|
+
},
|
|
213
|
+
};
|
|
191
214
|
cachedEngine = createEngine({
|
|
192
215
|
registrySet,
|
|
193
216
|
validator,
|
|
@@ -277,6 +300,9 @@ export function prepareEnvelopeForKernel(envelope, options = {}) {
|
|
|
277
300
|
return { envelope: env, warnings, errors: [`Content parsing error: ${error.message}`] };
|
|
278
301
|
}
|
|
279
302
|
const data = { ...parsed.data };
|
|
303
|
+
const length = checkRuleLength(data, parsed.content, options);
|
|
304
|
+
errors.push(...length.errors);
|
|
305
|
+
warnings.push(...length.warnings);
|
|
280
306
|
|
|
281
307
|
// Package 0.9 universal frontmatter.
|
|
282
308
|
if (!data.title && data.name) data.title = data.name;
|
|
@@ -392,11 +418,13 @@ export async function processViaPackageKernel(envelope, vaultRoot, options = {})
|
|
|
392
418
|
|
|
393
419
|
const eventsDir = path.join(vaultRoot, '.events');
|
|
394
420
|
if (!env.dry_run && !fs.existsSync(eventsDir)) fs.mkdirSync(eventsDir, { recursive: true });
|
|
395
|
-
const
|
|
421
|
+
const context = { envelope: env, vaultRoot, options, warnings: [] };
|
|
422
|
+
const response = await writePolicy.run(context, () => engine.processOperation(env, vaultRoot, {
|
|
396
423
|
...options,
|
|
397
424
|
vaultRoot,
|
|
398
425
|
principal: options.principal || mapTrPrincipal(env, options, vaultRoot),
|
|
399
|
-
});
|
|
426
|
+
}));
|
|
427
|
+
prepared.warnings.push(...context.warnings);
|
|
400
428
|
|
|
401
429
|
if (prepared.warnings.length && response?.validation) {
|
|
402
430
|
response.validation.warnings = [
|
|
@@ -148,7 +148,12 @@ export async function startupHealth({ cwd = process.cwd(), brainDir, brainUrl =
|
|
|
148
148
|
remoteDaemon = { status: body.daemon === 'running' ? 'running' : ['dead', 'stopped', 'not-started'].includes(body.daemon) ? 'stopped' : 'unknown', source: 'remote-health' };
|
|
149
149
|
if (local) {
|
|
150
150
|
const own = identity(serverFile, path.join(packageRoot, 'src', 'server', 'index.mjs'));
|
|
151
|
-
if (own.status !== 'running')
|
|
151
|
+
if (own.status !== 'running') {
|
|
152
|
+
const actualRoot = own.entry?.endsWith(path.join('src', 'server', 'index.mjs'))
|
|
153
|
+
? path.resolve(path.dirname(own.entry), '../..') : null;
|
|
154
|
+
return { status: own.status === 'conflict' ? 'conflict' : 'unknown',
|
|
155
|
+
reason: actualRoot ? `server runs from ${actualRoot}; expected ${packageRoot}; run total-recall server ensure` : 'server-process-unverified' };
|
|
156
|
+
}
|
|
152
157
|
if (!await listener(own.pid, new URL(brainUrl), { run, cwd })) return { status: 'unknown', reason: 'server-listener-unverified' };
|
|
153
158
|
}
|
|
154
159
|
const drift = local && verdict.status === 'ready' ? serverCodeDrift(verdict.version, onDiskVersion()) : null;
|
package/src/core/surface.mjs
CHANGED
|
@@ -496,13 +496,8 @@ function injectDirectives(fileContent, rulesBlock) {
|
|
|
496
496
|
return prependDirectives(authored, rulesBlock);
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (!content.includes('total-recall/SKILL.md')) {
|
|
502
|
-
content = baseline + '\n\n' + content;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return prependDirectives(content, rulesBlock);
|
|
499
|
+
// The managed block carries the start-here instructions; authored text stays as written.
|
|
500
|
+
return prependDirectives(fileContent.trimEnd(), rulesBlock);
|
|
506
501
|
}
|
|
507
502
|
|
|
508
503
|
function prependDirectives(authored, rulesBlock) {
|
|
@@ -516,14 +511,28 @@ function prependDirectives(authored, rulesBlock) {
|
|
|
516
511
|
/**
|
|
517
512
|
* Write or update a platform instruction shim with the pointer and active rules.
|
|
518
513
|
*/
|
|
514
|
+
/**
|
|
515
|
+
* The whole managed part of every client entry file (ON-DEMAND-CONTEXT-01). It is static: no rule
|
|
516
|
+
* bodies, CLI list, plugin table or command list, so a rule or plugin change rewrites no repository.
|
|
517
|
+
* The rules for a task are compiled when the agent acknowledges preflight, and commands are looked
|
|
518
|
+
* up when needed.
|
|
519
|
+
*/
|
|
520
|
+
export const ENTRY_BLOCK = `# Total Recall: start here
|
|
521
|
+
|
|
522
|
+
This repository uses Total Recall. Its rules are not copied into this file: they are compiled for your task when you acknowledge preflight.
|
|
523
|
+
|
|
524
|
+
1. Read \`.agent/skills/total-recall/SKILL.md\`.
|
|
525
|
+
2. Run \`total-recall tr-preflight --ack --task "<task>" --action read,edit,test\` and read everything it prints. Those are the rules for this task. Until it passes, working tools are denied.
|
|
526
|
+
3. Route the task through Metaharness with \`total-recall meta-harness route "<task>" --json\` and follow the route.
|
|
527
|
+
4. Look commands up only when you need them: \`total-recall --help\`, \`total-recall <namespace> --help\`, \`total-recall plugin library\`.`;
|
|
528
|
+
|
|
519
529
|
async function writeShim(shimPath, skillsDir, nodes = [], { vaultDir, derivedDir, force = false } = {}) {
|
|
520
530
|
const shimDir = path.dirname(shimPath);
|
|
521
|
-
const rulesBlock =
|
|
522
|
-
const baseline = '# 🔴 MANDATORY FIRST STEP ON EVERY TURN: TOTAL RECALL FIRST\n1. Run `total-recall --help` (or `node bin/total-recall.mjs --help`) to discover all tools, plugins, and commands.\n2. Read and follow .agent/skills/total-recall/SKILL.md before doing ANYTHING.\n3. Run `total-recall context "<task>"` and `total-recall recall "<topic>" --local` before any decision or action.\nTotal Recall is the mandatory working memory and toolset across all tasks and harnesses. Never skip this.\n';
|
|
531
|
+
const rulesBlock = ENTRY_BLOCK;
|
|
523
532
|
const mdcHeader = shimPath.endsWith('.mdc')
|
|
524
|
-
? '---\ndescription: "Total Recall —
|
|
533
|
+
? '---\ndescription: "Total Recall — start here."\nglobs:\nalwaysApply: true\n---\n\n'
|
|
525
534
|
: '';
|
|
526
|
-
const fullContent = prependDirectives(
|
|
535
|
+
const fullContent = prependDirectives(mdcHeader, rulesBlock);
|
|
527
536
|
|
|
528
537
|
try {
|
|
529
538
|
const normShim = path.normalize(shimPath);
|
|
@@ -772,46 +781,50 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
|
|
|
772
781
|
? mergeGlobalRuleNodes(nodes, getNodes(globalVault))
|
|
773
782
|
: nodes;
|
|
774
783
|
|
|
775
|
-
// ──
|
|
776
|
-
//
|
|
777
|
-
//
|
|
784
|
+
// ── Each output has its own trigger (ON-DEMAND-CONTEXT-01) ──
|
|
785
|
+
// Entry files are static: they change only with the entry block or the client list.
|
|
786
|
+
// Indexes change only with this vault. The CLI index changes only with this brain's
|
|
787
|
+
// plugin catalog. A plugin or global-rule change therefore rewrites no repository's
|
|
788
|
+
// entry files and starts no embeddings pass; rules reach agents at preflight.
|
|
778
789
|
const hashFile = path.join(derivedDir, 'vault-hash.txt');
|
|
779
790
|
const currentHash = computeVaultHash(vaultDir);
|
|
780
|
-
const globalHashFile = path.join(derivedDir, 'global-rules-hash.txt');
|
|
781
|
-
const globalHash = globalVault && fs.existsSync(globalVault) ? computeVaultHash(globalVault) : '';
|
|
782
|
-
// Commands and skills feed the surface too; a change to either must recompile
|
|
783
|
-
// even when no memory node changed.
|
|
784
791
|
const inputsHashFile = path.join(derivedDir, 'surface-inputs-hash.txt');
|
|
792
|
+
const cliHashFile = path.join(derivedDir, 'cli-index-hash.txt');
|
|
785
793
|
const files = instructionFiles(instructionsFile, skillsDir);
|
|
786
794
|
const surfaceDir = path.dirname(instructionsFile);
|
|
787
795
|
const artifactsFile = path.join(derivedDir, 'surface-artifacts.json');
|
|
788
|
-
const inputsHash =
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
};
|
|
804
|
-
}
|
|
796
|
+
const inputsHash = crypto.createHash('sha256').update(ENTRY_BLOCK).update(JSON.stringify(files.map(file => path.relative(surfaceDir, file)))).digest('hex');
|
|
797
|
+
const stored = file => (fs.existsSync(file) ? fs.readFileSync(file, 'utf8').trim() : null);
|
|
798
|
+
const entryCurrent = !force && stored(inputsHashFile) === inputsHash && instructionArtifactsMatch(files, surfaceDir, artifactsFile);
|
|
799
|
+
const vaultCurrent = !force && stored(hashFile) === currentHash;
|
|
800
|
+
const cliCurrent = !force && stored(cliHashFile) === catalog.fingerprint;
|
|
801
|
+
|
|
802
|
+
if (entryCurrent && vaultCurrent && cliCurrent) {
|
|
803
|
+
return {
|
|
804
|
+
nodesProcessed: nodes.length,
|
|
805
|
+
skillsInjected: 0,
|
|
806
|
+
semanticIndexed: 0,
|
|
807
|
+
semanticUnavailable: false,
|
|
808
|
+
skipped: true,
|
|
809
|
+
reason: 'vault-hash-unchanged'
|
|
810
|
+
};
|
|
805
811
|
}
|
|
806
812
|
|
|
807
|
-
// 1. Write pointer and active rules to all instruction shims
|
|
808
|
-
const skillsInjected = await compilePointers(instructionsFile, skillsDir, ruleNodes, { vaultDir, derivedDir, force, files });
|
|
809
|
-
|
|
810
|
-
// 2. Build derived indexes (powers semantic search API)
|
|
811
813
|
if (!fs.existsSync(derivedDir)) {
|
|
812
814
|
fs.mkdirSync(derivedDir, { recursive: true });
|
|
813
815
|
}
|
|
814
816
|
|
|
817
|
+
// 1. Entry files, only when the entry block or client list changed (or a file drifted).
|
|
818
|
+
const skillsInjected = entryCurrent ? 0 : await compilePointers(instructionsFile, skillsDir, ruleNodes, { vaultDir, derivedDir, force, files });
|
|
819
|
+
if (!entryCurrent) {
|
|
820
|
+
atomicWrite(inputsHashFile, inputsHash);
|
|
821
|
+
atomicWrite(artifactsFile, JSON.stringify({ version: 1, files: files.map(file => describeInstruction(file, surfaceDir)) }, null, 2));
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// 2. Derived indexes, only when this vault changed.
|
|
825
|
+
let semanticResult = { indexed: 0, skipped: nodes.length, unavailable: false };
|
|
826
|
+
if (!vaultCurrent) {
|
|
827
|
+
|
|
815
828
|
const graphIndex = nodes.map(n => ({
|
|
816
829
|
slug: n.slug,
|
|
817
830
|
title: n.title,
|
|
@@ -844,7 +857,7 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
|
|
|
844
857
|
// caller could detect any of it.
|
|
845
858
|
// Awaiting is cheap in steady state: buildEmbeddingsIndex skips nodes whose
|
|
846
859
|
// content hash is unchanged, so only genuinely new or edited nodes cost a call.
|
|
847
|
-
|
|
860
|
+
semanticResult = { indexed: 0, skipped: nodes.length, unavailable: true };
|
|
848
861
|
if (!semantic || process.env.TR_EMBEDDINGS_DISABLED === '1') {
|
|
849
862
|
logger.info('surface', 'Semantic embeddings explicitly disabled; local indexes updated.', { derivedDir });
|
|
850
863
|
} else try {
|
|
@@ -869,9 +882,18 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
|
|
|
869
882
|
semanticResult = { indexed: 0, skipped: nodes.length, unavailable: true, error: err.message };
|
|
870
883
|
}
|
|
871
884
|
|
|
872
|
-
//
|
|
873
|
-
let cliIndexResult = { built: 0, skipped: 0, failed: 0 };
|
|
885
|
+
// 4. Generate Obsidian Canvas
|
|
874
886
|
try {
|
|
887
|
+
generateCanvas(nodes, vaultDir);
|
|
888
|
+
} catch { /* non-fatal */ }
|
|
889
|
+
|
|
890
|
+
atomicWrite(hashFile, currentHash);
|
|
891
|
+
writeProjectionManifest(derivedDir, currentHash);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// 3. The CLI semantic index, only when this brain's plugin catalog changed.
|
|
895
|
+
let cliIndexResult = { built: 0, skipped: 0, failed: 0 };
|
|
896
|
+
if (!cliCurrent) try {
|
|
875
897
|
const { buildCliSemanticIndex } = await import('./cli-semantic-index.mjs');
|
|
876
898
|
const projectRoot = skillsDir ? path.dirname(path.dirname(skillsDir)) : process.cwd();
|
|
877
899
|
cliIndexResult = await buildCliSemanticIndex({
|
|
@@ -884,22 +906,11 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
|
|
|
884
906
|
if (cliIndexResult.built > 0) {
|
|
885
907
|
logger.info('surface', `CLI semantic index: ${cliIndexResult.built} indexed, ${cliIndexResult.skipped} skipped.`, { derivedDir });
|
|
886
908
|
}
|
|
909
|
+
atomicWrite(cliHashFile, catalog.fingerprint);
|
|
887
910
|
} catch (err) {
|
|
888
911
|
logger.warn('surface', `CLI semantic index build non-fatal warning: ${err.message}`, { derivedDir });
|
|
889
912
|
}
|
|
890
913
|
|
|
891
|
-
// 4. Generate Obsidian Canvas
|
|
892
|
-
try {
|
|
893
|
-
generateCanvas(nodes, vaultDir);
|
|
894
|
-
} catch { /* non-fatal */ }
|
|
895
|
-
|
|
896
|
-
// 5. Write vault hash + projection manifest
|
|
897
|
-
atomicWrite(hashFile, currentHash);
|
|
898
|
-
atomicWrite(globalHashFile, globalHash);
|
|
899
|
-
atomicWrite(inputsHashFile, inputsHash);
|
|
900
|
-
atomicWrite(artifactsFile, JSON.stringify({ version: 1, files: files.map(file => describeInstruction(file, surfaceDir)) }, null, 2));
|
|
901
|
-
writeProjectionManifest(derivedDir, currentHash);
|
|
902
|
-
|
|
903
914
|
return {
|
|
904
915
|
nodesProcessed: nodes.length,
|
|
905
916
|
skillsInjected,
|