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
@@ -14,6 +14,16 @@ export const DOC_SOURCES = [
14
14
  { id: 'muse', index: 'https://dev.meta.ai/docs/muse-code', prefix: 'https://dev.meta.ai/docs/muse-code' },
15
15
  { id: 'pi', repo: 'badlogic/pi-mono', branch: 'main', directories: ['packages/coding-agent/docs/'], index: 'https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/README.md' },
16
16
  { id: 'hermes', repo: 'NousResearch/hermes-agent', branch: 'main', directories: ['website/docs/'], index: 'https://raw.githubusercontent.com/NousResearch/hermes-agent/main/website/docs/reference/cli-commands.md' },
17
+ { id: 'devin', index: 'https://docs.devin.ai/llms.txt', prefix: 'https://docs.devin.ai/', catalog:true },
18
+ { id: 'windsurf', index: 'https://docs.windsurf.com/llms.txt', prefix: 'https://docs.windsurf.com/', catalog:true },
19
+ { id: 'cline', index: 'https://docs.cline.bot/llms.txt', prefix: 'https://docs.cline.bot/', catalog:true },
20
+ { id: 'openclaw', index: 'https://docs.openclaw.ai/llms.txt', prefix: 'https://docs.openclaw.ai/', catalog:true },
21
+ { id: 'aider', index: 'https://aider.chat/docs/', prefix: 'https://aider.chat/docs/' },
22
+ { id: 'goose', index: 'https://block.github.io/goose/docs/quickstart', prefix: 'https://block.github.io/goose/docs/' },
23
+ { id: 'openhands', index: 'https://docs.openhands.dev/llms.txt', prefix: 'https://docs.openhands.dev/', catalog:true },
24
+ { id: 'replit', index: 'https://docs.replit.com/llms.txt', prefix: 'https://docs.replit.com/', catalog:true },
25
+ { id: 'lovable', index: 'https://docs.lovable.dev/llms.txt', prefix: 'https://docs.lovable.dev/', catalog:true },
26
+ { id: 'ollama', index: 'https://docs.ollama.com/llms.txt', prefix: 'https://docs.ollama.com/', catalog:true },
17
27
  ];
18
28
  const hash = value => crypto.createHash('sha256').update(value).digest('hex');
19
29
  const TTL = 24 * 60 * 60 * 1000;
@@ -21,7 +31,11 @@ function pageFile(directory,id,file){if(!/^[a-f0-9]{64}-[a-f0-9]{64}\.txt$/.test
21
31
  function atomic(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); const temp = `${file}.${process.pid}.tmp`; fs.writeFileSync(temp, value, { mode: 0o600 }); fs.renameSync(temp, file); }
22
32
  export function documentationDirectory() {
23
33
  const brain = process.env.TR_SECRETS_BRAIN || path.join(process.env.AGENT_DIR || path.join(os.homedir(), '.agent'), 'skills/total-recall');
24
- return path.join(brain, 'memory-derived/cli-agents/docs');
34
+ const directory = path.join(brain, 'memory-derived/meta-harness/docs');
35
+ // Keep the downloaded pages when the old directory name is still on disk.
36
+ const legacy = path.join(brain, 'memory-derived/cli-agents/docs');
37
+ if (!fs.existsSync(directory) && fs.existsSync(legacy)) { fs.mkdirSync(path.dirname(directory), { recursive: true }); fs.renameSync(legacy, directory); }
38
+ return directory;
25
39
  }
26
40
  function metadata(directory, id) { try { return JSON.parse(fs.readFileSync(path.join(directory,id,'index.json'),'utf8')); } catch { return null; } }
27
41
  export function documentationStatus({ directory = documentationDirectory(), agent, now = Date.now() } = {}) {
@@ -39,11 +53,11 @@ export function documentationStatus({ directory = documentationDirectory(), agen
39
53
  return { ok:agents.every(row=>row.fresh), directory, agents };
40
54
  }
41
55
  async function download(url, fetchImpl) {
42
- const allowed = new Set(['code.claude.com','claude.com','platform.claude.com','docs.anthropic.com','anthropic.com','developers.openai.com','openai.com','help.openai.com','learn.chatgpt.com','chatgpt.com','antigravity.google','cursor.com','docs.x.ai','dev.meta.ai','raw.githubusercontent.com','api.github.com']);
56
+ const allowed = new Set(['code.claude.com','claude.com','platform.claude.com','docs.anthropic.com','anthropic.com','developers.openai.com','openai.com','help.openai.com','learn.chatgpt.com','chatgpt.com','antigravity.google','cursor.com','docs.x.ai','dev.meta.ai','docs.devin.ai','docs.windsurf.com','windsurf.com','docs.cline.bot','docs.openclaw.ai','aider.chat','docs.replit.com','docs.lovable.dev','docs.ollama.com','block.github.io','docs.openhands.dev','raw.githubusercontent.com','api.github.com']);
43
57
  let target = new URL(url);
44
58
  for(let redirects=0;redirects<5;redirects++) {
45
59
  if(target.protocol!=='https:' || target.username || target.password || !allowed.has(target.hostname)) throw new Error(`Unapproved official documentation origin: ${target.hostname}`);
46
- const response=await fetchImpl(target,{redirect:'manual',signal:AbortSignal.timeout(20000),headers:{'User-Agent':'Total-Recall-CLI-Agents-Docs','Accept':'*/*'}});
60
+ const response=await fetchImpl(target,{redirect:'manual',signal:AbortSignal.timeout(20000),headers:{'User-Agent':'Total-Recall-Meta-Harness-Docs','Accept':'*/*'}});
47
61
  if(response.status>=300 && response.status<400){target=new URL(response.headers.get('location'),target);continue;}
48
62
  // Some entries advertise Markdown exports which no longer exist while the
49
63
  // same official HTML document remains available. Fetch that representation.
@@ -63,15 +77,16 @@ function links(text,base,prefix) {
63
77
  for(const match of text.matchAll(/\]\(([^\s)]+)(?:\s+[^)]*)?\)/g)){try{values.push(new URL(match[1],base).href);}catch{}}
64
78
  for(const match of text.matchAll(/href=["']([^"']+)["']/g)) {try{values.push(new URL(match[1],base).href);}catch{}}
65
79
  return [...new Set(values.map(value=>{try{const url=new URL(value);url.hash='';url.search='';url.pathname=url.pathname.replace(/\/{2,}/g,'/').replace(/\/$/,'') || '/';return url.href;}catch{return '';}}))]
66
- .filter(url=>url.startsWith(prefix) && !/\.(png|jpg|jpeg|svg|gif|css|js|woff2?|ico|pdf)$/i.test(url));
80
+ .filter(url=>url.startsWith(prefix) && !/%3[CE]|[<>"'`\s]/i.test(url) && !/\.(png|jpg|jpeg|svg|gif|css|js|woff2?|ico|pdf|json|ya?ml|xml|zip|gz|tgz|lock)$/i.test(url));
67
81
  }
68
82
  export async function refreshDocumentation({ directory = documentationDirectory(), agent, fetchImpl = fetch, now = () => new Date(), maxPages = 1500 } = {}) {
69
83
  const sources=agent?DOC_SOURCES.filter(source=>source.id===agent):DOC_SOURCES;
70
84
  if(!sources.length)throw new Error('Unknown documentation adapter');
71
85
  const reports=[];
72
86
  for(const source of sources) {
73
- const previous=metadata(directory,source.id), pages=[], queue=[source.index], seen=new Set(), queued=new Set(queue), errors=[];
74
- const enqueue=url=>{if(!queued.has(url)){queued.add(url);queue.push(url);}};
87
+ const previous=metadata(directory,source.id), pages=[], queue=[source.index], seen=new Set(), queued=new Set(queue), errors=[], discovered=new Set(), skipped=[];
88
+ // `found` marks a link taken from HTML navigation. The vendor's catalog, the index and the repository tree are authoritative; a link found by scraping is not.
89
+ const enqueue=(url,found=false)=>{if(!queued.has(url)){queued.add(url);queue.push(url);if(found)discovered.add(url);}};
75
90
  try {
76
91
  if(source.repo) {
77
92
  const tree=JSON.parse(await download(`https://api.github.com/repos/${source.repo}/git/trees/${source.branch}?recursive=1`,fetchImpl));
@@ -85,25 +100,33 @@ export async function refreshDocumentation({ directory = documentationDirectory(
85
100
  if(seen.size>maxPages)throw new Error('Official documentation exceeds configured page budget');
86
101
  const downloads=await Promise.allSettled(batch.map(async url=>({url,text:await download(url,fetchImpl)})));
87
102
  for(let index=0;index<downloads.length;index++) {
88
- const result=downloads[index];if(result.status==='rejected'){errors.push({url:batch[index],error:result.reason.message});continue;}
103
+ const result=downloads[index];
104
+ if(result.status==='rejected'){
105
+ const message=result.reason.message;
106
+ // A scraped link that is gone (404 or 410) is a stale or illustrative link, such as a placeholder in a code sample. It is reported as skipped and does not fail the refresh. Anything else, and anything the vendor itself lists, stays a failure.
107
+ if(discovered.has(batch[index]) && /HTTP (404|410)\b/.test(message)){skipped.push({url:batch[index],error:message});continue;}
108
+ // A page that redirects off the approved origins (for example a trust-center portal) is an external link, not documentation. It is never followed, and it is reported. The index itself redirecting away still fails the refresh.
109
+ if(batch[index]!==source.index && /^Unapproved official documentation origin/.test(message)){skipped.push({url:batch[index],error:message});continue;}
110
+ errors.push({url:batch[index],error:message});continue;
111
+ }
89
112
  const {url,text}=result.value;
90
113
  const sha256=hash(text),file=`${hash(url)}-${sha256}.txt`;
91
114
  atomic(path.join(directory,source.id,file),text);
92
115
  pages.push({url,file,sha256,bytes:Buffer.byteLength(text)});
93
116
  // A published llms index is the authoritative documentation catalog.
94
117
  // HTML navigation also contains unrelated and obsolete marketing links.
95
- if(source.prefix && (!source.catalog || url.endsWith('llms.txt')))for(const link of links(text,url,source.prefix))enqueue(link);
118
+ if(source.prefix && (!source.catalog || url.endsWith('llms.txt')))for(const link of links(text,url,source.prefix))enqueue(link,!url.endsWith('llms.txt'));
96
119
  }
97
120
  }
98
121
  const checkedAt=now().toISOString();
99
122
  if(errors.length){
100
123
  const error=`${errors.length} official document(s) failed: ${errors[0].error}`;
101
124
  const preserved=previous?.complete?previous:{id:source.id,complete:false,pages};
102
- atomic(path.join(directory,source.id,'index.json'),JSON.stringify({...preserved,lastAttempt:checkedAt,error,failedCount:errors.length,failures:errors},null,2));
103
- reports.push({id:source.id,ok:false,error,pages:preserved.pages?.length || 0,downloadedPages:pages.length,failedCount:errors.length,failures:errors});continue;
125
+ atomic(path.join(directory,source.id,'index.json'),JSON.stringify({...preserved,lastAttempt:checkedAt,error,failedCount:errors.length,failures:errors,...(skipped.length?{skipped}:{})},null,2));
126
+ reports.push({id:source.id,ok:false,error,pages:preserved.pages?.length || 0,downloadedPages:pages.length,failedCount:errors.length,failures:errors,...(skipped.length?{skippedCount:skipped.length,skipped}:{})});continue;
104
127
  }
105
- atomic(path.join(directory,source.id,'index.json'),JSON.stringify({id:source.id,complete:true,checkedAt,lastAttempt:checkedAt,pages},null,2));
106
- reports.push({id:source.id,ok:true,pages:pages.length});
128
+ atomic(path.join(directory,source.id,'index.json'),JSON.stringify({id:source.id,complete:true,checkedAt,lastAttempt:checkedAt,pages,...(skipped.length?{skipped}:{})},null,2));
129
+ reports.push({id:source.id,ok:true,pages:pages.length,...(skipped.length?{skippedCount:skipped.length,skipped}:{})});
107
130
  } catch(error) {
108
131
  atomic(path.join(directory,source.id,'index.json'),JSON.stringify({...previous,id:source.id,lastAttempt:now().toISOString(),error:error.message},null,2));
109
132
  reports.push({id:source.id,ok:false,error:error.message,pages:previous?.pages?.length || 0});
@@ -142,9 +165,12 @@ export function readDocumentation({agent,url,directory=documentationDirectory()}
142
165
  export function searchDocumentation({agent,query,directory=documentationDirectory()}) {
143
166
  if(typeof query!=='string' || !query.trim())throw new Error('Documentation query required');
144
167
  const sources=agent?DOC_SOURCES.filter(source=>source.id===agent):DOC_SOURCES;
145
- const matches=[];
168
+ const matches=[],phrase=query.trim().toLowerCase(),terms=phrase.split(/\s+/).filter(Boolean);
146
169
  for(const source of sources)for(const page of metadata(directory,source.id)?.pages || []) {
147
- const doc=readDocumentation({agent:source.id,url:page.url,directory});const index=doc.content.toLowerCase().indexOf(query.toLowerCase());
170
+ const doc=readDocumentation({agent:source.id,url:page.url,directory});const text=doc.content.toLowerCase();
171
+ // The exact phrase wins. Otherwise a page matches when it contains every term, in any order, so a query of two words is not limited to pages where they happen to be adjacent.
172
+ let index=text.indexOf(phrase);
173
+ if(index<0 && terms.length>1 && terms.every(term=>text.includes(term)))index=Math.min(...terms.map(term=>text.indexOf(term)));
148
174
  if(index>=0)matches.push({agent:source.id,url:page.url,excerpt:doc.content.slice(Math.max(0,index-120),index+600)});
149
175
  if(matches.length>=30)return {matches,truncated:true};
150
176
  }
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
- import { runWorker, runProcess, getActiveAdapters } from './worker.mjs';
4
+ import { runWorker, runProcess, getActiveAdapters, notReady } from './worker.mjs';
5
5
 
6
6
  async function hostModule(name) {
7
7
  const selected = process.env.TR_PACKAGE_ROOT;
@@ -36,7 +36,7 @@ export async function listTargets() {
36
36
 
37
37
  export function workerProgram(request) {
38
38
  const source = fs.readFileSync(new URL('./worker.mjs', import.meta.url), 'utf8');
39
- return source + '\nrunWorker(' + JSON.stringify(request) + ').then(result=>console.log(JSON.stringify(result))).catch(()=>{console.error("Selected agent worker failed");process.exitCode=1;});\n';
39
+ return source + '\nrunWorker(' + JSON.stringify(request) + ').then(result=>console.log(JSON.stringify(result))).catch(error=>{console.error(String(error?.message||"Selected agent worker failed"));process.exitCode=1;});\n';
40
40
  }
41
41
 
42
42
  export async function executeOnNode(node, request, { worker = runWorker, processRunner = runProcess, mesh, access } = {}) {
@@ -49,8 +49,8 @@ export async function executeOnNode(node, request, { worker = runWorker, process
49
49
  const resolved = access.resolveNodeAccess(target);
50
50
  const args = access.buildSshArgs(resolved, { command: 'export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"; exec node --input-type=module', extraOptions: ['BatchMode=yes', 'ConnectTimeout=10'] });
51
51
  if (!args) throw new Error('Selected mesh node has no verified SSH access');
52
- const result = await processRunner('ssh', args, { input: workerProgram(request), timeoutMs: request.operation === 'inspect' ? 120000 : 330000 });
53
- if (result.reason || result.exitCode !== 0) throw new Error('Selected mesh worker failed; verify node access, workspace and agent installation');
52
+ const result = await processRunner('ssh', args, { input: workerProgram(request), timeoutMs: request.operation === 'inspect' ? 120000 : (request.timeoutMs || 900000) + 30000 });
53
+ if (result.reason || result.exitCode !== 0) throw notReady(`Worker on ${node} failed: ${String(result.stderr || result.reason || 'verify node access, workspace and agent installation').trim().slice(0, 400)}`);
54
54
  try { return JSON.parse(result.stdout); } catch { throw new Error('Selected mesh worker returned invalid data'); }
55
55
  }
56
56
 
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Google Antigravity (agy) PreToolUse & lifecycle hook adapter.
3
+ * Official doc: https://antigravity.google/docs/hooks?tab=cli
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+
9
+ const ACK_COMMAND = 'total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
10
+
11
+ export const agyAdapter = {
12
+ id: 'agy',
13
+ aliases: ['antigravity'],
14
+ level: 'enforced',
15
+ source: {
16
+ url: 'https://antigravity.google/docs/hooks?tab=cli',
17
+ retrieved: '2026-11-04',
18
+ note: 'Antigravity CLI hooks in ~/.gemini/config/hooks.json or .agents/hooks.json'
19
+ },
20
+ readablePaths: [
21
+ /\/\.gemini\/antigravity\/brain\/[^/]+\/\.system_generated\/tasks\/[^/]+\.log$/,
22
+ /\/\.gemini\/antigravity\/brain\/[^/]+\/\.system_generated\/steps\/[^/]+\/output\.txt$/
23
+ ],
24
+ limits: [
25
+ 'Hooks receive camelCase toolCall fields: toolCall.name and toolCall.args',
26
+ 'PreToolUse blocks execution with decision: "deny" and exit code 2'
27
+ ],
28
+
29
+ parse(input = {}) {
30
+ const event = input.event || input.hookEventName || 'PreToolUse';
31
+ const toolCall = input.toolCall || {};
32
+ const name = toolCall.name || input.toolName || input.tool_name || '';
33
+ const args = toolCall.args || input.toolArgs || input.tool_input || {};
34
+ const cwd = input.cwd || input.workspacePaths?.[0] || process.cwd();
35
+
36
+ let tool = 'other';
37
+ let command;
38
+ let filePath;
39
+
40
+ if (name === 'run_command' || name === 'Bash') {
41
+ tool = 'shell';
42
+ command = args.CommandLine || args.command || '';
43
+ } else if (name === 'read_file' || /^(Read|Glob|Grep)$/.test(name)) {
44
+ tool = 'read';
45
+ filePath = args.Path || args.file_path || args.path || '';
46
+ } else if (name === 'write_file' || name === 'edit_file' || /^(Edit|Write)$/.test(name)) {
47
+ tool = 'write';
48
+ filePath = args.Path || args.file_path || args.path || '';
49
+ } else if (name === 'search_web' || /^(WebFetch|WebSearch)$/.test(name)) {
50
+ tool = 'web';
51
+ } else if (/^(ask_question|show_message|manage_task)$/.test(name)) {
52
+ tool = 'meta';
53
+ } else if (/^(invoke_subagent|send_message|manage_subagents)$/.test(name)) {
54
+ tool = 'agent';
55
+ }
56
+
57
+ return {
58
+ event,
59
+ tool,
60
+ command,
61
+ path: filePath,
62
+ cwd,
63
+ raw: input
64
+ };
65
+ },
66
+
67
+ emit(decision, event, cwd = process.cwd()) {
68
+ if (decision?.decision === 'allow') {
69
+ const payload = { decision: 'allow' };
70
+ return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 0 };
71
+ }
72
+
73
+ const reason = decision?.reason || `TR-PREFLIGHT gate: action denied. Run: ${decision?.fix || ACK_COMMAND}`;
74
+ const payload = {
75
+ decision: 'deny',
76
+ reason,
77
+ fix: decision?.fix || ACK_COMMAND
78
+ };
79
+ return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 2 };
80
+ },
81
+
82
+ async run({ stdin, decideWith }) {
83
+ const parsed = this.parse(stdin);
84
+ const decision = decideWith({
85
+ tool: parsed.tool,
86
+ command: parsed.command,
87
+ path: parsed.path,
88
+ cwd: parsed.cwd
89
+ });
90
+ return this.emit(decision, parsed.event, parsed.cwd);
91
+ },
92
+
93
+ install({ home = os.homedir(), dryRun = false, remove = false, off = false } = {}) {
94
+ const isRemove = remove || off;
95
+ const geminiDir = path.join(home, '.gemini', 'config');
96
+ const hooksPath = path.join(geminiDir, 'hooks.json');
97
+ const added = [];
98
+ const removed = [];
99
+
100
+ let config = { hooks: {} };
101
+ if (fs.existsSync(hooksPath)) {
102
+ try {
103
+ config = JSON.parse(fs.readFileSync(hooksPath, 'utf8') || '{}');
104
+ } catch (err) {
105
+ throw new Error(`${hooksPath} is not valid JSON; fix it before installing hooks: ${err.message}`);
106
+ }
107
+ }
108
+ config.hooks = config.hooks || {};
109
+ config.hooks.PreToolUse = config.hooks.PreToolUse || [];
110
+
111
+ if (isRemove) {
112
+ const beforeLen = config.hooks.PreToolUse.length;
113
+ config.hooks.PreToolUse = config.hooks.PreToolUse.filter(e =>
114
+ !String(e.command || '').includes('preflight')
115
+ );
116
+ if (config.hooks.PreToolUse.length < beforeLen) {
117
+ removed.push('managed PreToolUse gate');
118
+ }
119
+ if (!dryRun && fs.existsSync(hooksPath)) {
120
+ fs.writeFileSync(hooksPath, `${JSON.stringify(config, null, 2)}\n`);
121
+ }
122
+ return { settings: hooksPath, added: [], removed };
123
+ }
124
+
125
+ const gateCommand = 'total-recall preflight gate --harness agy';
126
+ const hasPreTool = config.hooks.PreToolUse.some(e =>
127
+ String(e.command || '').includes('preflight gate --harness agy')
128
+ );
129
+
130
+ if (!hasPreTool) {
131
+ config.hooks.PreToolUse.push({
132
+ matcher: '*',
133
+ command: gateCommand,
134
+ timeout: 10
135
+ });
136
+ added.push('managed PreToolUse gate');
137
+ }
138
+
139
+ if (!dryRun && added.length) {
140
+ fs.mkdirSync(geminiDir, { recursive: true });
141
+ if (fs.existsSync(hooksPath)) {
142
+ fs.copyFileSync(hooksPath, `${hooksPath}.bak-total-recall`);
143
+ }
144
+ fs.writeFileSync(hooksPath, `${JSON.stringify(config, null, 2)}\n`);
145
+ }
146
+
147
+ return { settings: hooksPath, added };
148
+ },
149
+
150
+ verify({ home = os.homedir() } = {}) {
151
+ const hooksPath = path.join(home, '.gemini', 'config', 'hooks.json');
152
+ let installed = false;
153
+ if (fs.existsSync(hooksPath)) {
154
+ try {
155
+ const config = JSON.parse(fs.readFileSync(hooksPath, 'utf8') || '{}');
156
+ const preHooks = config.hooks?.PreToolUse || [];
157
+ installed = preHooks.some(e => String(e.command || '').includes('preflight gate --harness agy'));
158
+ } catch { /* unparseable */ }
159
+ }
160
+
161
+ const syntheticDeniedCall = {
162
+ event: 'PreToolUse',
163
+ toolCall: {
164
+ name: 'run_command',
165
+ args: { CommandLine: 'rm -rf /tmp/foo' }
166
+ },
167
+ cwd: '/nonexistent-directory-without-receipt'
168
+ };
169
+
170
+ const out = this.emit(
171
+ { decision: 'deny', reason: 'receipt missing', fix: ACK_COMMAND },
172
+ 'PreToolUse',
173
+ syntheticDeniedCall.cwd
174
+ );
175
+
176
+ const exercised = out.exitCode === 2 && out.stdout.includes('"decision":"deny"');
177
+
178
+ return {
179
+ installed,
180
+ exercised,
181
+ level: 'enforced',
182
+ native: out.stdout.trim()
183
+ };
184
+ }
185
+ };
186
+
187
+ export default agyAdapter;
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Claude Code PreToolUse & UserPromptSubmit adapter.
3
+ * Official doc: https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+
9
+ const ACK_COMMAND = 'total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
10
+
11
+ export const claudeAdapter = {
12
+ id: 'claude',
13
+ level: 'enforced',
14
+ source: {
15
+ url: 'https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview',
16
+ retrieved: '2026-11-04',
17
+ note: 'Claude Code hook configuration in settings.json with PreToolUse and UserPromptSubmit'
18
+ },
19
+ readablePaths: [
20
+ /\/\.claude\/projects\/[^/]+\/[^/]+\/tool-results\/[^/]+$/
21
+ ],
22
+ limits: [
23
+ 'UserPromptSubmit hook is advisory-only per ENFORCE-R1 and never blocks turn',
24
+ 'PreToolUse blocks tool executions with exit 2 or permissionDecision: "deny"'
25
+ ],
26
+
27
+ /**
28
+ * Parse Claude Code native hook JSON from stdin into normalized shape:
29
+ * { event, tool, command, path, cwd }
30
+ */
31
+ parse(input = {}) {
32
+ const event = input.hook_event_name || (input.tool_name ? 'PreToolUse' : 'UserPromptSubmit');
33
+ const toolName = input.tool_name || '';
34
+ const cwd = input.cwd || process.cwd();
35
+ const toolInput = input.tool_input || {};
36
+
37
+ let tool = 'other';
38
+ let command;
39
+ let filePath;
40
+
41
+ if (toolName === 'Bash') {
42
+ tool = 'shell';
43
+ command = toolInput.command || '';
44
+ } else if (/^(Read|Glob|Grep)$/.test(toolName)) {
45
+ tool = 'read';
46
+ filePath = toolInput.file_path || toolInput.path || toolInput.pattern || '';
47
+ } else if (/^(Edit|Write|MultiEdit|NotebookEdit)$/.test(toolName)) {
48
+ tool = 'write';
49
+ filePath = toolInput.file_path || toolInput.notebook_path || '';
50
+ } else if (/^(WebFetch|WebSearch)$/.test(toolName)) {
51
+ tool = 'web';
52
+ } else if (/^mcp__ccd_turn__/.test(toolName)) {
53
+ tool = 'ui';
54
+ } else if (/^mcp__/.test(toolName)) {
55
+ tool = 'mcp';
56
+ } else if (toolName === 'Agent') {
57
+ tool = 'agent';
58
+ } else if (/^(ToolSearch|Skill|AskUserQuestion)$/.test(toolName)) {
59
+ tool = 'meta';
60
+ }
61
+
62
+ return {
63
+ event,
64
+ tool,
65
+ command,
66
+ path: filePath,
67
+ cwd,
68
+ raw: input
69
+ };
70
+ },
71
+
72
+ /**
73
+ * Emit Claude Code native response: { stdout, exitCode }
74
+ */
75
+ emit(decision, event, cwd = process.cwd()) {
76
+ if (event === 'UserPromptSubmit') {
77
+ // ENFORCE-R1: Prompt hooks never block
78
+ if (decision?.decision === 'deny') {
79
+ const payload = {
80
+ hookSpecificOutput: {
81
+ hookEventName: 'UserPromptSubmit',
82
+ additionalContext: `TR-PREFLIGHT FAILED for ${cwd}: ${decision.reason || 'check failed'}. First read the full context capsule (never truncate it), then run: ${decision.fix || ACK_COMMAND}`
83
+ }
84
+ };
85
+ return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 0 };
86
+ }
87
+ return { stdout: '', exitCode: 0 };
88
+ }
89
+
90
+ // PreToolUse
91
+ if (decision?.decision === 'allow') {
92
+ return { stdout: '', exitCode: 0 };
93
+ }
94
+
95
+ // Denied
96
+ const payload = {
97
+ hookSpecificOutput: {
98
+ hookEventName: 'PreToolUse',
99
+ permissionDecision: 'deny',
100
+ permissionDecisionReason: decision?.reason || `TR-PREFLIGHT gate: action denied. Run: ${decision?.fix || ACK_COMMAND}`
101
+ }
102
+ };
103
+ return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 2 };
104
+ },
105
+
106
+ /**
107
+ * Run the gate pipeline for Claude Code
108
+ */
109
+ async run({ stdin, decideWith }) {
110
+ const parsed = this.parse(stdin);
111
+ const decision = decideWith({
112
+ tool: parsed.tool,
113
+ command: parsed.command,
114
+ path: parsed.path,
115
+ cwd: parsed.cwd
116
+ });
117
+ return this.emit(decision, parsed.event, parsed.cwd);
118
+ },
119
+
120
+ /**
121
+ * Install Claude Code managed hooks in settings.json, removing the stopgap
122
+ */
123
+ install({ home = os.homedir(), dryRun = false, remove = false, off = false } = {}) {
124
+ const isRemove = remove || off;
125
+ const claudeDir = path.join(home, '.claude');
126
+ const settingsPath = path.join(claudeDir, 'settings.json');
127
+ const stopgapPath = path.join(claudeDir, 'hooks', 'total-recall-preflight.mjs');
128
+ const added = [];
129
+ const removed = [];
130
+
131
+ let settings = {};
132
+ if (fs.existsSync(settingsPath)) {
133
+ try {
134
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8') || '{}');
135
+ } catch (err) {
136
+ throw new Error(`${settingsPath} is not valid JSON; fix it before installing hooks: ${err.message}`);
137
+ }
138
+ }
139
+
140
+ const hooks = settings.hooks || (settings.hooks = {});
141
+
142
+ if (isRemove) {
143
+ for (const event of ['PreToolUse', 'UserPromptSubmit', 'SessionStart', 'PostToolUse']) {
144
+ if (Array.isArray(hooks[event])) {
145
+ const beforeLen = hooks[event].length;
146
+ hooks[event] = hooks[event].filter(entry => {
147
+ const subHooks = entry.hooks || [];
148
+ return !subHooks.some(h => String(h.command || '').includes('preflight') || String(h.command || '').includes('total-recall-preflight'));
149
+ });
150
+ if (hooks[event].length < beforeLen) {
151
+ removed.push(`managed ${event} hook entry`);
152
+ }
153
+ }
154
+ }
155
+ if (!dryRun && (removed.length || fs.existsSync(settingsPath))) {
156
+ fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
157
+ }
158
+ return { settings: settingsPath, added: [], removed };
159
+ }
160
+
161
+ // 1. Remove stopgap references if present
162
+ for (const event of ['PreToolUse', 'UserPromptSubmit', 'SessionStart', 'PostToolUse']) {
163
+ if (Array.isArray(hooks[event])) {
164
+ const beforeLen = hooks[event].length;
165
+ hooks[event] = hooks[event].filter(entry => {
166
+ const subHooks = entry.hooks || [];
167
+ return !subHooks.some(h => String(h.command || '').includes('total-recall-preflight.mjs'));
168
+ });
169
+ if (hooks[event].length < beforeLen) {
170
+ removed.push(`stopgap ${event} hook entry`);
171
+ }
172
+ }
173
+ }
174
+
175
+ // Remove stopgap file on disk if not dryRun
176
+ if (!dryRun && fs.existsSync(stopgapPath)) {
177
+ try {
178
+ fs.unlinkSync(stopgapPath);
179
+ removed.push('total-recall-preflight.mjs file');
180
+ } catch { /* ignore */ }
181
+ }
182
+
183
+ // 2. Add managed preflight gate entries
184
+ const gateCommand = 'total-recall preflight gate --harness claude';
185
+ const remindCommand = 'total-recall preflight remind';
186
+
187
+ // PreToolUse: match all tools
188
+ hooks.PreToolUse = hooks.PreToolUse || [];
189
+ const hasPreTool = hooks.PreToolUse.some(e =>
190
+ (e.hooks || []).some(h => String(h.command || '').includes('preflight gate --harness claude'))
191
+ );
192
+ if (!hasPreTool) {
193
+ hooks.PreToolUse.push({
194
+ matcher: '*',
195
+ hooks: [{
196
+ type: 'command',
197
+ command: gateCommand,
198
+ timeout: 5
199
+ }]
200
+ });
201
+ added.push('managed PreToolUse gate');
202
+ }
203
+
204
+ // UserPromptSubmit: advisory reminder
205
+ hooks.UserPromptSubmit = hooks.UserPromptSubmit || [];
206
+ const hasPrompt = hooks.UserPromptSubmit.some(e =>
207
+ (e.hooks || []).some(h => String(h.command || '').includes('preflight remind'))
208
+ );
209
+ if (!hasPrompt) {
210
+ hooks.UserPromptSubmit.push({
211
+ hooks: [{
212
+ type: 'command',
213
+ command: remindCommand,
214
+ timeout: 5
215
+ }]
216
+ });
217
+ added.push('managed UserPromptSubmit reminder');
218
+ }
219
+
220
+ if (!dryRun && (added.length || removed.length)) {
221
+ fs.mkdirSync(claudeDir, { recursive: true });
222
+ if (fs.existsSync(settingsPath)) {
223
+ fs.copyFileSync(settingsPath, `${settingsPath}.bak-total-recall`);
224
+ }
225
+ fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
226
+ }
227
+
228
+ return { settings: settingsPath, added, removed };
229
+ },
230
+
231
+ /**
232
+ * Verify installed hook with a synthetic denied tool call
233
+ */
234
+ verify({ home = os.homedir() } = {}) {
235
+ const settingsPath = path.join(home, '.claude', 'settings.json');
236
+ if (!fs.existsSync(settingsPath)) {
237
+ return { installed: false, exercised: false, reason: 'settings.json not found' };
238
+ }
239
+ let settings;
240
+ try {
241
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8') || '{}');
242
+ } catch {
243
+ return { installed: false, exercised: false, reason: 'settings.json is unparseable' };
244
+ }
245
+ const preHooks = settings.hooks?.PreToolUse || [];
246
+ const installed = preHooks.some(e =>
247
+ (e.hooks || []).some(h => String(h.command || '').includes('preflight gate --harness claude'))
248
+ );
249
+
250
+ // Exercise with synthetic denied payload
251
+ const syntheticDeniedCall = {
252
+ hook_event_name: 'PreToolUse',
253
+ tool_name: 'Bash',
254
+ tool_input: { command: 'git status' },
255
+ cwd: '/nonexistent-directory-without-receipt'
256
+ };
257
+
258
+ const out = this.emit(
259
+ { decision: 'deny', reason: 'receipt missing', fix: ACK_COMMAND },
260
+ 'PreToolUse',
261
+ syntheticDeniedCall.cwd
262
+ );
263
+
264
+ const exercised = out.exitCode === 2 && out.stdout.includes('"permissionDecision":"deny"');
265
+
266
+ return {
267
+ installed,
268
+ exercised,
269
+ level: 'enforced',
270
+ native: out.stdout.trim()
271
+ };
272
+ }
273
+ };
274
+
275
+ export default claudeAdapter;