0xray 4.0.36 → 4.0.37

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 (169) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.xray/features.json +1 -1
  3. package/dist/CHANGELOG.md +15 -0
  4. package/dist/analytics/emerging-pattern-detector.d.ts +12 -1
  5. package/dist/analytics/emerging-pattern-detector.js +37 -12
  6. package/dist/analytics/pattern-learning-engine.js +1 -9
  7. package/dist/analytics/predictive-analytics.js +51 -33
  8. package/dist/analytics/prompt-pattern-analyzer.d.ts +1 -0
  9. package/dist/analytics/prompt-pattern-analyzer.js +39 -20
  10. package/dist/analytics/routing-performance-analyzer.d.ts +2 -0
  11. package/dist/analytics/routing-performance-analyzer.js +89 -37
  12. package/dist/analytics/simple-pattern-analyzer.d.ts +4 -0
  13. package/dist/analytics/simple-pattern-analyzer.js +66 -28
  14. package/dist/architect/architect-tools.d.ts +3 -0
  15. package/dist/architect/architect-tools.js +22 -0
  16. package/dist/cli/commands/grok-install.js +1 -1
  17. package/dist/cli/commands/hermes-install.js +11 -5
  18. package/dist/cli/commands/openclaw-install.js +13 -7
  19. package/dist/cli/commands/opencode-install.js +7 -4
  20. package/dist/cli/commands/suit-monitor.d.ts +30 -0
  21. package/dist/cli/commands/suit-monitor.js +128 -0
  22. package/dist/cli/index.js +52 -15
  23. package/dist/cli/suit-pulse.d.ts +29 -0
  24. package/dist/cli/suit-pulse.js +270 -0
  25. package/dist/delegation/agent-delegator.d.ts +4 -2
  26. package/dist/delegation/agent-delegator.js +46 -27
  27. package/dist/delegation/agent-expertise.js +2 -1
  28. package/dist/delegation/analytics/outcome-tracker.d.ts +6 -0
  29. package/dist/delegation/analytics/outcome-tracker.js +17 -3
  30. package/dist/delegation/ast-code-parser.d.ts +5 -0
  31. package/dist/delegation/ast-code-parser.js +53 -48
  32. package/dist/delegation/codebase-context-analyzer.d.ts +3 -7
  33. package/dist/delegation/codebase-context-analyzer.js +188 -159
  34. package/dist/delegation/session-coordinator.js +3 -2
  35. package/dist/enforcement/loaders/codex-loader.js +18 -14
  36. package/dist/enforcement/validators/architecture-validators.js +115 -9
  37. package/dist/governance/codex-policy.service.d.ts +12 -2
  38. package/dist/governance/codex-policy.service.js +91 -16
  39. package/dist/integrations/grok/hooks/session-start.js +6 -6
  40. package/dist/integrations/hooks/goggles-pipeline.mjs +169 -28
  41. package/dist/integrations/hooks/goggles-planes.json +172 -8
  42. package/dist/integrations/hooks/plates.cjs +138 -0
  43. package/dist/integrations/hooks/station-hook-runtime.cjs +63 -5
  44. package/dist/integrations/hooks/write-home.cjs +59 -0
  45. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  46. package/dist/mcps/architect-tools.server.d.ts +6 -0
  47. package/dist/mcps/architect-tools.server.js +19 -18
  48. package/dist/mcps/orchestrator/server.d.ts +4 -3
  49. package/dist/mcps/orchestrator/server.js +29 -17
  50. package/dist/memory-routing/lookup-cache.d.ts +8 -0
  51. package/dist/memory-routing/lookup-cache.js +72 -0
  52. package/dist/memory-routing/plates.d.ts +1 -1
  53. package/dist/memory-routing/plates.js +26 -0
  54. package/dist/memory-routing/provider-loader.js +3 -0
  55. package/dist/metrics/agent-metrics.d.ts +14 -5
  56. package/dist/metrics/agent-metrics.js +362 -224
  57. package/dist/nucleus/synthesis-consult-receipt.js +38 -16
  58. package/dist/orchestrator/enhanced-multi-agent-orchestrator.d.ts +5 -0
  59. package/dist/orchestrator/enhanced-multi-agent-orchestrator.js +9 -0
  60. package/dist/orchestrator/multi-agent-orchestration-coordinator.js +1 -2
  61. package/dist/orchestrator/orchestrator.js +1 -2
  62. package/dist/processors/implementations/agents-md-validation-processor.d.ts +0 -1
  63. package/dist/processors/implementations/agents-md-validation-processor.js +0 -11
  64. package/dist/processors/implementations/console-log-guard-processor.js +39 -50
  65. package/dist/processors/implementations/performance-budget-processor.d.ts +0 -5
  66. package/dist/processors/implementations/performance-budget-processor.js +29 -38
  67. package/dist/processors/implementations/storytelling-trigger-processor.d.ts +5 -0
  68. package/dist/processors/implementations/storytelling-trigger-processor.js +69 -16
  69. package/dist/processors/implementations/test-auto-creation-processor.js +39 -36
  70. package/dist/reporting/framework-reporting-system.d.ts +3 -0
  71. package/dist/reporting/framework-reporting-system.js +61 -8
  72. package/dist/reporting/log-parser.js +188 -105
  73. package/dist/reporting/metrics.js +42 -18
  74. package/dist/security/prompt-security-validator.js +8 -7
  75. package/dist/security/security-auditor.d.ts +4 -0
  76. package/dist/security/security-auditor.js +132 -57
  77. package/dist/security/security-hardener.d.ts +2 -0
  78. package/dist/security/security-hardener.js +31 -6
  79. package/dist/security/security-headers.d.ts +2 -0
  80. package/dist/security/security-headers.js +23 -21
  81. package/dist/security/security-scanner.js +18 -4
  82. package/dist/session/session-cleanup-manager.d.ts +2 -0
  83. package/dist/session/session-cleanup-manager.js +29 -8
  84. package/dist/session/session-monitor.d.ts +14 -4
  85. package/dist/session/session-monitor.js +208 -106
  86. package/dist/session/session-state-manager.d.ts +11 -0
  87. package/dist/session/session-state-manager.js +90 -8
  88. package/docs-site/docs/plates/activity-log.md +38 -0
  89. package/docs-site/docs/plates/cursor-compact.md +35 -0
  90. package/docs-site/docs/plates/grok-compact.md +35 -0
  91. package/docs-site/docs/plates/index.md +25 -3
  92. package/docs-site/docs/plates/inference-files.md +35 -0
  93. package/docs-site/docs/plates/kept-line.md +35 -0
  94. package/docs-site/docs/plates/lens-gate.md +35 -0
  95. package/docs-site/docs/plates/lens-page.md +35 -0
  96. package/docs-site/docs/plates/notes-page.md +35 -0
  97. package/docs-site/docs/plates/package-face.md +35 -0
  98. package/docs-site/docs/plates/payload-heat.md +35 -0
  99. package/docs-site/docs/plates/pickup-stamp.md +35 -0
  100. package/docs-site/docs/plates/pre-tool.md +35 -0
  101. package/docs-site/docs/plates/record-map.md +41 -0
  102. package/docs-site/docs/plates/reflection-page.md +35 -0
  103. package/docs-site/docs/plates/review.md +57 -0
  104. package/docs-site/docs/plates/session-capture.md +38 -0
  105. package/docs-site/docs/plates/site-manual.md +35 -0
  106. package/docs-site/docs/plates/stamp-plate.md +35 -0
  107. package/docs-site/docs/plates/station-card.md +35 -0
  108. package/docs-site/docs/plates/station-heat.md +35 -0
  109. package/docs-site/docs/plates/suit-organs.md +38 -0
  110. package/docs-site/docs/plates/suit-settings.md +35 -0
  111. package/docs-site/docs/plates/suit-wear.md +37 -0
  112. package/docs-site/docs/plates/trail-state.md +35 -0
  113. package/docs-site/docs/plates/work-fresh.md +35 -0
  114. package/docs-site/docs/plates/write-home.md +38 -0
  115. package/package.json +3 -2
  116. package/scripts/node/install-bridges.cjs +302 -78
  117. package/scripts/node/postinstall.cjs +14 -3
  118. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  119. package/src/mcps/architect-tools.server.ts +25 -20
  120. package/src/mcps/orchestrator/server.ts +33 -19
  121. package/xray/features.json +1 -1
  122. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +0 -1
  123. package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +0 -1
  124. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.d.ts.map +0 -1
  125. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.js.map +0 -1
  126. package/vendor/@0xray/repertoire/dist/index.d.ts.map +0 -1
  127. package/vendor/@0xray/repertoire/dist/index.js.map +0 -1
  128. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts.map +0 -1
  129. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js.map +0 -1
  130. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.d.ts.map +0 -1
  131. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.js.map +0 -1
  132. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.d.ts.map +0 -1
  133. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.js.map +0 -1
  134. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +0 -1
  135. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +0 -1
  136. package/vendor/@0xray/repertoire/dist/mcp/server.d.ts.map +0 -1
  137. package/vendor/@0xray/repertoire/dist/mcp/server.js.map +0 -1
  138. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.d.ts.map +0 -1
  139. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js.map +0 -1
  140. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.d.ts.map +0 -1
  141. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.js.map +0 -1
  142. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +0 -1
  143. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +0 -1
  144. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.d.ts.map +0 -1
  145. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.js.map +0 -1
  146. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +0 -1
  147. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +0 -1
  148. package/vendor/@0xray/repertoire/dist/paths.d.ts.map +0 -1
  149. package/vendor/@0xray/repertoire/dist/paths.js.map +0 -1
  150. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +0 -1
  151. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +0 -1
  152. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +0 -1
  153. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +0 -1
  154. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts.map +0 -1
  155. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js.map +0 -1
  156. package/vendor/@0xray/repertoire/dist/registry/confidence-decay.d.ts.map +0 -1
  157. package/vendor/@0xray/repertoire/dist/registry/confidence-decay.js.map +0 -1
  158. package/vendor/@0xray/repertoire/dist/registry/signal-prune.d.ts.map +0 -1
  159. package/vendor/@0xray/repertoire/dist/registry/signal-prune.js.map +0 -1
  160. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.d.ts.map +0 -1
  161. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.js.map +0 -1
  162. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts.map +0 -1
  163. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js.map +0 -1
  164. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.d.ts.map +0 -1
  165. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.js.map +0 -1
  166. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.d.ts.map +0 -1
  167. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.js.map +0 -1
  168. package/vendor/@0xray/repertoire/dist/types.d.ts.map +0 -1
  169. package/vendor/@0xray/repertoire/dist/types.js.map +0 -1
@@ -35,6 +35,110 @@ const {
35
35
  XRAY_MCP_SERVERS,
36
36
  } = require("./bridge-mcp-wiring.cjs");
37
37
 
38
+ const wearIo = { writes: 0, skips: 0 };
39
+
40
+ function resetWearIo() {
41
+ wearIo.writes = 0;
42
+ wearIo.skips = 0;
43
+ }
44
+
45
+ function readWearIo() {
46
+ return { writes: wearIo.writes, skips: wearIo.skips };
47
+ }
48
+
49
+ function sameFileBytes(filePath, body) {
50
+ let current;
51
+ try {
52
+ current = fs.readFileSync(filePath);
53
+ } catch {
54
+ return false;
55
+ }
56
+ const next = Buffer.isBuffer(body) ? body : Buffer.from(body);
57
+ return current.length === next.length && current.equals(next);
58
+ }
59
+
60
+ function writeFileIfChanged(filePath, body) {
61
+ if (sameFileBytes(filePath, body)) {
62
+ wearIo.skips += 1;
63
+ return false;
64
+ }
65
+ fs.writeFileSync(filePath, body);
66
+ wearIo.writes += 1;
67
+ return true;
68
+ }
69
+
70
+ function fileMode(filePath) {
71
+ try {
72
+ return fs.statSync(filePath).mode & 0o777;
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ /** Node copyFileSync/cpSync still copy mode when the bytes already match. */
79
+ function adoptSourceMode(src, dest) {
80
+ const srcMode = fileMode(src);
81
+ const destMode = fileMode(dest);
82
+ if (srcMode === null || destMode === null || srcMode === destMode) return false;
83
+ fs.chmodSync(dest, srcMode);
84
+ wearIo.writes += 1;
85
+ return true;
86
+ }
87
+
88
+ function copyFileIfChanged(src, dest) {
89
+ let srcBytes;
90
+ try {
91
+ srcBytes = fs.readFileSync(src);
92
+ } catch {
93
+ fs.copyFileSync(src, dest);
94
+ wearIo.writes += 1;
95
+ return true;
96
+ }
97
+ if (sameFileBytes(dest, srcBytes)) {
98
+ if (adoptSourceMode(src, dest)) return true;
99
+ wearIo.skips += 1;
100
+ return false;
101
+ }
102
+ fs.copyFileSync(src, dest);
103
+ wearIo.writes += 1;
104
+ return true;
105
+ }
106
+
107
+ /** cpSync without rewriting files whose bytes already match. Extra dest files stay. */
108
+ function copyEntryIfChanged(src, dest) {
109
+ const srcStat = fs.lstatSync(src);
110
+ if (srcStat.isDirectory()) {
111
+ if (fs.existsSync(dest) && !fs.lstatSync(dest).isDirectory()) {
112
+ fs.rmSync(dest, { recursive: true, force: true });
113
+ }
114
+ fs.mkdirSync(dest, { recursive: true });
115
+ for (const name of fs.readdirSync(src)) {
116
+ copyEntryIfChanged(path.join(src, name), path.join(dest, name));
117
+ }
118
+ return;
119
+ }
120
+ if (!srcStat.isFile()) {
121
+ fs.cpSync(src, dest, { force: true });
122
+ wearIo.writes += 1;
123
+ return;
124
+ }
125
+ copyFileIfChanged(src, dest);
126
+ }
127
+
128
+ function chmodIfNeeded(filePath, mode) {
129
+ try {
130
+ if ((fs.statSync(filePath).mode & 0o777) === mode) {
131
+ wearIo.skips += 1;
132
+ return false;
133
+ }
134
+ fs.chmodSync(filePath, mode);
135
+ wearIo.writes += 1;
136
+ return true;
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+
38
142
  const SKIP_DIRS = new Set(["node_modules", "logs"]);
39
143
  const MERGE_FILES = new Set(["enforcer-config.json"]);
40
144
  const KEEP_IF_EXISTS = new Set([".yml", ".yaml", ".md"]);
@@ -116,9 +220,11 @@ function syncBuiltinSkills(targetSkillsDir, packageRoot) {
116
220
  const destMd = path.join(targetSkillsDir, entry.name, "SKILL.md");
117
221
  const destDir = path.dirname(destMd);
118
222
  if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
119
- if (fs.existsSync(destMd) && fs.statSync(skillMd).mtime <= fs.statSync(destMd).mtime) continue;
120
- fs.copyFileSync(skillMd, destMd);
121
- copied++;
223
+ if (fs.existsSync(destMd) && fs.statSync(skillMd).mtime <= fs.statSync(destMd).mtime) {
224
+ wearIo.skips += 1;
225
+ continue;
226
+ }
227
+ if (copyFileIfChanged(skillMd, destMd)) copied++;
122
228
  }
123
229
  } catch {
124
230
  // best-effort
@@ -146,17 +252,17 @@ function copyTree(src, dest, relPath = "") {
146
252
  const srcData = JSON.parse(fs.readFileSync(srcPath, "utf8"));
147
253
  if (fs.existsSync(destPath)) {
148
254
  const destData = JSON.parse(fs.readFileSync(destPath, "utf8"));
149
- fs.writeFileSync(destPath, JSON.stringify(deepMerge(srcData, destData), null, 2));
255
+ writeFileIfChanged(destPath, JSON.stringify(deepMerge(srcData, destData), null, 2));
150
256
  } else {
151
- fs.copyFileSync(srcPath, destPath);
257
+ copyFileIfChanged(srcPath, destPath);
152
258
  }
153
259
  } catch {
154
- fs.copyFileSync(srcPath, destPath);
260
+ copyFileIfChanged(srcPath, destPath);
155
261
  }
156
262
  } else if (KEEP_IF_EXISTS.has(path.extname(srcPath)) && fs.existsSync(destPath)) {
157
263
  continue;
158
264
  } else {
159
- fs.copyFileSync(srcPath, destPath);
265
+ copyFileIfChanged(srcPath, destPath);
160
266
  }
161
267
  }
162
268
  }
@@ -168,11 +274,13 @@ function copyPluginDir(src, dest) {
168
274
  fs.rmSync(dest, { recursive: true, force: true });
169
275
  }
170
276
  fs.cpSync(src, dest, { recursive: true, force: true });
277
+ wearIo.writes += 1;
171
278
  return true;
172
279
  } catch (e) {
173
280
  try {
174
281
  fs.rmSync(dest, { recursive: true, force: true });
175
282
  fs.cpSync(src, dest, { recursive: true, force: true });
283
+ wearIo.writes += 1;
176
284
  return true;
177
285
  } catch {
178
286
  throw e;
@@ -182,9 +290,8 @@ function copyPluginDir(src, dest) {
182
290
 
183
291
  function writePluginMcpJson(pluginDir, targetDir, log, label) {
184
292
  if (!fs.existsSync(pluginDir)) return;
185
- const { buildPluginMcpJson } = require("./bridge-mcp-wiring.cjs");
186
293
  const dest = path.join(pluginDir, ".mcp.json");
187
- fs.writeFileSync(dest, JSON.stringify(buildPluginMcpJson(targetDir), null, 2) + "\n");
294
+ writeFileIfChanged(dest, pluginMcpBody(targetDir));
188
295
  log(label, "plugin .mcp.json patched for consumer", "info");
189
296
  }
190
297
 
@@ -269,33 +376,7 @@ function patchGrokHooks(pluginDir, packageRoot, targetDir, log, label) {
269
376
  try {
270
377
  const hooks = JSON.parse(fs.readFileSync(hooksPath, "utf8"));
271
378
  if (!fs.existsSync(hookScript)) return;
272
- patchGrokHookEntry(hooks.hooks?.PreToolUse?.[0]?.hooks?.[0], packageRoot, targetDir, "pre-tool-use.js");
273
- patchGrokHookEntry(hooks.hooks?.SessionStart?.[0]?.hooks?.[0], packageRoot, targetDir, "session-start.js");
274
- patchGrokHookEntry(
275
- hooks.hooks?.UserPromptSubmit?.[0]?.hooks?.[0],
276
- packageRoot,
277
- targetDir,
278
- "session-start.js",
279
- "--hook-event=user_prompt_submit",
280
- );
281
- patchGrokHookEntry(hooks.hooks?.PostToolUse?.[0]?.hooks?.[0], packageRoot, targetDir, "post-tool-use.js");
282
- ensureGrokHookEvent(hooks, "PreCompact");
283
- ensureGrokHookEvent(hooks, "PostCompact");
284
- patchGrokHookEntry(
285
- hooks.hooks?.PreCompact?.[0]?.hooks?.[0],
286
- packageRoot,
287
- targetDir,
288
- "session-start.js",
289
- "--hook-event=pre_compact",
290
- );
291
- patchGrokHookEntry(
292
- hooks.hooks?.PostCompact?.[0]?.hooks?.[0],
293
- packageRoot,
294
- targetDir,
295
- "session-start.js",
296
- "--hook-event=post_compact",
297
- );
298
- fs.writeFileSync(hooksPath, JSON.stringify(hooks, null, 2) + "\n");
379
+ writeFileIfChanged(hooksPath, patchedGrokHooksBody(hooks, packageRoot, targetDir));
299
380
  writeGrokDiscoveredHooks(targetDir, hooksPath, log, label);
300
381
  log(label, "hooks.json patched → Grok command-string enforcement gate", "info");
301
382
  } catch (e) {
@@ -310,7 +391,7 @@ function writeGrokDiscoveredHooks(targetDir, hooksPath, log, label) {
310
391
  const destDir = path.join(targetDir, ".grok", "hooks");
311
392
  fs.mkdirSync(destDir, { recursive: true });
312
393
  const dest = path.join(destDir, "0xray.json");
313
- fs.copyFileSync(hooksPath, dest);
394
+ copyFileIfChanged(hooksPath, dest);
314
395
  log(label, "Grok discovered hooks → .grok/hooks/0xray.json", "info", { dest });
315
396
  } catch (e) {
316
397
  log(label, "Grok discovered hooks copy failed", "warn", { error: e.message });
@@ -325,10 +406,10 @@ function copyOpencodePlugin(packageRoot, opencodeDest, log) {
325
406
  if (!fs.existsSync(pluginDestDir)) fs.mkdirSync(pluginDestDir, { recursive: true });
326
407
  const scopePkg = path.join(opencodeDest, "package.json");
327
408
  if (!fs.existsSync(scopePkg)) {
328
- fs.writeFileSync(scopePkg, `${JSON.stringify({ type: "module" })}\n`);
409
+ writeFileIfChanged(scopePkg, `${JSON.stringify({ type: "module" })}\n`);
329
410
  }
330
411
  const shim = `export { default } from ${JSON.stringify(pluginSource)};\n`;
331
- fs.writeFileSync(pluginDest, shim);
412
+ writeFileIfChanged(pluginDest, shim);
332
413
  log("opencode-bridge", "plugin shim written", "info");
333
414
  }
334
415
 
@@ -411,6 +492,94 @@ function registerGrokMcpServers(targetDir, log, pluginDirs) {
411
492
  }
412
493
  }
413
494
 
495
+
496
+ function pluginMcpBody(targetDir) {
497
+ const { buildPluginMcpJson } = require("./bridge-mcp-wiring.cjs");
498
+ return JSON.stringify(buildPluginMcpJson(targetDir), null, 2) + "\n";
499
+ }
500
+
501
+ function patchedGrokHooksBody(hooks, packageRoot, targetDir) {
502
+ patchGrokHookEntry(hooks.hooks?.PreToolUse?.[0]?.hooks?.[0], packageRoot, targetDir, "pre-tool-use.js");
503
+ patchGrokHookEntry(hooks.hooks?.SessionStart?.[0]?.hooks?.[0], packageRoot, targetDir, "session-start.js");
504
+ patchGrokHookEntry(
505
+ hooks.hooks?.UserPromptSubmit?.[0]?.hooks?.[0],
506
+ packageRoot,
507
+ targetDir,
508
+ "session-start.js",
509
+ "--hook-event=user_prompt_submit",
510
+ );
511
+ patchGrokHookEntry(hooks.hooks?.PostToolUse?.[0]?.hooks?.[0], packageRoot, targetDir, "post-tool-use.js");
512
+ ensureGrokHookEvent(hooks, "PreCompact");
513
+ ensureGrokHookEvent(hooks, "PostCompact");
514
+ patchGrokHookEntry(
515
+ hooks.hooks?.PreCompact?.[0]?.hooks?.[0],
516
+ packageRoot,
517
+ targetDir,
518
+ "session-start.js",
519
+ "--hook-event=pre_compact",
520
+ );
521
+ patchGrokHookEntry(
522
+ hooks.hooks?.PostCompact?.[0]?.hooks?.[0],
523
+ packageRoot,
524
+ targetDir,
525
+ "session-start.js",
526
+ "--hook-event=post_compact",
527
+ );
528
+ return JSON.stringify(hooks, null, 2) + "\n";
529
+ }
530
+
531
+ function listTreeFiles(root) {
532
+ if (!fs.existsSync(root)) return null;
533
+ const files = new Map();
534
+ const stack = [""];
535
+ while (stack.length) {
536
+ const rel = stack.pop();
537
+ const abs = rel ? path.join(root, rel) : root;
538
+ let entries;
539
+ try {
540
+ entries = fs.readdirSync(abs, { withFileTypes: true });
541
+ } catch {
542
+ return null;
543
+ }
544
+ for (const entry of entries) {
545
+ const next = rel ? `${rel}/${entry.name}` : entry.name;
546
+ if (entry.isDirectory()) stack.push(next);
547
+ else if (entry.isFile()) files.set(next, path.join(abs, entry.name));
548
+ else return null;
549
+ }
550
+ }
551
+ return files;
552
+ }
553
+
554
+ /** Dest already holds the post-patch plugin, so a repeat wear must not rm+cp it. */
555
+ function grokPluginSettled(sourceDir, dest, packageRoot, targetDir) {
556
+ const sourceFiles = listTreeFiles(sourceDir);
557
+ const destFiles = listTreeFiles(dest);
558
+ if (!sourceFiles || !destFiles) return false;
559
+ const desired = new Map();
560
+ for (const [rel, abs] of sourceFiles) desired.set(rel, fs.readFileSync(abs));
561
+ desired.set(".mcp.json", Buffer.from(pluginMcpBody(targetDir)));
562
+ const hookScript = path.join(packageRoot, "scripts", "mjs", "run-grok-hook.mjs");
563
+ if (desired.has("hooks/hooks.json") && fs.existsSync(hookScript)) {
564
+ try {
565
+ const hooks = JSON.parse(desired.get("hooks/hooks.json").toString("utf8"));
566
+ desired.set("hooks/hooks.json", Buffer.from(patchedGrokHooksBody(hooks, packageRoot, targetDir)));
567
+ } catch {
568
+ return false;
569
+ }
570
+ }
571
+ if (desired.size !== destFiles.size) return false;
572
+ for (const [rel, body] of desired) {
573
+ const abs = destFiles.get(rel);
574
+ if (!abs || !sameFileBytes(abs, body)) return false;
575
+ }
576
+ for (const [rel, abs] of sourceFiles) {
577
+ const destAbs = destFiles.get(rel);
578
+ if (destAbs) adoptSourceMode(abs, destAbs);
579
+ }
580
+ return true;
581
+ }
582
+
414
583
  function installGrokBridge(targetDir, packageRoot, log, opts) {
415
584
  const sourceDir = findGrokPluginSource(packageRoot);
416
585
  if (!sourceDir) {
@@ -440,8 +609,16 @@ function installGrokBridge(targetDir, packageRoot, log, opts) {
440
609
  });
441
610
  continue;
442
611
  }
443
- if (copyPluginDir(sourceDir, dest)) {
444
- const rel = dest.startsWith(machine) ? dest.replace(machine, "~") : path.relative(targetDir, dest);
612
+ const rel = dest.startsWith(machine) ? dest.replace(machine, "~") : path.relative(targetDir, dest);
613
+ const settled = grokPluginSettled(sourceDir, dest, packageRoot, targetDir);
614
+ let placed = false;
615
+ if (settled) {
616
+ wearIo.skips += 1;
617
+ placed = true;
618
+ } else {
619
+ placed = copyPluginDir(sourceDir, dest);
620
+ }
621
+ if (placed) {
445
622
  log("grok-bridge", "plugin copied", "info", { path: rel || dest });
446
623
  writePluginMcpJson(dest, targetDir, log, "grok-bridge");
447
624
  patchGrokHooks(dest, packageRoot, targetDir, log, "grok-bridge");
@@ -483,16 +660,20 @@ function installHermesBridge(targetDir, packageRoot, log) {
483
660
  log("hermes-bridge", "skip machine ~/.hermes plugin — isolated HOME", "info");
484
661
  }
485
662
  fs.mkdirSync(targetPluginDir, { recursive: true });
663
+ const hermesMcp = pluginMcpBody(targetDir);
486
664
  for (const entry of fs.readdirSync(sourceDir)) {
487
665
  const src = path.join(sourceDir, entry);
488
666
  const dst = path.join(targetPluginDir, entry);
489
- if (fs.statSync(src).isDirectory()) {
490
- fs.cpSync(src, dst, { recursive: true, force: true });
491
- } else {
492
- fs.copyFileSync(src, dst);
667
+ if (entry === ".mcp.json" && sameFileBytes(dst, hermesMcp)) {
668
+ if (!adoptSourceMode(src, dst)) wearIo.skips += 1;
669
+ continue;
493
670
  }
671
+ copyEntryIfChanged(src, dst);
494
672
  }
495
- copyHermesFindProjectRootHelper(packageRoot, targetPluginDir);
673
+ const helperSrc = path.join(packageRoot, "scripts", "helpers", "find-project-root.mjs");
674
+ const helperDst = path.join(targetPluginDir, "scripts", "helpers", "find-project-root.mjs");
675
+ if (!fs.existsSync(helperDst)) copyHermesFindProjectRootHelper(packageRoot, targetPluginDir);
676
+ else if (fs.existsSync(helperSrc)) copyFileIfChanged(helperSrc, helperDst);
496
677
  if (!ephemeral && !isolated && copyHermesHookRuntimes(packageRoot)) {
497
678
  log("hermes-bridge", "hook runtimes copied", "info", { path: "~/.hermes/plugins/hooks" });
498
679
  }
@@ -510,7 +691,7 @@ function installHermesBridge(targetDir, packageRoot, log) {
510
691
 
511
692
  const rootMarker = path.join(targetPluginDir, "xray-consumer-root.txt");
512
693
  if (!isEphemeralInstallRoot(targetDir)) {
513
- fs.writeFileSync(rootMarker, targetDir + "\n");
694
+ writeFileIfChanged(rootMarker, targetDir + "\n");
514
695
  log("hermes-bridge", "consumer root marker written", "info");
515
696
  } else {
516
697
  log("hermes-bridge", "skip machine consumer marker — ephemeral consumer", "info");
@@ -555,7 +736,7 @@ function installOpenclawBridge(targetDir, packageRoot, log) {
555
736
  debug: false,
556
737
  logLevel: "info",
557
738
  };
558
- fs.writeFileSync(configPath, JSON.stringify(sampleConfig, null, 2) + "\n");
739
+ writeFileIfChanged(configPath, JSON.stringify(sampleConfig, null, 2) + "\n");
559
740
  log("openclaw-bridge", "config created", "info", { path: ".xray/config/openclaw.json" });
560
741
  }
561
742
 
@@ -623,31 +804,40 @@ function readPackageVersion(packageRoot) {
623
804
  }
624
805
 
625
806
  function writeJsonFile(filePath, data) {
626
- fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`);
807
+ const body = `${JSON.stringify(data, null, 2)}\n`;
808
+ if (sameFileBytes(filePath, body)) {
809
+ wearIo.skips += 1;
810
+ return false;
811
+ }
812
+ fs.writeFileSync(filePath, body);
813
+ wearIo.writes += 1;
814
+ return true;
627
815
  }
628
816
 
629
817
  function applyResolvedMemoryRouting(dst, targetDir) {
630
818
  try {
631
819
  const features = JSON.parse(fs.readFileSync(dst, "utf8"));
632
820
  const next = enableMemoryRoutingIfResolves(features, targetDir);
633
- if (next.changed) writeJsonFile(dst, next.features);
821
+ if (!next.changed) return false;
822
+ return writeJsonFile(dst, next.features);
634
823
  } catch {
635
824
  /* leftover default stays off when features.json is unreadable */
825
+ return false;
636
826
  }
637
827
  }
638
828
 
639
829
  function deployXrayConfigFile(file, src, dst, packageRoot, targetDir) {
640
830
  if (!fs.existsSync(dst)) {
641
- fs.copyFileSync(src, dst);
831
+ copyFileIfChanged(src, dst);
642
832
  if (file === "features.json") applyResolvedMemoryRouting(dst, targetDir);
643
833
  return true;
644
834
  }
645
835
 
646
836
  if (file === "features.schema.json") {
647
837
  if (fs.statSync(src).mtime > fs.statSync(dst).mtime) {
648
- fs.copyFileSync(src, dst);
649
- return true;
838
+ return copyFileIfChanged(src, dst);
650
839
  }
840
+ wearIo.skips += 1;
651
841
  return false;
652
842
  }
653
843
 
@@ -672,26 +862,26 @@ function deployXrayConfigFile(file, src, dst, packageRoot, targetDir) {
672
862
  profile: "guided",
673
863
  };
674
864
  }
675
- writeJsonFile(dst, merged);
676
- applyResolvedMemoryRouting(dst, targetDir);
677
- return true;
865
+ const wroteMerged = writeJsonFile(dst, merged);
866
+ const wroteRouting = applyResolvedMemoryRouting(dst, targetDir);
867
+ return wroteMerged || wroteRouting;
678
868
  }
679
- writeJsonFile(dst, merged);
680
- return true;
869
+ return writeJsonFile(dst, merged);
681
870
  } catch {
682
871
  if (fs.statSync(src).mtime > fs.statSync(dst).mtime) {
683
- fs.copyFileSync(src, dst);
872
+ const wrote = copyFileIfChanged(src, dst);
684
873
  if (file === "features.json") applyResolvedMemoryRouting(dst, targetDir);
685
- return true;
874
+ return wrote;
686
875
  }
876
+ wearIo.skips += 1;
687
877
  return false;
688
878
  }
689
879
  }
690
880
 
691
881
  if (fs.statSync(src).mtime > fs.statSync(dst).mtime) {
692
- fs.copyFileSync(src, dst);
693
- return true;
882
+ return copyFileIfChanged(src, dst);
694
883
  }
884
+ wearIo.skips += 1;
695
885
  return false;
696
886
  }
697
887
 
@@ -816,12 +1006,8 @@ function fastenCursorHookScripts(targetDir, packageRoot, log) {
816
1006
  const src = path.join(srcDir, name);
817
1007
  if (!fs.existsSync(src)) continue;
818
1008
  const dest = path.join(destDir, name);
819
- fs.copyFileSync(src, dest);
820
- try {
821
- fs.chmodSync(dest, 0o755);
822
- } catch {
823
- // chmod is best-effort on hosts that ignore mode
824
- }
1009
+ copyFileIfChanged(src, dest);
1010
+ chmodIfNeeded(dest, 0o755);
825
1011
  copied++;
826
1012
  }
827
1013
  return copied;
@@ -888,7 +1074,7 @@ function fastenCursorHooksAtUnsafe(targetDir, packageRoot, log) {
888
1074
  if (previousText !== body) {
889
1075
  saveCursorWearSnapshot(targetDir, previousText, body);
890
1076
  fs.mkdirSync(path.dirname(dest), { recursive: true });
891
- fs.writeFileSync(dest, body);
1077
+ writeFileIfChanged(dest, body);
892
1078
  }
893
1079
  log("cursor-bridge", existing ? "hooks.json rewritten cloud-safe" : "hooks.json fastened", "info", {
894
1080
  path: dest,
@@ -1784,7 +1970,7 @@ function removeWearExcludeLine(targetDir) {
1784
1970
  const lines = fs.readFileSync(excludeFile, "utf8").split("\n");
1785
1971
  const next = lines.filter((line) => line.trim() !== pattern);
1786
1972
  if (next.length === lines.length) return;
1787
- fs.writeFileSync(excludeFile, next.join("\n"));
1973
+ writeFileIfChanged(excludeFile, next.join("\n"));
1788
1974
  }
1789
1975
 
1790
1976
  function saveCursorWearSnapshot(targetDir, previousText, writtenBody) {
@@ -1804,17 +1990,17 @@ function saveCursorWearSnapshot(targetDir, previousText, writtenBody) {
1804
1990
  cursorDirExisted = fs.existsSync(path.join(targetDir, ".cursor"));
1805
1991
  }
1806
1992
  const backup = existed ? userBytesBeforeWear(previousText) : null;
1807
- if (backup != null) fs.writeFileSync(paths.backup, backup);
1993
+ if (backup != null) writeFileIfChanged(paths.backup, backup);
1808
1994
  else if (fs.existsSync(paths.backup)) fs.unlinkSync(paths.backup);
1809
1995
  const meta = { version: 1, existed, cursorDirExisted, also };
1810
- fs.writeFileSync(paths.written, writtenBody);
1811
- fs.writeFileSync(paths.meta, `${JSON.stringify(meta, null, 2)}\n`);
1996
+ writeFileIfChanged(paths.written, writtenBody);
1997
+ writeFileIfChanged(paths.meta, `${JSON.stringify(meta, null, 2)}\n`);
1812
1998
  return { paths, meta };
1813
1999
  }
1814
2000
 
1815
2001
  function saveWearState(paths, meta) {
1816
2002
  fs.mkdirSync(paths.dir, { recursive: true });
1817
- fs.writeFileSync(paths.meta, `${JSON.stringify(meta, null, 2)}\n`);
2003
+ writeFileIfChanged(paths.meta, `${JSON.stringify(meta, null, 2)}\n`);
1818
2004
  }
1819
2005
 
1820
2006
  /** git prints the physical path. Node's tmpdir is the /var symlink. Compare the same file. */
@@ -1931,7 +2117,7 @@ function wearCursorHooks(targetDir, packageRoot, log, opts) {
1931
2117
  }
1932
2118
  if (changed) {
1933
2119
  fs.mkdirSync(path.dirname(hooksPath), { recursive: true });
1934
- fs.writeFileSync(hooksPath, body);
2120
+ writeFileIfChanged(hooksPath, body);
1935
2121
  } else if (!snap) {
1936
2122
  write("cursor-bridge", "hooks.json already runs these hooks", "info", { path: hooksPath });
1937
2123
  }
@@ -2097,11 +2283,46 @@ function setupProjectBridges(opts) {
2097
2283
  * Framework dogfood: do not deploy consumer copies over the package,
2098
2284
  * but keep Grok discovery hooks hot and enable Repertoire when it resolves.
2099
2285
  */
2100
- function installFrameworkDogfoodWear(packageRoot, log) {
2286
+ function materializeDogfoodFeatures(packageRoot, log) {
2101
2287
  const featuresPath = path.join(packageRoot, ".xray", "features.json");
2102
- if (fs.existsSync(featuresPath)) {
2103
- applyResolvedMemoryRouting(featuresPath, packageRoot);
2288
+ if (!fs.existsSync(featuresPath)) {
2289
+ const shipped = resolveXrayConfigSource(packageRoot, "features.json");
2290
+ fs.mkdirSync(path.dirname(featuresPath), { recursive: true });
2291
+ if (shipped && path.resolve(shipped) !== path.resolve(featuresPath)) {
2292
+ fs.copyFileSync(shipped, featuresPath);
2293
+ log("grok-dogfood", "features.json materialized from shipped template", "info");
2294
+ } else {
2295
+ writeJsonFile(featuresPath, { memory_routing: { enabled: false, provider: "null" } });
2296
+ log("grok-dogfood", "features.json materialized default-off", "info");
2297
+ }
2298
+ }
2299
+ applyResolvedMemoryRouting(featuresPath, packageRoot);
2300
+ const stateDir = path.join(packageRoot, ".xray", "state", "repertoire");
2301
+ fs.mkdirSync(path.join(stateDir, "feedback"), { recursive: true });
2302
+ const signalsPath = path.join(stateDir, "curated_signals.json");
2303
+ if (!fs.existsSync(signalsPath)) {
2304
+ const candidates = [
2305
+ path.join(packageRoot, "vendor", "@0xray", "repertoire", "data", "curated_signals.json"),
2306
+ path.join(packageRoot, "..", "repertoire", "data", "curated_signals.json"),
2307
+ ];
2308
+ const src = candidates.find((candidate) => fs.existsSync(candidate));
2309
+ if (src) fs.copyFileSync(src, signalsPath);
2310
+ else {
2311
+ // CuratedSignalsManager.load() reads document.signals, not a bare array.
2312
+ writeJsonFile(signalsPath, {
2313
+ description: "Curated high-signal primitives for Repertoire",
2314
+ schema_version: "1.1",
2315
+ signals: [],
2316
+ });
2317
+ }
2318
+ log("grok-dogfood", "repertoire signals materialized", "info");
2104
2319
  }
2320
+ const inferenceState = path.join(stateDir, "inference-state.json");
2321
+ if (!fs.existsSync(inferenceState)) writeJsonFile(inferenceState, {});
2322
+ }
2323
+
2324
+ function installFrameworkDogfoodWear(packageRoot, log) {
2325
+ materializeDogfoodFeatures(packageRoot, log);
2105
2326
 
2106
2327
  installCursorBridge(packageRoot, packageRoot, log);
2107
2328
 
@@ -2157,6 +2378,9 @@ function installAllBridges(opts) {
2157
2378
  module.exports = {
2158
2379
  installAllBridges,
2159
2380
  installGrokBridge,
2381
+ installHermesBridge,
2382
+ resetWearIo,
2383
+ readWearIo,
2160
2384
  resolveGrokPluginDests,
2161
2385
  resolveConsumerTargetDir,
2162
2386
  isInstallPrefixTarget,
@@ -52,19 +52,30 @@ function outsideRegionEdited(parts) {
52
52
  return parts.before.trim() !== "" || parts.after.trim() !== "";
53
53
  }
54
54
 
55
+ function writeTextIfChanged(filePath, body) {
56
+ try {
57
+ if (fs.readFileSync(filePath, "utf8") === body) return false;
58
+ } catch {
59
+ /* absent */
60
+ }
61
+ fs.writeFileSync(filePath, body);
62
+ return true;
63
+ }
64
+
55
65
  function deployManagedAgents(packageRoot, targetDir, log) {
56
66
  const agentsConsumer = path.join(packageRoot, "AGENTS-consumer.md");
57
67
  const agentsDest = path.join(targetDir, "AGENTS.md");
58
68
  if (!fs.existsSync(agentsConsumer)) return;
59
69
  const next = renderManagedAgents(packageRoot, targetDir);
60
70
  if (!fs.existsSync(agentsDest)) {
61
- fs.writeFileSync(agentsDest, next);
71
+ writeTextIfChanged(agentsDest, next);
62
72
  return;
63
73
  }
64
74
  const current = fs.readFileSync(agentsDest, "utf8");
65
75
  const parts = managedRegion(current);
66
76
  if (!parts || outsideRegionEdited(parts)) {
67
- fs.writeFileSync(path.join(targetDir, "AGENTS.md.0xray-new"), next);
77
+ const side = path.join(targetDir, "AGENTS.md.0xray-new");
78
+ writeTextIfChanged(side, next);
68
79
  log("postinstall", "AGENTS.md left in place; wrote AGENTS.md.0xray-new", "info");
69
80
  return;
70
81
  }
@@ -72,7 +83,7 @@ function deployManagedAgents(packageRoot, targetDir, log) {
72
83
  const endAt = next.indexOf(XRAY_MANAGED_AGENTS_END);
73
84
  const interior = next.slice(beginAt, endAt + XRAY_MANAGED_AGENTS_END.length);
74
85
  const updated = `${parts.before}${interior}${parts.after}`;
75
- if (updated !== current) fs.writeFileSync(agentsDest, updated);
86
+ writeTextIfChanged(agentsDest, updated);
76
87
  }
77
88
 
78
89
  /** npm install links vendored @0xray/repertoire and does not write `.mcp.json`. `npx 0xray wear` rewrites a checkout `dist/cli` launch to `node_modules/0xray`. */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.36",
3
+ "version": "4.0.37",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",