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
@@ -1,6 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { detectHarnesses, detectHarnessesWithAuth, queryHarnessUsage, dispatchTask, runCouncil, HARNESS_SPECS } from '../core/meta-harness.mjs';
3
+ import { checkReceipt, findRoot, ACK_COMMAND } from '../core/preflight-receipt.mjs';
4
+
5
+ /** Harness work belongs to the meta-harness plugin. These commands are a thin door to it. */
6
+ async function callPlugin(subcommand, args, options = {}) {
7
+ const { getPlugin } = await import('../core/plugin-loader.mjs');
8
+ const plugin = getPlugin('meta-harness', process.cwd());
9
+ if (!plugin?.valid) throw new Error('The meta-harness plugin is not installed. Run: total-recall plugin install meta-harness');
10
+ const { runPluginCommand } = await import('../core/plugin-runner.mjs');
11
+ const result = await runPluginCommand(plugin, { subcommand, args: [...args, '--json'], timeoutMs: 600000, ...options });
12
+ let data = null;
13
+ try { data = JSON.parse(result.output); } catch { /* reported below */ }
14
+ if (!data) throw new Error(`meta-harness ${subcommand} returned no result: ${String(result.output).trim().slice(0, 300)}`);
15
+ return { ok: result.ok, data };
16
+ }
4
17
 
5
18
  function printHelp() {
6
19
  console.log(`
@@ -10,10 +23,10 @@ Usage:
10
23
  npx total-recall harness <command> [options]
11
24
 
12
25
  Commands:
13
- list Inspect all external developer harnesses, versions & auth
14
- usage Query harness account APIs for 5h & weekly quota remaining %
15
- dispatch <id> "<task>" Headlessly invoke a specific agent harness
26
+ list, usage Harness readiness, versions and measured usage
27
+ dispatch <id> "<task>" Headlessly invoke a specific agent harness (read-only by default)
16
28
  Options: --node <name> to execute remotely on a mesh node
29
+ --write to let the agent change files
17
30
  council "<task>" Run concurrent multi-harness consensus deliberation
18
31
 
19
32
  Available Harness IDs:
@@ -49,45 +62,32 @@ export async function run(argv) {
49
62
  const asJson = args.includes('--json');
50
63
 
51
64
  if (!command || command === 'list' || command === 'status' || command === 'usage') {
52
- const reports = await queryHarnessUsage();
53
- if (asJson) {
54
- console.log(JSON.stringify(reports, null, 2));
55
- return;
65
+ let report;
66
+ try { ({ data: report } = await callPlugin('quota', [])); }
67
+ catch (err) { console.error(`\n❌ ${err.message}`); process.exit(1); }
68
+ if (asJson) { console.log(JSON.stringify(report, null, 2)); return; }
69
+ const pad = (value, width) => String(value ?? '—').padEnd(width).slice(0, width);
70
+ console.log(`\n🎮 Total Recall — Agent Harnesses (${report.node})\n`);
71
+ console.log(`${pad('Harness', 12)} ${pad('Version', 12)} ${pad('Status', 14)} ${pad('5h used', 9)} ${pad('Week used', 10)} Note`);
72
+ for (const [id, quota] of Object.entries(report.quotas || {})) {
73
+ if (id === 'system' || id === 'metered') continue;
74
+ console.log(`${pad(id, 12)} ${pad(quota.version, 12)} ${pad(quota.status, 14)} ${pad(quota.rolling5hUsedPercent, 9)} ${pad(quota.weeklyUsedPercent, 10)} ${quota.quotaNote || ''}`);
56
75
  }
57
-
58
- console.log(`\n🎮 Total Recall — Connected Agent Harnesses, Versions & Quotas\n`);
59
-
60
- const pad = (s, l) => String(s || '').padEnd(l).slice(0, l);
61
- console.log('┌──────────────┬──────────────────────────────┬──────────────┬──────────────┬──────────────────────────┬──────────────────────┬──────────────────────┬──────────────┐');
62
- console.log(`│ ${pad('Harness ID', 12)} │ ${pad('Name', 28)} │ ${pad('Version', 12)} │ ${pad('Current?', 12)} │ ${pad('Auth Route', 24)} │ ${pad('5h Quota Rem. %', 20)} │ ${pad('Weekly Rem. %', 20)} │ ${pad('Status', 12)} │`);
63
- console.log('├──────────────┼──────────────────────────────┼──────────────┼──────────────┼──────────────────────────┼──────────────────────┼──────────────────────┼──────────────┤');
64
-
65
- for (const h of reports) {
66
- const ver = h.version || '—';
67
- const cur = h.isCurrent ? 'Current ✅ ' : (h.latestVersion ? `Update (${h.latestVersion.slice(0, 5)})` : 'Check ⚠️ ');
68
- const auth = h.authRoute || '—';
69
- const q5h = h.rolling5hRemainingPct || '—';
70
- const qWeek = h.weeklyRemainingPct || '—';
71
- let statusStr = '\x1b[31mNot Found ❌\x1b[0m';
72
- if (h.available) {
73
- if (h.status === 'ready') statusStr = '\x1b[32mReady ✅\x1b[0m ';
74
- else if (h.status === 'update_available') statusStr = '\x1b[33mUpdate 🔄\x1b[0m ';
75
- else statusStr = '\x1b[33mNo Auth ⚠️\x1b[0m ';
76
- }
77
- console.log(`│ \x1b[1m${pad(h.id, 12)}\x1b[0m │ ${pad(h.name, 28)} │ ${pad(ver, 12)} │ ${pad(cur, 12)} │ ${pad(auth, 24)} │ ${pad(q5h, 20)} │ ${pad(qWeek, 20)} │ ${statusStr} │`);
78
- }
79
- console.log('└──────────────┴──────────────────────────────┴──────────────┴──────────────┴──────────────────────────┴──────────────────────┴──────────────────────┴──────────────┘\n');
76
+ console.log('');
80
77
  return;
81
78
  }
82
79
 
83
80
  if (command === 'dispatch') {
84
81
  let targetNode = null;
82
+ let write = false;
85
83
  const filteredRest = [];
86
84
  for (let i = 0; i < rest.length; i++) {
87
85
  if (rest[i] === '--node' || rest[i] === '-n') {
88
86
  targetNode = rest[i + 1];
89
87
  i++;
90
- } else {
88
+ } else if (rest[i] === '--write') {
89
+ write = true;
90
+ } else if (rest[i] !== '--json') {
91
91
  filteredRest.push(rest[i]);
92
92
  }
93
93
  }
@@ -100,13 +100,24 @@ export async function run(argv) {
100
100
  process.exit(1);
101
101
  }
102
102
 
103
+ if (!targetNode) {
104
+ const root = findRoot(process.cwd());
105
+ const receipt = checkReceipt(root);
106
+ if (!receipt.ok) {
107
+ console.error(`\n❌ Dispatch refused by TR-PREFLIGHT gate: ${receipt.problems[0] || 'check failed'} (directory: ${root}).`);
108
+ console.error(` Read the complete context capsule, then run: ${ACK_COMMAND}\n`);
109
+ process.exit(1);
110
+ }
111
+ }
112
+
103
113
  const nodeStr = targetNode ? ` on mesh node \x1b[1;33m${targetNode}\x1b[0m` : '';
104
114
  console.log(`\n🚀 Dispatching task to \x1b[1;36m${harnessId}\x1b[0m${nodeStr}...`);
105
115
  try {
106
- const res = await dispatchTask(harnessId, task, { node: targetNode });
107
- console.log(`\n--- [Response from ${res.harnessName}${targetNode ? ' (' + targetNode + ')' : ''}] ---\n`);
108
- console.log(res.response);
109
- console.log(`\n✅ Exit code: ${res.exitCode}`);
116
+ const { ok, data } = await callPlugin('dispatch', [], { stdinText: JSON.stringify({ agent: harnessId, cwd: process.cwd(), prompt: task, node: targetNode || 'local', permissions: { read: true, write }, allowUnknownAuth: true }) });
117
+ if (asJson) { console.log(JSON.stringify(data, null, 2)); if (!ok) process.exit(1); return; }
118
+ if (!ok) throw new Error(data.error || 'The agent failed');
119
+ console.log(`\n--- [Response from ${harnessId}${targetNode ? ' (' + targetNode + ')' : ''}] ---\n`);
120
+ console.log(data.response);
110
121
  } catch (err) {
111
122
  console.error(`\n❌ Dispatch failed: ${err.message}`);
112
123
  process.exit(1);
@@ -115,7 +126,14 @@ export async function run(argv) {
115
126
  }
116
127
 
117
128
  if (command === 'council') {
118
- const task = rest.join(' ').trim();
129
+ const root = findRoot(process.cwd());
130
+ const receipt = checkReceipt(root);
131
+ if (!receipt.ok) {
132
+ console.error(`\n❌ Council deliberation refused by TR-PREFLIGHT gate: ${receipt.problems[0] || 'check failed'} (directory: ${root}).`);
133
+ console.error(` Read the complete context capsule, then run: ${ACK_COMMAND}\n`);
134
+ process.exit(1);
135
+ }
136
+ const task = rest.filter(arg => arg !== '--json').join(' ').trim();
119
137
  if (!task) {
120
138
  console.error('Error: Please specify task prompt for council deliberation.');
121
139
  console.error('Usage: npx total-recall harness council "<task>"');
@@ -126,20 +144,14 @@ export async function run(argv) {
126
144
  console.log(` Task: "${task}"\n`);
127
145
 
128
146
  try {
129
- const councilRes = await runCouncil(task);
130
- console.log(`Participating Harnesses: ${councilRes.participants.join(', ')}\n`);
131
-
132
- for (const r of councilRes.results) {
133
- console.log(`================================================================`);
134
- console.log(`● Harness: \x1b[1;36m${r.harnessName}\x1b[0m (${r.harnessId})`);
135
- console.log(`================================================================`);
136
- if (r.success) {
137
- console.log(r.response);
138
- } else {
139
- console.log(`\x1b[31mError: ${r.error || r.stderr || 'Execution failed'}\x1b[0m`);
140
- }
141
- console.log('');
147
+ const { data } = await callPlugin('coordinate', [task, '--mode', 'council', '--execute']);
148
+ if (!data.votes) throw new Error(data.error || 'No votes returned');
149
+ if (asJson) { console.log(JSON.stringify(data, null, 2)); return; }
150
+ for (const vote of data.votes) {
151
+ console.log(`● \x1b[1;36m${vote.harness}\x1b[0m: ${vote.vote}${vote.error ? ` (${vote.error})` : ''}`);
152
+ if (vote.feedback) console.log(`${vote.feedback}\n`);
142
153
  }
154
+ console.log(`Consensus: ${data.consensus}`);
143
155
  } catch (err) {
144
156
  console.error(`\n❌ Council deliberation failed: ${err.message}`);
145
157
  process.exit(1);
@@ -69,10 +69,10 @@ Installed capabilities:`);
69
69
  return;
70
70
  }
71
71
  const plugins = discoverPlugins(process.cwd()).filter(p => p.valid);
72
- const plugin = plugins.find(p => p.manifest.cli?.handler && (p.manifest.cli.command || p.id) === command)
72
+ const plugin = plugins.find(p => p.manifest.cli?.handler && ((p.manifest.cli.command || p.id) === command || p.manifest.cli.alias === command))
73
73
  || plugins.find(p => p.manifest.commands?.some(entry => entry.name === command));
74
74
  if (!plugin) throw new Error(`Unknown command '${command}'. Install its capability plugin or run total-recall --help.`);
75
- const declared = plugin.manifest.cli?.handler && (plugin.manifest.cli.command || plugin.id) === command
75
+ const declared = plugin.manifest.cli?.handler && ((plugin.manifest.cli.command || plugin.id) === command || plugin.manifest.cli.alias === command)
76
76
  ? null : plugin.manifest.commands.find(entry => entry.name === command);
77
77
  if (declared && rest.some(arg => ['--help', '-h'].includes(arg))) {
78
78
  console.log(`Usage: total-recall ${command} [args] [--json]\n${declared.description || ''}`);
@@ -1,13 +1,14 @@
1
1
  import fs from 'node:fs';
2
2
  import { decide, SWEEP_CONSULT_MS } from '../core/preflight-gate.mjs';
3
- import { ACK_COMMAND, acknowledge, checkReceipt, findRoot, lastConsultMs } from '../core/preflight-receipt.mjs';
3
+ import { ACK_COMMAND, acknowledge, checkReceipt, findRoot, lastConsultMs, recordDecision, readDecisions } from '../core/preflight-receipt.mjs';
4
4
 
5
5
  const USAGE = `Usage: total-recall preflight <command>
6
6
  ack --task "<task>" [--action read,edit,test] [--json] Record a receipt after context ready:true and rules verify
7
7
  check [--json] Exit 1 unless a fresh receipt exists for this directory
8
8
  gate [--harness <id>] Hook entry: read a call on stdin, answer allow or deny (exit 2 on deny)
9
9
  remind [--json] Prompt-hook entry: advisory text when the receipt is missing; always exits 0
10
- install | verify | report Harness adapters, owned by the cli-agents plugin
10
+ install [--harness <id>] [--dry-run] Install the gate for a harness. The gate has no off switch.
11
+ verify | report Harness adapters, owned by the cli-agents plugin
11
12
  Aliases: total-recall tr-preflight --ack | --check. A receipt lasts 30 minutes (config preflight_max_age_minutes).`;
12
13
 
13
14
  /** An explicit override only. The configured window is read once, by `ack`, and recorded in the receipt. */
@@ -76,6 +77,15 @@ async function ack(args) {
76
77
  console.log(` receipt: ${result.file}`);
77
78
  }
78
79
 
80
+ async function loadGateAdapters() {
81
+ let adapters;
82
+ try { adapters = await (await import('../core/plugin-runtime.mjs')).loadPluginModule('meta-harness', 'gate-adapters'); } catch { adapters = null; }
83
+ if (!adapters) {
84
+ try { adapters = await (await import('../core/plugin-runtime.mjs')).loadPluginModule('cli-agents', 'gate-adapters'); } catch { adapters = null; }
85
+ }
86
+ return adapters;
87
+ }
88
+
79
89
  /**
80
90
  * Hook entry. It starts at exit 2 and only an explicit allow clears it, so any
81
91
  * failure, including one nobody anticipated, denies: most harnesses let the
@@ -91,19 +101,15 @@ async function gate(args) {
91
101
  if (!call || typeof call !== 'object' || Array.isArray(call)) return deny('gate-error', 'TR-PREFLIGHT gate input must be a JSON object.');
92
102
  const harness = flag(args, '--harness');
93
103
  if (harness) {
94
- let adapters;
95
- try { adapters = await (await import('../core/plugin-runtime.mjs')).loadPluginModule('meta-harness', 'gate-adapters'); } catch { adapters = null; }
96
- if (!adapters) {
97
- try { adapters = await (await import('../core/plugin-runtime.mjs')).loadPluginModule('cli-agents', 'gate-adapters'); } catch { adapters = null; }
98
- }
104
+ const adapters = await loadGateAdapters();
99
105
  const adapter = adapters?.get?.(harness);
100
106
  if (!adapter) return deny('gate-error', `No gate adapter for harness "${harness}": adapters are owned by the meta-harness plugin and it does not provide one. The gate fails closed.`);
101
- const out = await adapter.run({ stdin: call, decideWith: evaluate });
107
+ const out = await adapter.run({ stdin: call, decideWith: (c) => evaluate(c, { harness, readablePaths: adapter.readablePaths }) });
102
108
  if (out?.stdout) process.stdout.write(out.stdout);
103
109
  process.exitCode = Number.isInteger(out?.exitCode) ? out.exitCode : 2;
104
110
  return;
105
111
  }
106
- const answer = evaluate(call);
112
+ const answer = evaluate(call, { harness: 'generic' });
107
113
  process.stdout.write(`${JSON.stringify(answer)}\n`);
108
114
  if (answer.decision === 'allow') process.exitCode = 0;
109
115
  } catch (err) {
@@ -111,10 +117,128 @@ async function gate(args) {
111
117
  }
112
118
  }
113
119
 
114
- function evaluate(call) {
120
+ async function install(args) {
121
+ const harness = flag(args, '--harness') || 'claude';
122
+ const dryRun = args.includes('--dry-run');
123
+ if (args.includes('--remove') || args.includes('--off')) {
124
+ console.error('✖ The preflight gate has no off switch. If it blocks work, fix the cause: `total-recall tr-preflight --ack` names it, and `total-recall rules` stays open to repair it.');
125
+ process.exitCode = 1;
126
+ return;
127
+ }
128
+ const adapters = await loadGateAdapters();
129
+ if (!adapters) {
130
+ console.error('✖ Gate adapters are owned by the meta-harness (cli-agents) plugin and are not available.');
131
+ process.exitCode = 1;
132
+ return;
133
+ }
134
+ try {
135
+ const res = adapters.install(harness, { dryRun });
136
+ if (args.includes('--json')) {
137
+ console.log(JSON.stringify({ ok: true, harness, action: 'installed', ...res }));
138
+ } else {
139
+ console.log(`✔ Installed preflight gate for ${harness}`);
140
+ if (res.added?.length) console.log(` added: ${res.added.join(', ')}`);
141
+ if (res.removed?.length) console.log(` removed: ${res.removed.join(', ')}`);
142
+ console.log(` settings: ${res.settings}`);
143
+ }
144
+ } catch (err) {
145
+ console.error(`✖ Failed to install preflight gate for ${harness}: ${err.message}`);
146
+ process.exitCode = 1;
147
+ }
148
+ }
149
+
150
+ async function verify(args) {
151
+ const harness = flag(args, '--harness') || 'claude';
152
+ const adapters = await loadGateAdapters();
153
+ if (!adapters) {
154
+ console.error('✖ Gate adapters are owned by the meta-harness (cli-agents) plugin and are not available.');
155
+ process.exitCode = 1;
156
+ return;
157
+ }
158
+ try {
159
+ const res = adapters.verify(harness);
160
+ const ok = Boolean(res.installed && res.exercised);
161
+ if (args.includes('--json')) {
162
+ console.log(JSON.stringify({ ok, harness, ...res }));
163
+ } else {
164
+ if (ok) {
165
+ console.log(`✔ Preflight gate for ${harness} is verified (${res.level || 'enforced'})`);
166
+ } else {
167
+ console.error(`✖ Preflight gate for ${harness} verification failed`);
168
+ if (!res.installed) console.error(' hook is not installed in harness configuration');
169
+ if (!res.exercised) console.error(' synthetic deny exercise failed');
170
+ }
171
+ }
172
+ if (!ok) process.exitCode = 1;
173
+ } catch (err) {
174
+ console.error(`✖ Failed to verify preflight gate for ${harness}: ${err.message}`);
175
+ process.exitCode = 1;
176
+ }
177
+ }
178
+
179
+ async function report(args) {
180
+ const adapters = await loadGateAdapters();
181
+ if (!adapters) {
182
+ console.error('✖ Gate adapters are owned by the meta-harness (cli-agents) plugin and are not available.');
183
+ process.exitCode = 1;
184
+ return;
185
+ }
186
+ const all = adapters.list();
187
+ const results = all.map(a => ({
188
+ id: a.id,
189
+ level: a.level,
190
+ verification: adapters.verify(a.id)
191
+ }));
192
+
193
+ const decisions = readDecisions();
194
+ const summary = {
195
+ total: decisions.length,
196
+ allowed: decisions.filter(d => d.decision === 'allow').length,
197
+ denied: decisions.filter(d => d.decision === 'deny').length,
198
+ byHarness: {},
199
+ byRepo: {}
200
+ };
201
+ for (const d of decisions) {
202
+ const h = d.harness || 'generic';
203
+ summary.byHarness[h] ??= { total: 0, allowed: 0, denied: 0 };
204
+ summary.byHarness[h].total++;
205
+ if (d.decision === 'allow') summary.byHarness[h].allowed++;
206
+ else summary.byHarness[h].denied++;
207
+
208
+ const r = d.repo || 'unknown';
209
+ summary.byRepo[r] ??= { total: 0, allowed: 0, denied: 0 };
210
+ summary.byRepo[r].total++;
211
+ if (d.decision === 'allow') summary.byRepo[r].allowed++;
212
+ else summary.byRepo[r].denied++;
213
+ }
214
+
215
+ if (args.includes('--json')) {
216
+ console.log(JSON.stringify({ adapters: results, compliance: summary }, null, 2));
217
+ } else {
218
+ for (const r of results) {
219
+ console.log(`- ${r.id}: ${r.level} (installed: ${r.verification.installed}, exercised: ${r.verification.exercised})`);
220
+ }
221
+ if (summary.total > 0) {
222
+ console.log(`\nCompliance summary (${summary.total} real gate decisions observed):`);
223
+ const pct = ((summary.allowed / summary.total) * 100).toFixed(1);
224
+ console.log(` Allowed: ${summary.allowed} | Denied: ${summary.denied} | Rate: ${pct}%`);
225
+ console.log(' By harness:');
226
+ for (const [hId, stats] of Object.entries(summary.byHarness)) {
227
+ const hPct = ((stats.allowed / stats.total) * 100).toFixed(1);
228
+ console.log(` ${hId.padEnd(14)}: ${stats.allowed}/${stats.total} allowed (${hPct}%)`);
229
+ }
230
+ } else {
231
+ console.log('\nCompliance summary:');
232
+ console.log(' No gate decisions recorded yet (live gate traffic has not been observed).');
233
+ }
234
+ }
235
+ }
236
+
237
+ function evaluate(call, { harness, readablePaths } = {}) {
115
238
  const root = findRoot(call.cwd || process.cwd());
116
239
  const limit = envMaxAgeMs();
117
- return decide(call, {
240
+ const decision = decide(call, {
241
+ readablePaths,
118
242
  check: () => checkReceipt(root, { maxAgeMs: limit }),
119
243
  recentConsult: () => {
120
244
  const now = Date.now();
@@ -123,6 +247,18 @@ function evaluate(call) {
123
247
  return Boolean(receipt && now - Date.parse(receipt.at) < SWEEP_CONSULT_MS);
124
248
  },
125
249
  });
250
+ recordDecision({
251
+ harness: harness || call.harness || 'generic',
252
+ cwd: call.cwd || process.cwd(),
253
+ repo: root,
254
+ tool: call.tool || 'other',
255
+ command: call.command || null,
256
+ path: call.path || null,
257
+ decision: decision.decision,
258
+ rule: decision.rule,
259
+ reason: decision.reason || null
260
+ });
261
+ return decision;
126
262
  }
127
263
 
128
264
  /**
@@ -149,13 +285,13 @@ export default async function preflight(args = []) {
149
285
  // Original `tr-preflight` forms.
150
286
  if (first === '--ack' || first === 'ack') return ack(args);
151
287
  if (first === '--check' || first === 'check') return check(args);
288
+ // Help never performs the subcommand: `install --help` once rewrote the harness settings (C-019).
289
+ if (args.includes('--help') || args.includes('-h')) return console.log(USAGE);
152
290
  if (first === 'gate') return gate(args.slice(1));
153
291
  if (first === 'remind') return remind(args);
154
- if (['install', 'verify', 'report'].includes(first)) {
155
- console.error(`preflight ${first}: harness adapters are owned by the meta-harness (cli-agents) plugin and are not installed yet (ENFORCE-01 EN-03 onward).`);
156
- process.exitCode = 1;
157
- return;
158
- }
292
+ if (first === 'install') return install(args.slice(1));
293
+ if (first === 'verify') return verify(args.slice(1));
294
+ if (first === 'report') return report(args.slice(1));
159
295
  console.error(`Unknown preflight command: ${first}\n\n${USAGE}`);
160
296
  process.exitCode = 1;
161
297
  }