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.
Files changed (212) hide show
  1. package/bin/total-recall.mjs +6 -4
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +13 -8
  4. package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
  5. package/frontend/dist/index.html +1 -1
  6. package/frontend/src/App.tsx +2 -2
  7. package/frontend/src/pages/PluginsPage.tsx +1 -1
  8. package/package.json +1 -1
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  11. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  12. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  14. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  15. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  16. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  17. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
  20. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  21. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  22. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
  25. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  26. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
  27. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  33. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
  34. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  38. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  42. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
  44. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
  45. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
  46. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
  68. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
  69. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
  70. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  71. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
  72. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  73. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  74. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
  75. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
  76. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
  77. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
  78. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
  79. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
  80. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  81. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
  82. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
  84. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
  85. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
  86. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  87. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
  88. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  89. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  90. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
  91. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
  93. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
  96. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
  97. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
  98. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  99. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
  101. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  105. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  106. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  108. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  109. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  110. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
  111. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  112. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
  113. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  114. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
  115. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
  122. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
  123. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  124. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  125. package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
  126. package/src/cli/agent.mjs +1 -1
  127. package/src/cli/command.mjs +41 -2
  128. package/src/cli/dream.mjs +93 -69
  129. package/src/cli/harness.mjs +4 -4
  130. package/src/cli/help.mjs +1 -1
  131. package/src/cli/init.mjs +0 -25
  132. package/src/cli/lessons.mjs +51 -0
  133. package/src/cli/memory-plugin.mjs +1 -1
  134. package/src/cli/plugin/copies.mjs +64 -14
  135. package/src/cli/plugin/create.mjs +6 -7
  136. package/src/cli/plugin/index.mjs +11 -7
  137. package/src/cli/plugin/install.mjs +10 -15
  138. package/src/cli/plugin/remove.mjs +1 -1
  139. package/src/cli/plugin/search.mjs +2 -2
  140. package/src/cli/preflight.mjs +19 -6
  141. package/src/cli/proposals.mjs +1 -1
  142. package/src/cli/recall.mjs +66 -23
  143. package/src/cli/remember.mjs +3 -14
  144. package/src/cli/server.mjs +18 -0
  145. package/src/cli/startup.mjs +1 -0
  146. package/src/cli/task.mjs +9 -2
  147. package/src/cli/update.mjs +6 -2
  148. package/src/core/access-ledger.mjs +78 -0
  149. package/src/core/agent-manager.mjs +2 -2
  150. package/src/core/brain-locate.mjs +15 -0
  151. package/src/core/brain-updates.mjs +31 -11
  152. package/src/core/claude-hooks.mjs +3 -1
  153. package/src/core/cleanup-task.mjs +15 -0
  154. package/src/core/cli-commands.mjs +2 -0
  155. package/src/core/cli-maintenance.mjs +4 -1
  156. package/src/core/cli-semantic-index.mjs +23 -3
  157. package/src/core/context-compiler.mjs +20 -0
  158. package/src/core/daemon-loop.mjs +39 -62
  159. package/src/core/default-plugins.mjs +1 -1
  160. package/src/core/dream.mjs +33 -105
  161. package/src/core/ensure-dependencies.mjs +148 -0
  162. package/src/core/fast-recall.mjs +2 -2
  163. package/src/core/field-evaluation.mjs +23 -0
  164. package/src/core/gate-mode.mjs +1 -1
  165. package/src/core/harness-hooks.mjs +14 -2
  166. package/src/core/lesson-hooks.mjs +74 -0
  167. package/src/core/lesson-memory.mjs +24 -0
  168. package/src/core/lesson-sessions.mjs +52 -0
  169. package/src/core/lessons.mjs +96 -0
  170. package/src/core/meta-harness.mjs +2 -2
  171. package/src/core/optimizer.mjs +66 -165
  172. package/src/core/plugin-contracts/card.mjs +3 -0
  173. package/src/core/plugin-loader.mjs +12 -3
  174. package/src/core/plugin-runtime.mjs +40 -1
  175. package/src/core/plugin-store.mjs +35 -23
  176. package/src/core/post-mortem.mjs +52 -36
  177. package/src/core/preflight-gate.mjs +23 -2
  178. package/src/core/proposal-applier.mjs +31 -2
  179. package/src/core/repo-sync.mjs +4 -6
  180. package/src/core/required-plugins.mjs +26 -4
  181. package/src/core/rule-length.mjs +23 -0
  182. package/src/core/runtime.mjs +1 -1
  183. package/src/core/scheduler.mjs +35 -43
  184. package/src/core/schema.mjs +1 -2
  185. package/src/core/search.mjs +4 -3
  186. package/src/core/server-service.mjs +148 -0
  187. package/src/core/session-watcher.mjs +19 -1
  188. package/src/core/skill-gap.mjs +16 -0
  189. package/src/core/ssss-kernel-bridge.mjs +32 -4
  190. package/src/core/startup-health.mjs +6 -1
  191. package/src/core/surface.mjs +65 -54
  192. package/src/core/task-executors.mjs +54 -106
  193. package/src/core/validated-write.mjs +1 -0
  194. package/src/core/vector-field.mjs +134 -192
  195. package/src/server/invoke-core-child.mjs +24 -0
  196. package/src/server/invoke-core-operations.mjs +102 -0
  197. package/src/server/route-manifest.json +0 -56
  198. package/src/server/routes/brains.mjs +0 -18
  199. package/src/server/routes/context.mjs +0 -41
  200. package/src/server/routes/invoke.mjs +26 -1
  201. package/src/server/routes/ssss.mjs +0 -169
  202. package/src/server/routes/system.mjs +0 -15
  203. package/src/server/routes/tasks.mjs +0 -26
  204. package/src/server/routes/vault.mjs +2 -23
  205. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  206. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  207. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  208. package/src/core/append-log.mjs +0 -131
  209. package/src/core/plugin-tasks.mjs +0 -173
  210. package/templates/onboarding-interview.md +0 -82
  211. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
  212. /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
- if (fs.readFileSync(sessionFile, 'utf8') === jsonl) {
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 { createEngine } from '@ssss/cli/engine';
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 validator = createValidator({ registrySet });
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 response = await engine.processOperation(env, vaultRoot, {
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') return { status: own.status === 'conflict' ? 'conflict' : 'unknown', reason: 'server-process-unverified' };
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;
@@ -496,13 +496,8 @@ function injectDirectives(fileContent, rulesBlock) {
496
496
  return prependDirectives(authored, rulesBlock);
497
497
  }
498
498
 
499
- let content = fileContent.trimEnd();
500
- 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.';
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 = await buildRulesBlock(skillsDir, nodes, { vaultDir, derivedDir, bootstrap: true });
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 — Auto-generated behavioral memory surface."\nglobs:\nalwaysApply: true\n---\n\n'
533
+ ? '---\ndescription: "Total Recall — start here."\nglobs:\nalwaysApply: true\n---\n\n'
525
534
  : '';
526
- const fullContent = prependDirectives(`${mdcHeader}${baseline}`, rulesBlock);
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
- // ── Incremental compilation: vault content hash check ──
776
- // The global rules hash lives in its own file: vault-hash.txt is served by
777
- // /api/vault/hash and must keep describing this vault alone.
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 = sourceHash(SURFACE_SOURCE) + catalog.fingerprint + JSON.stringify(files) + surfaceInputsHash({ commandDirs: commandDirsFor(skillsDir), skillsDir });
789
-
790
- if (!force && fs.existsSync(hashFile)) {
791
- const storedHash = fs.readFileSync(hashFile, 'utf8').trim();
792
- const storedGlobal = fs.existsSync(globalHashFile) ? fs.readFileSync(globalHashFile, 'utf8').trim() : '';
793
- const storedInputs = fs.existsSync(inputsHashFile) ? fs.readFileSync(inputsHashFile, 'utf8').trim() : '';
794
- if (storedHash === currentHash && storedGlobal === globalHash && storedInputs === inputsHash &&
795
- instructionArtifactsMatch(files, surfaceDir, artifactsFile)) {
796
- return {
797
- nodesProcessed: nodes.length,
798
- skillsInjected: 0,
799
- semanticIndexed: 0,
800
- semanticUnavailable: false,
801
- skipped: true,
802
- reason: 'vault-hash-unchanged'
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
- let semanticResult = { indexed: 0, skipped: nodes.length, unavailable: true };
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
- // 3b. Build or update the CLI semantic index.
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,