total-recall-brain 3.41.4 → 3.41.6

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 (147) hide show
  1. package/bin/total-recall.mjs +10 -6
  2. package/docs/reference/cli-reference.md +18 -0
  3. package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
  4. package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/package.json +11 -0
  7. package/frontend/src/App.tsx +47 -122
  8. package/frontend/src/components/plugins/plugin-frame.ts +1 -0
  9. package/frontend/src/index.css +152 -86
  10. package/frontend/src/pages/HelpPage.tsx +13 -10
  11. package/frontend/src/pages/MemoryPage.tsx +28 -30
  12. package/frontend/src/pages/NetworkPage.tsx +3 -3
  13. package/frontend/src/pages/PluginsPage.tsx +6 -5
  14. package/frontend/src/pages/RulesPage.tsx +16 -12
  15. package/package.json +4 -3
  16. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
  17. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +4 -1
  18. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
  19. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +4 -1
  20. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
  21. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +4 -1
  22. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
  23. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +4 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3127 -49
  25. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -2
  26. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
  27. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
  28. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
  29. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
  30. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +5 -2
  31. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +40 -14
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +304 -46
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +5 -2
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +33 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
  71. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
  76. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
  79. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3097 -81
  83. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +4 -1
  84. package/src/cli/agent.mjs +12 -1
  85. package/src/cli/compact.mjs +108 -0
  86. package/src/cli/connect.mjs +56 -1
  87. package/src/cli/context.mjs +55 -23
  88. package/src/cli/doctor.mjs +5 -2
  89. package/src/cli/forget.mjs +2 -0
  90. package/src/cli/harness.mjs +62 -50
  91. package/src/cli/memory-entry.mjs +2 -2
  92. package/src/cli/preflight.mjs +152 -16
  93. package/src/cli/release.mjs +386 -0
  94. package/src/cli/remember.mjs +19 -1
  95. package/src/cli/rules.mjs +54 -12
  96. package/src/cli/startup.mjs +1 -1
  97. package/src/cli/status.mjs +12 -2
  98. package/src/core/claude-hooks.mjs +41 -3
  99. package/src/core/cli-commands.mjs +2 -0
  100. package/src/core/cli-semantic-index.mjs +3 -2
  101. package/src/core/conflict-detector.mjs +23 -12
  102. package/src/core/context-compactor.mjs +145 -0
  103. package/src/core/context-policy.mjs +20 -0
  104. package/src/core/duplicate-guard.mjs +350 -0
  105. package/src/core/meta-harness.mjs +31 -11
  106. package/src/core/preflight-gate.mjs +2 -2
  107. package/src/core/preflight-receipt.mjs +29 -1
  108. package/src/core/scaffold-plugins.mjs +7 -0
  109. package/src/core/stdio-flush.mjs +23 -0
  110. package/src/core/surface.mjs +33 -10
  111. package/src/server/invoke-durability.mjs +39 -0
  112. package/src/server/invoke-write-child.mjs +25 -0
  113. package/src/server/routes/invoke.mjs +7 -0
  114. package/src/server/routes/plugins.mjs +2 -2
  115. package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
  116. package/frontend/dist/assets/index-CRzjQLu7.js +0 -5631
  117. package/frontend/src/components/DaemonLogsTab.tsx +0 -54
  118. package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
  119. package/frontend/src/components/TaskQueueTab.tsx +0 -193
  120. package/frontend/src/components/VoiceInput.tsx +0 -112
  121. package/frontend/src/pages/ChatPage.tsx +0 -1059
  122. package/frontend/src/pages/CollabPage.tsx +0 -470
  123. package/frontend/src/pages/DesignDocsPage.tsx +0 -621
  124. package/frontend/src/pages/FilesPage.tsx +0 -393
  125. package/frontend/src/pages/HealthPage.tsx +0 -359
  126. package/frontend/src/pages/InboxPage.tsx +0 -83
  127. package/frontend/src/pages/InstructionsPage.tsx +0 -583
  128. package/frontend/src/pages/IntegrationsPage.tsx +0 -761
  129. package/frontend/src/pages/MeshPage.css +0 -58
  130. package/frontend/src/pages/MeshPage.tsx +0 -1219
  131. package/frontend/src/pages/OkfPage.tsx +0 -535
  132. package/frontend/src/pages/SandboxPage.tsx +0 -113
  133. package/frontend/src/pages/SkillsPage.tsx +0 -1994
  134. package/frontend/src/pages/TasksPage.tsx +0 -427
  135. package/frontend/src/pages/UsagePage.tsx +0 -702
  136. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
  137. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
  138. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
  139. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
  140. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
  141. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
  142. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
  143. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
  144. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
  145. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
  146. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
  147. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
@@ -24,6 +24,9 @@
24
24
  "access": "public"
25
25
  },
26
26
  "dependencies": {
27
- "@gregiteen/ssss-cli": "0.14.3"
27
+ "@gregiteen/ssss-cli": "0.14.4"
28
+ },
29
+ "overrides": {
30
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.4"
28
31
  }
29
32
  }
package/src/cli/agent.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { listAgents, spawnAgent, killAgent, getAgentLogs } from '../core/agent-manager.mjs';
4
4
  import { detectHarnesses } from '../core/meta-harness.mjs';
5
+ import { checkReceipt, findRoot, ACK_COMMAND } from '../core/preflight-receipt.mjs';
5
6
 
6
7
  function printHelp() {
7
8
  console.log(`
@@ -33,7 +34,7 @@ Auth:
33
34
  else from the Total Recall secret store, so SSH/launchd sessions without a login keychain work.
34
35
 
35
36
  Available Harnesses:
36
- agy, claude, codex, gemini
37
+ agy, claude, codex, dsh, hermes, openclaw
37
38
 
38
39
  Examples:
39
40
  npx total-recall agent list
@@ -144,6 +145,16 @@ export async function run(argv) {
144
145
  process.exit(1);
145
146
  }
146
147
 
148
+ if (!targetNode) {
149
+ const root = findRoot(cwd || process.cwd());
150
+ const receipt = checkReceipt(root);
151
+ if (!receipt.ok) {
152
+ console.error(`\n❌ Agent spawn refused by TR-PREFLIGHT gate: ${receipt.problems[0] || 'check failed'} (directory: ${root}).`);
153
+ console.error(` Read the complete context capsule, then run: ${ACK_COMMAND}\n`);
154
+ process.exit(1);
155
+ }
156
+ }
157
+
147
158
  const nodeStr = targetNode ? ` on mesh node \x1b[1;33m${targetNode}\x1b[0m` : '';
148
159
  if (!isJson) console.log(`\n🚀 Spawning agent on harness: \x1b[1;36m${harness}\x1b[0m${nodeStr}...`);
149
160
  try {
@@ -0,0 +1,108 @@
1
+ import { parseLayerFlag } from './agent-dir.mjs';
2
+ import { loadContextNodes } from './context.mjs';
3
+ import { curatedRules, selectRules } from '../core/context-policy.mjs';
4
+ import { compactRules } from '../core/context-compactor.mjs';
5
+ import { cosineSimilarity, getEmbedding, loadEmbeddingsIndex } from '../core/embeddings.mjs';
6
+ import { legacyRuleContributions } from '../core/surface.mjs';
7
+
8
+ const DEFAULT_BUDGET = 4000;
9
+ const EMBED_TIMEOUT_MS = 8000;
10
+
11
+ const USAGE = `Usage: total-recall compact "task" [--budget 4000] [--action edit,test] [--expand slug,slug] [--format text|json] [--project|--global]
12
+ Fits the applicable rules into --budget estimated tokens, ranked against the task. Rules keep their own words: full text, a leading excerpt, or a slug under Referenced. Read any of them in full with --expand. The output is partial by design; \`total-recall context\` prints the complete capsule.`;
13
+
14
+ function parseArgs(args) {
15
+ const options = { budget: DEFAULT_BUDGET, actions: [], expand: [], format: 'text' };
16
+ for (let i = 1; i < args.length; i++) {
17
+ const flag = args[i];
18
+ const value = args[++i];
19
+ if (flag === '--budget') options.budget = Number(value);
20
+ else if (flag === '--action') options.actions = value?.split(',').filter(Boolean) || [];
21
+ else if (flag === '--expand') options.expand = value?.split(',').filter(Boolean) || [];
22
+ else if (flag === '--format' && ['json', 'text'].includes(value)) options.format = value;
23
+ else throw new Error(`Unknown compact option: ${flag}`);
24
+ }
25
+ if (!Number.isFinite(options.budget) || options.budget < 1) throw new Error('--budget must be a positive number of tokens');
26
+ return options;
27
+ }
28
+
29
+ function loadVectors(derivedDirs) {
30
+ const vectors = {};
31
+ for (const dir of derivedDirs) {
32
+ try {
33
+ for (const [slug, entry] of Object.entries(loadEmbeddingsIndex(dir))) vectors[slug] ??= entry.embedding;
34
+ } catch { /* a brain without an index simply contributes no vectors */ }
35
+ }
36
+ return vectors;
37
+ }
38
+
39
+ async function embedTask(task) {
40
+ let timer;
41
+ const timeout = new Promise(resolve => { timer = setTimeout(() => resolve(null), EMBED_TIMEOUT_MS); });
42
+ try {
43
+ return await Promise.race([getEmbedding(task), timeout]);
44
+ } catch {
45
+ return null;
46
+ } finally {
47
+ clearTimeout(timer);
48
+ }
49
+ }
50
+
51
+ function lexicalScore(terms, text) {
52
+ if (!terms.length) return 0;
53
+ const haystack = text.toLowerCase();
54
+ return terms.filter(term => haystack.includes(term)).length / terms.length;
55
+ }
56
+
57
+ /** Rank the applicable rules against the task and fit them into `budget`. Shared by `compact` and `context`. */
58
+ export async function compactNodes({ task, actions = [], budget, expand = [], complete = false, rank = true, nodes, skillsDir, derivedDirs, header }) {
59
+ const rules = selectRules(curatedRules(nodes), { actions, projectRoot: process.cwd() });
60
+ const vectors = loadVectors(derivedDirs);
61
+ const taskVector = rank ? await embedTask(task) : null;
62
+ const terms = String(task).toLowerCase().match(/[\p{L}\p{N}_-]{3,}/gu) || [];
63
+ const semantic = Boolean(taskVector);
64
+
65
+ const score = (slug, text) => semantic && vectors[slug] ? cosineSimilarity(taskVector, vectors[slug]) : lexicalScore(terms, text);
66
+ const items = rules.map(node => {
67
+ const body = (node._directive || node.body || node.content || '').trim();
68
+ return {
69
+ slug: node.slug, body, priority: node.priority, importance: node.importance,
70
+ text: node._directive ? `[${node.slug}] ${node._directive}` : `## ${node.title || node.slug} [${node.slug}]\n\n${body}`,
71
+ vector: vectors[node.slug], score: score(node.slug, `${node.title || ''} ${body}`),
72
+ };
73
+ });
74
+ for (const { id, text } of legacyRuleContributions(skillsDir)) {
75
+ items.push({ slug: id, body: text, text, score: lexicalScore(terms, text) });
76
+ }
77
+
78
+ const ranking = semantic ? 'semantic' : 'lexical';
79
+ return { ranking, ...compactRules(items, { budget, expand, complete, header: (counts, tokens) => header(counts, tokens, ranking) }) };
80
+ }
81
+
82
+ export default async function compact(args = []) {
83
+ if (!args.length || args.includes('--help') || args.includes('-h')) {
84
+ console.log(USAGE);
85
+ return;
86
+ }
87
+ const { layer, remainingArgs } = parseLayerFlag(args);
88
+ const task = remainingArgs[0];
89
+ const { budget, actions, expand, format } = parseArgs(remainingArgs);
90
+ const { nodes, skillsDir, derivedDirs } = loadContextNodes(layer);
91
+ const header = (counts, tokens, ranking) => [
92
+ `compacted:true tokens:${tokens} budget:${budget} rules:${counts.rules} full:${counts.full} summarized:${counts.summarized} referenced:${counts.referenced} duplicate:${counts.duplicate} omitted:${counts.omitted} ranking:${ranking}`,
93
+ 'PARTIAL: read any rule in full with --expand <slug>; total-recall context prints the complete capsule.',
94
+ ].join('\n');
95
+
96
+ let result;
97
+ try {
98
+ result = await compactNodes({ task, actions, budget, expand, nodes, skillsDir, derivedDirs, header });
99
+ } catch (error) {
100
+ console.log(error.requiredBudget ? `compacted:false reason:budget-too-small budget:${budget} required_budget:${error.requiredBudget}` : `compacted:false reason:${error.message}`);
101
+ process.exitCode = 2;
102
+ return;
103
+ }
104
+ const { context, counts, tokens, tiers, ranking } = result;
105
+ console.log(format === 'json'
106
+ ? JSON.stringify({ compacted: true, ranking, budget, tokens, counts, tiers, context })
107
+ : context);
108
+ }
@@ -14,18 +14,24 @@ export const CLIENTS = {
14
14
  vscode: {
15
15
  label: 'VS Code Copilot',
16
16
  mode: 'file',
17
+ enforcement: 'advisory',
18
+ enforcementReason: 'No tool hook mechanism; rules provided via copilot-instructions.md',
17
19
  target: path.join('.github', 'copilot-instructions.md'),
18
20
  render: instructions => `<!-- Total Recall — auto-generated by \`npx total-recall connect vscode\` -->\n<!-- Do not edit; re-run connect to refresh -->\n\n${instructions}`
19
21
  },
20
22
  pi: {
21
23
  label: 'Pi Coding Agent',
22
24
  mode: 'global-symlink',
25
+ enforcement: 'enforced (unverified live)',
26
+ enforcementReason: 'tool_call extension blocks unreceipted tool calls with exit 2',
23
27
  globalTarget: path.join(os.homedir(), '.pi', 'agent', 'AGENTS.md'),
24
28
  localTarget: 'AGENTS.md',
25
29
  },
26
30
  hermes: {
27
31
  label: 'Hermes Agent',
28
32
  mode: 'symlink',
33
+ enforcement: 'enforced',
34
+ enforcementReason: 'pre_tool_call hook denies unreceipted tool calls with exit 2',
29
35
  // Hermes loads the AGENTS.md chain (git root to cwd) as context files.
30
36
  // ~/.hermes/memories/MEMORY.md is the agent's own notes store, read only
31
37
  // from HERMES_HOME; Total Recall never writes it.
@@ -35,6 +41,8 @@ export const CLIENTS = {
35
41
  'hermes-agent': {
36
42
  label: 'Hermes Agent',
37
43
  mode: 'symlink',
44
+ enforcement: 'enforced',
45
+ enforcementReason: 'pre_tool_call hook denies unreceipted tool calls with exit 2',
38
46
  // Hermes loads the AGENTS.md chain (git root to cwd) as context files.
39
47
  // ~/.hermes/memories/MEMORY.md is the agent's own notes store, read only
40
48
  // from HERMES_HOME; Total Recall never writes it.
@@ -44,6 +52,8 @@ export const CLIENTS = {
44
52
  dsh: {
45
53
  label: 'DeepSeek Harness (dsh)',
46
54
  mode: 'dsh',
55
+ enforcement: 'enforced',
56
+ enforcementReason: 'PreToolUse hook denies unreceipted tool calls with exit 2',
47
57
  globalMemoryPath: path.join(os.homedir(), '.dsh', 'memory', 'MEMORY.md'),
48
58
  globalUserPath: path.join(os.homedir(), '.dsh', 'memory', 'user.md'),
49
59
  localTarget: 'AGENTS.md',
@@ -60,6 +70,8 @@ export const CLIENTS = {
60
70
  'deepseek-harness': {
61
71
  label: 'DeepSeek Harness',
62
72
  mode: 'dsh',
73
+ enforcement: 'enforced',
74
+ enforcementReason: 'PreToolUse hook denies unreceipted tool calls with exit 2',
63
75
  globalMemoryPath: path.join(os.homedir(), '.dsh', 'memory', 'MEMORY.md'),
64
76
  globalUserPath: path.join(os.homedir(), '.dsh', 'memory', 'user.md'),
65
77
  localTarget: 'AGENTS.md',
@@ -69,12 +81,16 @@ export const CLIENTS = {
69
81
  openclaw: {
70
82
  label: 'OpenClaw',
71
83
  mode: 'openclaw',
84
+ enforcement: 'enforced',
85
+ enforcementReason: 'before_tool_call hook denies unreceipted tool calls with exit 2',
72
86
  memoryTarget: 'MEMORY.md',
73
87
  agentsTarget: 'AGENTS.md',
74
88
  },
75
89
  cursor: {
76
90
  label: 'Cursor',
77
91
  mode: 'file',
92
+ enforcement: 'enforced (unverified live)',
93
+ enforcementReason: 'hooks.json failClosed PreToolUse hook denies unreceipted tool calls',
78
94
  target: path.join('.cursor', 'rules', 'total-recall.mdc'),
79
95
  render: instructions => [
80
96
  '---',
@@ -89,6 +105,8 @@ export const CLIENTS = {
89
105
  cline: {
90
106
  label: 'Cline',
91
107
  mode: 'file',
108
+ enforcement: 'enforced (unverified live)',
109
+ enforcementReason: 'beforeTool hook cancels unreceipted tool calls',
92
110
  // Cline uses a .clinerules/ directory (primary); plain markdown, no frontmatter.
93
111
  target: path.join('.clinerules', 'total-recall.md'),
94
112
  render: instructions => instructions,
@@ -98,6 +116,8 @@ export const CLIENTS = {
98
116
  'claude-code': {
99
117
  label: 'Claude Code',
100
118
  mode: 'symlink',
119
+ enforcement: 'enforced',
120
+ enforcementReason: 'PreToolUse hook denies unreceipted tool calls with exit 2',
101
121
  target: 'CLAUDE.md',
102
122
  writeSlashCommands: true,
103
123
  installHooks: true,
@@ -108,6 +128,8 @@ export const CLIENTS = {
108
128
  codex: {
109
129
  label: 'Codex',
110
130
  mode: 'symlink',
131
+ enforcement: 'enforced',
132
+ enforcementReason: 'hooks.json PreToolUse hook denies unreceipted tool calls with exit 2',
111
133
  target: 'AGENTS.md',
112
134
  // Codex discovers repository skills in <project>/.agents/skills.
113
135
  skillsProjection: { scope: 'project', dir: path.join('.agents', 'skills') }
@@ -115,6 +137,8 @@ export const CLIENTS = {
115
137
  grok: {
116
138
  label: 'Grok Build (xAI)',
117
139
  mode: 'symlink',
140
+ enforcement: 'enforced (unverified live)',
141
+ enforcementReason: 'hooks/ PreToolUse hook denies unreceipted tool calls with exit 2',
118
142
  target: 'AGENTS.md',
119
143
  // Grok discovers repository skills in <project>/.agents/skills.
120
144
  skillsProjection: { scope: 'project', dir: path.join('.agents', 'skills') }
@@ -122,34 +146,46 @@ export const CLIENTS = {
122
146
  muse: {
123
147
  label: 'Muse Code',
124
148
  mode: 'symlink',
149
+ enforcement: 'advisory',
150
+ enforcementReason: 'No tool hook mechanism; rules provided via context files',
125
151
  target: 'AGENTS.md'
126
152
  },
127
153
  replit: {
128
154
  label: 'Replit Agent',
129
155
  mode: 'file',
156
+ enforcement: 'advisory',
157
+ enforcementReason: 'No tool hook mechanism; rules provided via context files',
130
158
  target: 'replit.md',
131
159
  render: instructions => `<!-- Total Recall — auto-generated by \`npx total-recall connect replit\` -->\n<!-- Do not edit; re-run connect to refresh -->\n\n${instructions}`
132
160
  },
133
161
  lovable: {
134
162
  label: 'Lovable',
135
163
  mode: 'symlink',
164
+ enforcement: 'advisory',
165
+ enforcementReason: 'No tool hook mechanism; rules provided via context files',
136
166
  target: 'AGENTS.md'
137
167
  },
138
168
  windsurf: {
139
169
  label: 'Windsurf / Devin Desktop',
140
170
  mode: 'file',
171
+ enforcement: 'enforced (unverified live)',
172
+ enforcementReason: 'hooks.json PreToolUse hook denies unreceipted tool calls with exit 2',
141
173
  target: path.join('.windsurf', 'rules', 'total-recall.md'),
142
174
  render: instructions => instructions
143
175
  },
144
176
  devin: {
145
177
  label: 'Devin Desktop',
146
178
  mode: 'file',
179
+ enforcement: 'enforced (unverified live)',
180
+ enforcementReason: 'hooks.json PreToolUse hook denies unreceipted tool calls with exit 2',
147
181
  target: path.join('.devin', 'rules', 'total-recall.md'),
148
182
  render: instructions => instructions
149
183
  },
150
184
  antigravity: {
151
185
  label: 'Antigravity',
152
186
  mode: 'symlink',
187
+ enforcement: 'enforced',
188
+ enforcementReason: 'PreToolUse hook denies unreceipted tool calls with exit 2',
153
189
  target: '.agents/rules/AGENTS.md',
154
190
  // Antigravity reads workspace skills from .agents/skills and exposes native /name invocation
155
191
  skillsProjection: { scope: 'project', dir: path.join('.agents', 'skills') }
@@ -157,6 +193,8 @@ export const CLIENTS = {
157
193
  aider: {
158
194
  label: 'Aider',
159
195
  mode: 'file',
196
+ enforcement: 'dispatch-only',
197
+ enforcementReason: 'No pre-tool hooks; preflight enforced when invoked via Total Recall dispatch',
160
198
  target: '.aider.rules.md',
161
199
  render: instructions => instructions,
162
200
  after: [
@@ -169,17 +207,23 @@ export const CLIENTS = {
169
207
  'http-api': {
170
208
  label: 'HTTP host app',
171
209
  mode: 'api',
210
+ enforcement: 'advisory',
211
+ enforcementReason: 'HTTP API integration; caller enforces preflight',
172
212
  // Any HTTP host: OpenAI-compatible API + PAT. Not tied to a product repo.
173
213
  },
174
214
  obsidian: {
175
215
  label: 'Obsidian',
176
216
  mode: 'vault',
217
+ enforcement: 'advisory',
218
+ enforcementReason: 'Direct vault projection; no agent execution harness',
177
219
  // Symlinks memory-vault into the Obsidian vault as "Total Recall/"
178
220
  folderName: 'Total Recall'
179
221
  },
180
222
  generic: {
181
223
  label: 'Generic OpenAI-Compatible Client',
182
- mode: 'api'
224
+ mode: 'api',
225
+ enforcement: 'advisory',
226
+ enforcementReason: 'Generic client; no native pre-tool enforcement'
183
227
  }
184
228
  };
185
229
 
@@ -358,6 +402,8 @@ function registerClient(brainDir, clientName, preset, projectionPath) {
358
402
  registry.clients[clientName] = {
359
403
  label: preset.label,
360
404
  mode: preset.mode,
405
+ enforcement: preset.enforcement || 'advisory',
406
+ enforcementReason: preset.enforcementReason || null,
361
407
  projectionPath: projectionPath || null,
362
408
  connectedAt: new Date().toISOString()
363
409
  };
@@ -755,12 +801,18 @@ export default async function connect(args) {
755
801
  client: opts.client,
756
802
  label: preset.label,
757
803
  mode: preset.mode,
804
+ enforcement: preset.enforcement || 'advisory',
805
+ enforcement_reason: preset.enforcementReason || null,
758
806
  brain_config_path: brainConfigPath,
759
807
  api: details,
760
808
  projection: null,
761
809
  notes: []
762
810
  };
763
811
 
812
+ if (preset.enforcement === 'advisory' || preset.enforcement === 'dispatch-only') {
813
+ result.notes.push(` ℹ️ Enforcement tier: ${preset.enforcement} — ${preset.enforcementReason}`);
814
+ }
815
+
764
816
  if (preset.mode === 'file') {
765
817
  const instructions = readInstructions(cwd, agentDir);
766
818
  if (!instructions) {
@@ -949,6 +1001,9 @@ export default async function connect(args) {
949
1001
  }
950
1002
 
951
1003
  console.log(`\n Total Recall connected: ${preset.label}`);
1004
+ if (preset.enforcement) {
1005
+ console.log(` Enforcement: ${preset.enforcement} (${preset.enforcementReason || ''})`);
1006
+ }
952
1007
  if (result.projection) {
953
1008
  const action = result.projection.action;
954
1009
  const target = result.projection.targetPath;
@@ -2,15 +2,64 @@ import path from 'node:path';
2
2
  import { getBothBrains, parseLayerFlag } from './agent-dir.mjs';
3
3
  import { getNodes } from '../core/vault-cache.mjs';
4
4
  import { mergeGlobalRuleNodes, legacyRuleContributions } from '../core/surface.mjs';
5
- import { compileContext, capsuleResponse, renderCapsule } from '../core/context-compiler.mjs';
5
+ import { compileContext, capsuleResponse, renderCapsule, PREFLIGHT_NOTICE } from '../core/context-compiler.mjs';
6
6
  import { isBrainEnabled } from '../core/brain-registry.mjs';
7
7
  import { surfaceInputsHash } from '../core/command-surface.mjs';
8
8
  import { readConfigValue } from './config.mjs';
9
9
  import { recordCapsule } from '../core/context-telemetry.mjs';
10
10
 
11
+ /** About 28 KB: harnesses save output over about 30 KB to a file instead of showing it, which agents then cannot read (audit C-005, F-019). */
12
+ export const CAPSULE_LIMIT = 7000;
13
+ const JSON_ESCAPE_SHARE = 0.97;
14
+ const JSON_WRAPPER_TOKENS = 128;
15
+
16
+ /** The merged rule nodes a capsule draws from, for the selected layer. */
17
+ export function loadContextNodes(layer) {
18
+ const brains = getBothBrains();
19
+ const own = layer !== 'global' && brains.project && isBrainEnabled(brains.project.brainDir) ? brains.project.brainDir : null;
20
+ const global = layer !== 'project' && brains.global && isBrainEnabled(brains.global.brainDir) ? brains.global.brainDir : null;
21
+ if (!own && !global) throw new Error('No enabled brain in selected scope');
22
+ const nodes = own ? mergeGlobalRuleNodes(getNodes(path.join(own, 'memory-vault')), global ? getNodes(path.join(global, 'memory-vault')) : []) :
23
+ getNodes(path.join(global, 'memory-vault')).map(n => ({ ...n, _layer: 'global' }));
24
+ return { nodes, skillsDir: path.dirname(own || global),
25
+ derivedDirs: [own, global].filter(Boolean).map(dir => path.join(dir, 'memory-derived')) };
26
+ }
27
+
28
+ /** The first lines of an automatically compacted capsule. `rules verify` measures with it too, so both agree on what fits. */
29
+ export const compactedHeader = (version, budget) => (counts, tokens, ranking) => [
30
+ `ready:true compacted:true tokens:${tokens} budget:${budget} version:${version} rules:${counts.rules} full:${counts.full} summarized:${counts.summarized} referenced:${counts.referenced} duplicate:${counts.duplicate} ranking:${ranking}`,
31
+ PREFLIGHT_NOTICE,
32
+ `COMPACTED: every rule below still applies. Read a referenced rule with: total-recall compact "<task>" --expand <slug>`,
33
+ ].join('\n');
34
+
35
+ /** Compact the over-budget capsule, or explain the budget it needs. `fullRequired` is what the uncompacted capsule would take. */
36
+ async function printCompacted({ query, actions, budget, format, version, fullRequired, nodes, skillsDir, derivedDirs }) {
37
+ const { compactNodes } = await import('./compact.mjs');
38
+ // JSON escaping and its wrapper count against the budget too.
39
+ const target = format === 'json' ? Math.floor(budget * JSON_ESCAPE_SHARE) - JSON_WRAPPER_TOKENS : budget;
40
+ const header = compactedHeader(version, budget);
41
+ let result;
42
+ try {
43
+ result = await compactNodes({ task: query, actions, budget: Math.max(target, 1), complete: true, nodes, skillsDir, derivedDirs, header });
44
+ } catch (error) {
45
+ if (!error.requiredBudget) throw error;
46
+ const floor = format === 'json' ? Math.ceil((error.requiredBudget + JSON_WRAPPER_TOKENS) / JSON_ESCAPE_SHARE) : error.requiredBudget;
47
+ const requiredBudget = Math.min(fullRequired, floor);
48
+ console.log(format === 'json'
49
+ ? JSON.stringify({ ready: false, reason: 'budget-overflow', context: '', stats: { version, budget, required_budget: requiredBudget, overage: requiredBudget - budget, rules: error.ruleCount, token_measurement: 'estimated_chars_divided_by_four' } })
50
+ : `ready:false reason:budget-overflow budget:${budget} required_budget:${requiredBudget} overage:${requiredBudget - budget} rules:${error.ruleCount} version:${version}\nThis budget cannot hold all ${error.ruleCount} applicable rules even compacted; \`total-recall rules audit\` lists them by size. Retry with --budget ${requiredBudget}; do not act until ready:true.`);
51
+ process.exitCode = 2;
52
+ return;
53
+ }
54
+ console.log(format === 'json'
55
+ ? JSON.stringify({ ready: true, compacted: true, ranking: result.ranking, context: result.context, counts: result.counts,
56
+ stats: { version, budget, total_tokens: result.tokens, token_measurement: 'estimated_chars_divided_by_four' } })
57
+ : result.context);
58
+ }
59
+
11
60
  export default async function context(args = []) {
12
61
  if (!args.length || args.includes('--help')) {
13
- console.log('Usage: total-recall context "task" [--action edit,test,publish] [--budget 4000] [--format text|json] [--knowledge] [--debug] [--project|--global]\nLocal rules only by default. Unknown applicability is required. Overflow exits 2; do not act until the complete output fits. --knowledge adds supporting documents; --debug exposes inventories within the same budget. Refresh on task/action/project or policy changes.');
62
+ console.log(`Usage: total-recall context "task" [--action edit,test,publish] [--budget ${CAPSULE_LIMIT}] [--format text|json] [--knowledge] [--debug] [--project|--global]\nLocal rules only by default. Unknown applicability is required. Over budget, the capsule compacts automatically: absolute rules keep at least a summary and every other rule keeps at least its slug. Configured budgets are capped at ${CAPSULE_LIMIT}; pass a larger --budget to print the full capsule. Exit 2 means the budget cannot hold every rule; do not act until ready:true. --knowledge adds supporting documents; --debug exposes inventories within the same budget. Refresh on task/action/project or policy changes.`);
14
63
  return;
15
64
  }
16
65
  const { layer, remainingArgs } = parseLayerFlag(args);
@@ -24,7 +73,7 @@ export default async function context(args = []) {
24
73
  }
25
74
  return undefined;
26
75
  })();
27
- let actions = [], budget = configuredBudget ?? 4000, format = 'text', includeKnowledge = false, debug = false;
76
+ let actions = [], budget = Math.min(configuredBudget ?? CAPSULE_LIMIT, CAPSULE_LIMIT), format = 'text', includeKnowledge = false, debug = false;
28
77
  for (let i = 1; i < remainingArgs.length; i++) {
29
78
  const flag = remainingArgs[i];
30
79
  if (flag === '--knowledge') { includeKnowledge = true; continue; }
@@ -35,14 +84,8 @@ export default async function context(args = []) {
35
84
  else if (flag === '--format' && ['json', 'text'].includes(value)) format = value;
36
85
  else throw new Error(`Unknown context option: ${flag}`);
37
86
  }
38
- const brains = getBothBrains();
39
- const own = layer !== 'global' && brains.project && isBrainEnabled(brains.project.brainDir) ? brains.project.brainDir : null;
40
- const global = layer !== 'project' && brains.global && isBrainEnabled(brains.global.brainDir) ? brains.global.brainDir : null;
41
- if (!own && !global) throw new Error('No enabled brain in selected scope');
42
- const nodes = own ? mergeGlobalRuleNodes(getNodes(path.join(own, 'memory-vault')), global ? getNodes(path.join(global, 'memory-vault')) : []) :
43
- getNodes(path.join(global, 'memory-vault')).map(n => ({ ...n, _layer: 'global' }));
44
- const skillsDir = path.dirname(own || global);
45
- const derivedDir = own ? path.join(own, 'memory-derived') : (global ? path.join(global, 'memory-derived') : null);
87
+ const { nodes, skillsDir, derivedDirs } = loadContextNodes(layer);
88
+ const derivedDir = derivedDirs[0];
46
89
  const result = await compileContext({ query, actions, nodes, includeKnowledge, budget: { total: budget }, projectRoot: process.cwd(),
47
90
  derivedDir, contributions: legacyRuleContributions(skillsDir), versionInputs: surfaceInputsHash({ skillsDir }) });
48
91
  if (debug && format === 'text') throw new Error('--debug requires --format json');
@@ -55,18 +98,7 @@ export default async function context(args = []) {
55
98
  derivedDir, action: actions, query, consumer: 'cli',
56
99
  });
57
100
  if (!response.ready && !debug) {
58
- // Admission failed: emitting bodies here makes each budget retry load the
59
- // same rules again. Keep the full set in the compiler/API, and admit it only
60
- // after the caller explicitly raises the budget or curates applicability.
61
- const requiredBudget = response.stats.total_tokens + 32;
62
- const diagnostic = { ready: false, reason: 'budget-overflow', context: '', stats: {
63
- version: response.stats.version, required_count: response.stats.required_count,
64
- budget, required_budget: requiredBudget,
65
- token_measurement: 'estimated_chars_divided_by_four',
66
- } };
67
- console.log(format === 'json' ? JSON.stringify(diagnostic) :
68
- `ready:false reason:budget-overflow budget:${budget} required_budget:${requiredBudget} required_rules:${response.stats.required_count} version:${response.stats.version}\nRetry with --budget ${requiredBudget}; do not act until ready:true.\nTo shorten the rules for good: total-recall rules audit, then rules draft/apply.`);
69
- process.exitCode = 2;
101
+ await printCompacted({ query, actions, budget, format, version: response.stats.version, fullRequired: response.stats.total_tokens + 32, nodes, skillsDir, derivedDirs });
70
102
  return;
71
103
  }
72
104
  console.log(renderCapsule(response, format));
@@ -204,13 +204,16 @@ export default async function doctor() {
204
204
 
205
205
  if (agents.length > 0) {
206
206
  let healthyCount = 0;
207
+ const { HARNESS_SPECS } = await import('../core/meta-harness.mjs').catch(() => ({ HARNESS_SPECS: {} }));
207
208
  for (const agent of agents) {
208
209
  const exists = commandExists(agent.binary);
210
+ const spec = HARNESS_SPECS[agent.name] || HARNESS_SPECS[agent.binary];
211
+ const enfTier = spec?.enforcement || 'advisory';
209
212
  if (exists) {
210
- logOk(`Agent ${agent.name.padEnd(8)}: Available (Binary: ${agent.binary})`);
213
+ logOk(`Agent ${agent.name.padEnd(8)}: Available (Binary: ${agent.binary}) [${enfTier}]`);
211
214
  healthyCount++;
212
215
  } else {
213
- logWarn(`Agent ${agent.name.padEnd(8)}: NOT FOUND in PATH (Binary: ${agent.binary})`);
216
+ logWarn(`Agent ${agent.name.padEnd(8)}: NOT FOUND in PATH (Binary: ${agent.binary}) [${enfTier}]`);
214
217
  warnings++;
215
218
  }
216
219
  }
@@ -33,6 +33,8 @@ function printHelp() {
33
33
  }
34
34
 
35
35
  export default async function forget(args) {
36
+ // Checked first: a destructive command must never act on a request for help.
37
+ if (args.includes('--help') || args.includes('-h')) { printHelp(); return; }
36
38
  const { layer: explicitLayer, remainingArgs } = parseLayerFlag(args);
37
39
 
38
40
  // Check for --project-all