0xray 3.5.5 → 4.0.1

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 (233) hide show
  1. package/.mcp.json +18 -14
  2. package/.opencode/skills/api-design/SKILL.md +1 -1
  3. package/.opencode/skills/architect-tools/SKILL.md +1 -1
  4. package/.opencode/skills/architecture-patterns/SKILL.md +1 -1
  5. package/.opencode/skills/auto-format/SKILL.md +1 -1
  6. package/.opencode/skills/boot-orchestrator/SKILL.md +2 -6
  7. package/.opencode/skills/bug-triage/SKILL.md +1 -1
  8. package/.opencode/skills/code-analyzer/SKILL.md +1 -1
  9. package/.opencode/skills/code-review/SKILL.md +1 -1
  10. package/.opencode/skills/enforcer/SKILL.md +1 -1
  11. package/.opencode/skills/framework-compliance-audit/SKILL.md +1 -1
  12. package/.opencode/skills/git-workflow/SKILL.md +1 -1
  13. package/.opencode/skills/hermes-agent/SKILL.md +36 -36
  14. package/.opencode/skills/inference-improve/SKILL.md +1 -1
  15. package/.opencode/skills/lint/SKILL.md +1 -1
  16. package/.opencode/skills/log-monitor/SKILL.md +1 -1
  17. package/.opencode/skills/model-health-check/SKILL.md +1 -1
  18. package/.opencode/skills/multimodal-looker/SKILL.md +1 -1
  19. package/.opencode/skills/orchestrator/SKILL.md +38 -14
  20. package/.opencode/skills/performance-analysis/SKILL.md +1 -1
  21. package/.opencode/skills/performance-optimization/SKILL.md +1 -1
  22. package/.opencode/skills/processor-pipeline/SKILL.md +1 -1
  23. package/.opencode/skills/project-analysis/SKILL.md +1 -1
  24. package/.opencode/skills/refactoring-strategies/SKILL.md +1 -1
  25. package/.opencode/skills/registry.json +65 -0
  26. package/.opencode/skills/researcher/SKILL.md +18 -5
  27. package/.opencode/skills/security-audit/SKILL.md +1 -1
  28. package/.opencode/skills/security-scan/SKILL.md +1 -1
  29. package/.opencode/skills/session-management/SKILL.md +1 -1
  30. package/.opencode/skills/state-manager/SKILL.md +1 -1
  31. package/.opencode/skills/storyteller/SKILL.md +1 -1
  32. package/.opencode/skills/strategist/SKILL.md +12 -0
  33. package/.opencode/skills/testing-best-practices/SKILL.md +37 -0
  34. package/.opencode/skills/testing-strategy/SKILL.md +14 -3
  35. package/.opencode/skills/ui-ux-design/SKILL.md +1 -1
  36. package/.opencode/skills/xray-orchestrator/index.ts +267 -0
  37. package/.xray/codex.json +12 -3
  38. package/.xray/features.json +28 -6
  39. package/.xray/features.schema.json +31 -0
  40. package/AGENTS-consumer.md +17 -7
  41. package/AGENTS.md +18 -10
  42. package/README.md +30 -27
  43. package/SKILLS.md +2 -2
  44. package/dist/AGENTS.md +18 -10
  45. package/dist/CHANGELOG.md +81 -11
  46. package/dist/README.md +30 -27
  47. package/dist/SKILLS.md +2 -2
  48. package/dist/cli/commands/hermes-install.js +3 -0
  49. package/dist/cli/commands/openclaw-install.js +14 -0
  50. package/dist/cli/index.js +10 -1
  51. package/dist/core/features-config.d.ts +15 -2
  52. package/dist/infrastructure/schemas/cloud-schemas.d.ts +1 -1
  53. package/dist/integrations/grok/grok-cli.d.ts +3 -0
  54. package/dist/integrations/grok/grok-cli.js +92 -4
  55. package/dist/integrations/grok/hooks/grok-hook-utils.js +53 -7
  56. package/dist/integrations/grok/hooks/post-tool-use.js +68 -46
  57. package/dist/integrations/grok/hooks/pre-tool-use.js +14 -31
  58. package/dist/integrations/grok/hooks/session-start.js +86 -4
  59. package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +31 -24
  60. package/dist/integrations/hermes-agent/bridge.mjs +26 -1
  61. package/dist/integrations/hooks/delegation-gate-runtime.mjs +32 -3
  62. package/dist/integrations/hooks/pipeline-hook-runtime.mjs +1 -1
  63. package/dist/integrations/hooks/station-hook-runtime.cjs +421 -0
  64. package/dist/integrations/hooks/station-hook-runtime.mjs +24 -0
  65. package/dist/integrations/hooks/station-memory-ingest.mjs +45 -0
  66. package/dist/integrations/hooks/station-memory-match.mjs +47 -0
  67. package/dist/integrations/openclaw/client.d.ts +5 -1
  68. package/dist/integrations/openclaw/client.js +17 -9
  69. package/dist/integrations/openclaw/config.js +0 -1
  70. package/dist/integrations/openclaw/hooks/pre-tool-gate-runtime.mjs +102 -0
  71. package/dist/integrations/openclaw/index.js +17 -0
  72. package/dist/integrations/openclaw/plugin/xray-pre-tool/index.js +75 -0
  73. package/dist/integrations/openclaw/plugin/xray-pre-tool/openclaw.plugin.json +10 -0
  74. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +12 -0
  75. package/dist/integrations/openclaw/pre-tool-gate.d.ts +19 -0
  76. package/dist/integrations/openclaw/pre-tool-gate.js +39 -0
  77. package/dist/mcps/orchestrator/handlers/complexity-handler.js +2 -2
  78. package/dist/memory-routing/index.d.ts +1 -1
  79. package/dist/memory-routing/index.js +1 -1
  80. package/dist/memory-routing/provider-loader.d.ts +4 -0
  81. package/dist/memory-routing/provider-loader.js +44 -2
  82. package/dist/memory-routing/provider-registry.js +3 -2
  83. package/dist/nucleus/autonomy-kernel.d.ts +2 -2
  84. package/dist/nucleus/autonomy-kernel.js +17 -6
  85. package/dist/nucleus/confer.js +17 -3
  86. package/dist/nucleus/delegation-gate.d.ts +14 -8
  87. package/dist/nucleus/delegation-gate.js +151 -16
  88. package/dist/nucleus/suit-temperament.d.ts +40 -0
  89. package/dist/nucleus/suit-temperament.js +157 -0
  90. package/dist/nucleus/user-aside.js +1 -1
  91. package/dist/plugin/xray-codex-injection.js +14 -3
  92. package/dist/postprocessor/triggers/GitHookTrigger.js +11 -5
  93. package/opencode.json +80 -0
  94. package/package.json +15 -5
  95. package/scripts/hooks/install-hooks.cjs +18 -3
  96. package/scripts/hooks/run-hook.js +67 -3
  97. package/scripts/mjs/run-repertoire-mcp.mjs +39 -0
  98. package/scripts/mjs/verify-confer-core.mjs +1 -1
  99. package/scripts/mjs/verify-delegation-gate-core.mjs +29 -2
  100. package/scripts/mjs/verify-four-floor-compact.mjs +524 -0
  101. package/scripts/mjs/verify-pipeline-facets.mjs +215 -0
  102. package/scripts/mjs/verify-temperament-live.mjs +261 -0
  103. package/scripts/node/bridge-mcp-wiring.cjs +277 -29
  104. package/scripts/node/consumer-install-smoke.mjs +55 -2
  105. package/scripts/node/install-bridges.cjs +195 -33
  106. package/scripts/node/postinstall.cjs +50 -27
  107. package/scripts/node/reconcile-version.mjs +0 -4
  108. package/scripts/node/release-gate.mjs +1 -1
  109. package/scripts/node/release.mjs +2 -2
  110. package/scripts/node/universal-version-manager.js +1 -1
  111. package/scripts/node/validate-release-docs.mjs +20 -33
  112. package/scripts/node/version-manager.mjs +63 -18
  113. package/src/integrations/grok/plugin/0xray/hooks/hooks.json +31 -24
  114. package/src/integrations/openclaw/README.md +134 -0
  115. package/src/integrations/openclaw/plugin/xray-pre-tool/index.js +75 -0
  116. package/src/integrations/openclaw/plugin/xray-pre-tool/openclaw.plugin.json +10 -0
  117. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +12 -0
  118. package/src/mcps/orchestrator/handlers/complexity-handler.ts +2 -1
  119. package/vendor/@0xray/repertoire/AGENTS.md +163 -0
  120. package/vendor/@0xray/repertoire/LICENSE +21 -0
  121. package/vendor/@0xray/repertoire/README.md +120 -0
  122. package/vendor/@0xray/repertoire/data/curated_signals.json +208 -0
  123. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts +78 -0
  124. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +1 -0
  125. package/vendor/@0xray/repertoire/dist/RepertoireService.js +153 -0
  126. package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +1 -0
  127. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.d.ts +39 -0
  128. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.d.ts.map +1 -0
  129. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.js +60 -0
  130. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.js.map +1 -0
  131. package/vendor/@0xray/repertoire/dist/index.d.ts +14 -0
  132. package/vendor/@0xray/repertoire/dist/index.d.ts.map +1 -0
  133. package/vendor/@0xray/repertoire/dist/index.js +14 -0
  134. package/vendor/@0xray/repertoire/dist/index.js.map +1 -0
  135. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts +26 -0
  136. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts.map +1 -0
  137. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js +109 -0
  138. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js.map +1 -0
  139. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.d.ts +18 -0
  140. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.d.ts.map +1 -0
  141. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.js +94 -0
  142. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.js.map +1 -0
  143. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.d.ts +7 -0
  144. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.d.ts.map +1 -0
  145. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.js +18 -0
  146. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.js.map +1 -0
  147. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts +31 -0
  148. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +1 -0
  149. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js +83 -0
  150. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +1 -0
  151. package/vendor/@0xray/repertoire/dist/mcp/server.d.ts +6 -0
  152. package/vendor/@0xray/repertoire/dist/mcp/server.d.ts.map +1 -0
  153. package/vendor/@0xray/repertoire/dist/mcp/server.js +173 -0
  154. package/vendor/@0xray/repertoire/dist/mcp/server.js.map +1 -0
  155. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.d.ts +27 -0
  156. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.d.ts.map +1 -0
  157. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js +107 -0
  158. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js.map +1 -0
  159. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.d.ts +11 -0
  160. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.d.ts.map +1 -0
  161. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.js +32 -0
  162. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.js.map +1 -0
  163. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts +9 -0
  164. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +1 -0
  165. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js +72 -0
  166. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +1 -0
  167. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.d.ts +5 -0
  168. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.d.ts.map +1 -0
  169. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.js +18 -0
  170. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.js.map +1 -0
  171. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts +31 -0
  172. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +1 -0
  173. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js +218 -0
  174. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +1 -0
  175. package/vendor/@0xray/repertoire/dist/paths.d.ts +24 -0
  176. package/vendor/@0xray/repertoire/dist/paths.d.ts.map +1 -0
  177. package/vendor/@0xray/repertoire/dist/paths.js +74 -0
  178. package/vendor/@0xray/repertoire/dist/paths.js.map +1 -0
  179. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts +141 -0
  180. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +1 -0
  181. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js +210 -0
  182. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +1 -0
  183. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts +48 -0
  184. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +1 -0
  185. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +221 -0
  186. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +1 -0
  187. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts +11 -0
  188. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts.map +1 -0
  189. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js +43 -0
  190. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js.map +1 -0
  191. package/vendor/@0xray/repertoire/dist/registry/signal-prune.d.ts +15 -0
  192. package/vendor/@0xray/repertoire/dist/registry/signal-prune.d.ts.map +1 -0
  193. package/vendor/@0xray/repertoire/dist/registry/signal-prune.js +35 -0
  194. package/vendor/@0xray/repertoire/dist/registry/signal-prune.js.map +1 -0
  195. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.d.ts +3 -0
  196. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.d.ts.map +1 -0
  197. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.js +24 -0
  198. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.js.map +1 -0
  199. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts +24 -0
  200. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts.map +1 -0
  201. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js +148 -0
  202. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js.map +1 -0
  203. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.d.ts +4 -0
  204. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.d.ts.map +1 -0
  205. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.js +44 -0
  206. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.js.map +1 -0
  207. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.d.ts +22 -0
  208. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.d.ts.map +1 -0
  209. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.js +66 -0
  210. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.js.map +1 -0
  211. package/vendor/@0xray/repertoire/dist/types.d.ts +210 -0
  212. package/vendor/@0xray/repertoire/dist/types.d.ts.map +1 -0
  213. package/vendor/@0xray/repertoire/dist/types.js +2 -0
  214. package/vendor/@0xray/repertoire/dist/types.js.map +1 -0
  215. package/vendor/@0xray/repertoire/docs/MEMORY-ROUTING-PROVIDER.md +54 -0
  216. package/vendor/@0xray/repertoire/hermes-mcp.example.json +20 -0
  217. package/vendor/@0xray/repertoire/mcp-config.example.json +11 -0
  218. package/vendor/@0xray/repertoire/package.json +124 -0
  219. package/vendor/@0xray/repertoire/schemas/curated-signal.schema.json +68 -0
  220. package/vendor/@0xray/repertoire/schemas/inference-log.schema.json +82 -0
  221. package/vendor/@0xray/repertoire/scripts/confirm-suit-all.mjs +253 -0
  222. package/vendor/@0xray/repertoire/scripts/confirm-suit-harness.mjs +220 -0
  223. package/vendor/@0xray/repertoire/scripts/run-live-synthesis-checkpoint.mjs +215 -0
  224. package/vendor/@0xray/repertoire/scripts/suit-bridge-shared.mjs +139 -0
  225. package/vendor/@0xray/repertoire/scripts/verify-grok-suit.mjs +304 -0
  226. package/vendor/@0xray/repertoire/scripts/verify-hermes-suit.mjs +163 -0
  227. package/vendor/@0xray/repertoire/scripts/verify-openclaw-suit.mjs +125 -0
  228. package/vendor/@0xray/repertoire/scripts/verify-opencode-suit.mjs +127 -0
  229. package/vendor/@0xray/repertoire/scripts/verify-synthesis-dogfood.mjs +207 -0
  230. package/vendor/@0xray/repertoire/skills/repertoire-trap-handling/SKILL.md +206 -0
  231. package/xray/codex.json +12 -3
  232. package/xray/features.json +28 -6
  233. package/xray/features.schema.json +31 -0
@@ -2,15 +2,11 @@
2
2
  "hooks": {
3
3
  "PreToolUse": [
4
4
  {
5
- "matcher": ".*",
6
5
  "hooks": [
7
6
  {
8
7
  "type": "command",
9
- "command": "node",
10
- "args": [
11
- "${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/pre-tool-use.js"
12
- ],
13
- "timeout": 30000
8
+ "command": "XRAY_AI_PATH=\"${XRAY_AI_PATH:-node_modules/0xray}\" node \"${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/pre-tool-use.js\"",
9
+ "timeout": 30
14
10
  }
15
11
  ]
16
12
  }
@@ -20,11 +16,8 @@
20
16
  "hooks": [
21
17
  {
22
18
  "type": "command",
23
- "command": "node",
24
- "args": [
25
- "${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/session-start.js"
26
- ],
27
- "timeout": 15000
19
+ "command": "XRAY_AI_PATH=\"${XRAY_AI_PATH:-node_modules/0xray}\" node \"${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/session-start.js\"",
20
+ "timeout": 30
28
21
  }
29
22
  ]
30
23
  }
@@ -34,30 +27,44 @@
34
27
  "hooks": [
35
28
  {
36
29
  "type": "command",
37
- "command": "node",
38
- "args": [
39
- "${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/session-start.js",
40
- "--hook-event=user_prompt_submit"
41
- ],
42
- "timeout": 15000
30
+ "command": "XRAY_AI_PATH=\"${XRAY_AI_PATH:-node_modules/0xray}\" node \"${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/session-start.js\" --hook-event=user_prompt_submit",
31
+ "timeout": 30
43
32
  }
44
33
  ]
45
34
  }
46
35
  ],
47
36
  "PostToolUse": [
48
37
  {
49
- "matcher": ".*",
50
38
  "hooks": [
51
39
  {
52
40
  "type": "command",
53
- "command": "node",
54
- "args": [
55
- "${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/post-tool-use.js"
56
- ],
57
- "timeout": 15000
41
+ "command": "XRAY_AI_PATH=\"${XRAY_AI_PATH:-node_modules/0xray}\" node \"${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/post-tool-use.js\"",
42
+ "timeout": 30
43
+ }
44
+ ]
45
+ }
46
+ ],
47
+ "PreCompact": [
48
+ {
49
+ "hooks": [
50
+ {
51
+ "type": "command",
52
+ "command": "XRAY_AI_PATH=\"${XRAY_AI_PATH:-node_modules/0xray}\" node \"${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/session-start.js\" --hook-event=pre_compact",
53
+ "timeout": 30
54
+ }
55
+ ]
56
+ }
57
+ ],
58
+ "PostCompact": [
59
+ {
60
+ "hooks": [
61
+ {
62
+ "type": "command",
63
+ "command": "XRAY_AI_PATH=\"${XRAY_AI_PATH:-node_modules/0xray}\" node \"${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/session-start.js\" --hook-event=post_compact",
64
+ "timeout": 30
58
65
  }
59
66
  ]
60
67
  }
61
68
  ]
62
69
  }
63
- }
70
+ }
@@ -332,6 +332,12 @@ async function runProcessors(tool, args, phase, projectRoot, logDir) {
332
332
  priority: 30,
333
333
  enabled: true,
334
334
  });
335
+ processorManager.registerProcessor({
336
+ name: "nudge",
337
+ type: "post",
338
+ priority: 78,
339
+ enabled: true,
340
+ });
335
341
  }
336
342
 
337
343
  let results;
@@ -436,6 +442,24 @@ async function handleSessionStart(input, projectRoot, logDir) {
436
442
  /* non-blocking — dist may be absent during dev */
437
443
  }
438
444
 
445
+ try {
446
+ const gate = await import('../hooks/delegation-gate-runtime.mjs');
447
+ if (typeof gate.writeSuitSessionBoot === 'function') {
448
+ const compact =
449
+ input.compact === true ||
450
+ input.hookEvent === 'pre_compact' ||
451
+ input.hookEvent === 'post_compact';
452
+ gate.writeSuitSessionBoot(projectRoot, 'hermes', {
453
+ source: compact ? '0xray/hermes-compact' : '0xray/hermes-session-start',
454
+ sessionId,
455
+ hookEvent: compact ? 'post_compact' : 'session_start',
456
+ ...(input.intent ? { intent: input.intent } : {}),
457
+ });
458
+ }
459
+ } catch {
460
+ /* dist may be absent during dev */
461
+ }
462
+
439
463
  logToActivity(logDir, `session-start: session=${sessionId} source=bridge`);
440
464
  return { ok: true, sessionId, planArchive };
441
465
  }
@@ -494,6 +518,7 @@ async function handlePostProcess(input, projectRoot, logDir) {
494
518
  });
495
519
 
496
520
  logToActivity(logDir, `post-process: complete duration=${duration}ms processors=${processorResult.success}`);
521
+ logToActivity(logDir, `[nudge] post-process tool=${tool}`);
497
522
 
498
523
  // Log routing outcome for test verification
499
524
  logRoutingOutcome(logDir, {
@@ -862,7 +887,7 @@ async function handleDelegationGate(command, projectRoot, logDir) {
862
887
  await recordHermesSynthesisTurn(projectRoot, sessionId);
863
888
  }
864
889
 
865
- const features = gate.loadDelegationGateFeatures(projectRoot);
890
+ const features = gate.loadDelegationGateFeatures(projectRoot, host);
866
891
  const outcome = gate.evaluatePreToolGate(tool, args, {
867
892
  projectRoot,
868
893
  sessionId,
@@ -4,7 +4,8 @@
4
4
  * Used by Grok hooks, Hermes bridge, and verify fixtures.
5
5
  */
6
6
  import { createRequire } from 'node:module';
7
- import { existsSync } from 'node:fs';
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
8
9
  import { dirname, join, resolve } from 'node:path';
9
10
  import { fileURLToPath } from 'node:url';
10
11
 
@@ -35,17 +36,40 @@ function resolvePackageRoot() {
35
36
  return resolve(__dirname, '../../..');
36
37
  }
37
38
 
39
+ function markedRoots() {
40
+ const files = [
41
+ join(homedir(), '.hermes', 'plugins', 'xray-hermes', 'xray-consumer-root.txt'),
42
+ join(homedir(), '.openclaw', 'xray-consumer-root.txt'),
43
+ ];
44
+ const roots = [];
45
+ for (const file of files) {
46
+ if (!existsSync(file)) continue;
47
+ try {
48
+ const marked = readFileSync(file, 'utf8').trim();
49
+ if (marked) {
50
+ roots.push(marked, join(marked, 'node_modules', '0xray'));
51
+ }
52
+ } catch {
53
+ /* ignore unreadable marker */
54
+ }
55
+ }
56
+ return roots;
57
+ }
58
+
38
59
  function loadDelegationGate() {
39
60
  const root = resolvePackageRoot();
61
+ const envRoot = process.env.XRAY_AI_PATH || '';
40
62
  const candidates = [
41
63
  join(__dirname, '../../nucleus/delegation-gate.js'),
42
64
  join(root, 'dist/nucleus/delegation-gate.js'),
65
+ envRoot ? join(envRoot, 'dist/nucleus/delegation-gate.js') : '',
66
+ ...markedRoots().map((r) => join(r, 'dist/nucleus/delegation-gate.js')),
43
67
  join(process.cwd(), 'node_modules/0xray/dist/nucleus/delegation-gate.js'),
44
- ];
68
+ ].filter(Boolean);
45
69
  const found = candidates.find((p) => existsSync(p));
46
70
  if (!found) {
47
71
  throw new Error(
48
- 'delegation-gate.js missing — run npm run build in 0xray or npm install 0xray@^3.5.1',
72
+ 'delegation-gate.js missing — run npm run build in 0xray or npm install 0xray',
49
73
  );
50
74
  }
51
75
  return createRequire(import.meta.url)(found);
@@ -53,6 +77,9 @@ function loadDelegationGate() {
53
77
 
54
78
  const gate = loadDelegationGate();
55
79
 
80
+ export const writeSuitSessionBoot = gate.writeSuitSessionBoot;
81
+ export const maybeHeatHostStation = gate.maybeHeatHostStation;
82
+
56
83
  export const {
57
84
  loadDelegationGateFeatures,
58
85
  normalizeHostToolInput,
@@ -78,4 +105,6 @@ export const {
78
105
  buildReceiptFromConsultOutput,
79
106
  parseConsultVerdictFromText,
80
107
  isSynthesisConsultTodoId,
108
+ archiveStaleLeadDevPlan,
109
+ findRecentStalePlanArchive,
81
110
  } = gate;
@@ -189,7 +189,7 @@ export function runGrokPostprocessorLight(projectRoot, entry = {}) {
189
189
  phase: 'post-process-light',
190
190
  tool: entry.tool ?? 'unknown',
191
191
  paths: entry.paths ?? [],
192
- mode: 'grok-pre-tool-light',
192
+ mode: 'grok-post-tool-light',
193
193
  };
194
194
  writeFileSync(markerPath, `${JSON.stringify(payload, null, 2)}\n`);
195
195
  appendActivity(projectRoot, 'postprocessor-light', 'info', {
@@ -0,0 +1,421 @@
1
+ /**
2
+ * Station heat — compaction / host-swap card.
3
+ * SSOT remains session-boot.json. STATION.md is the projection the model Reads.
4
+ * Grok ignores SessionStart/UserPromptSubmit stdout; disk + AGENTS.md is the contract.
5
+ */
6
+ const { execFileSync } = require("child_process");
7
+ const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs");
8
+ const { join } = require("path");
9
+
10
+ const HOOKS_DIR = __dirname;
11
+
12
+ const INTENT_MAX = 240;
13
+
14
+ function stationMarkdownPath(root) {
15
+ return join(root, ".xray", "state", "STATION.md");
16
+ }
17
+
18
+ function sessionBootPath(root) {
19
+ return join(root, ".xray", "state", "session-boot.json");
20
+ }
21
+
22
+ function clipIntent(raw) {
23
+ if (raw == null) return null;
24
+ const text = String(raw)
25
+ .replace(/<\/?user_query>/gi, " ")
26
+ .replace(/\s+/g, " ")
27
+ .trim();
28
+ if (!text) return null;
29
+ if (text.length <= INTENT_MAX) return text;
30
+ return `${text.slice(0, INTENT_MAX - 1)}…`;
31
+ }
32
+
33
+ function readExistingBoot(root) {
34
+ const bootPath = sessionBootPath(root);
35
+ if (!existsSync(bootPath)) return {};
36
+ try {
37
+ const data = JSON.parse(readFileSync(bootPath, "utf8"));
38
+ return data && typeof data === "object" ? data : {};
39
+ } catch {
40
+ return {};
41
+ }
42
+ }
43
+
44
+ function readGitBrief(root) {
45
+ try {
46
+ const opts = { cwd: root, encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] };
47
+ const head = execFileSync("git", ["rev-parse", "--short", "HEAD"], opts).trim();
48
+ const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], opts).trim();
49
+ if (!head) return null;
50
+ return { branch: branch || "HEAD", head };
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ function readGitSubject(root) {
57
+ try {
58
+ const opts = { cwd: root, encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] };
59
+ const subject = execFileSync("git", ["log", "-1", "--format=%s"], opts).trim();
60
+ return clipIntent(subject);
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function readLiveTodoLine(plan) {
67
+ const phases = Array.isArray(plan.phases) ? plan.phases : [];
68
+ for (const phase of phases) {
69
+ const todos = Array.isArray(phase && phase.todos) ? phase.todos : [];
70
+ for (const todo of todos) {
71
+ const status = String((todo && todo.status) || "pending");
72
+ if (status === "completed" || status === "cancelled") continue;
73
+ const task = clipIntent((todo && (todo.task || todo.description)) || "");
74
+ if (task) return task;
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+
80
+ function readPlanLine(root) {
81
+ const planPath = join(root, ".xray", "state", "lead-dev-plan.json");
82
+ if (existsSync(planPath)) {
83
+ try {
84
+ const plan = JSON.parse(readFileSync(planPath, "utf8"));
85
+ if (plan && plan.active !== false) {
86
+ const live = readLiveTodoLine(plan);
87
+ if (live) return live;
88
+ }
89
+ } catch {
90
+ /* fall through to git subject — stale / empty plan is not the card */
91
+ }
92
+ }
93
+ return readGitSubject(root);
94
+ }
95
+
96
+ function resolveRepertoireProviderModule(root) {
97
+ const mr = readMemoryRoutingConfig(root);
98
+ if (mr && typeof mr.module_path === "string" && mr.module_path) {
99
+ const configured = mr.module_path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(mr.module_path)
100
+ ? mr.module_path
101
+ : join(root, mr.module_path);
102
+ if (existsSync(configured)) return configured;
103
+ }
104
+ const siblingRoot = join(root, "..", "repertoire");
105
+ const siblingProvider = join(siblingRoot, "dist", "provider", "memory-routing-provider.js");
106
+ const nmProvider = join(
107
+ root,
108
+ "node_modules",
109
+ "@0xray",
110
+ "repertoire",
111
+ "dist",
112
+ "provider",
113
+ "memory-routing-provider.js",
114
+ );
115
+ const vendorProvider = join(
116
+ root,
117
+ "vendor",
118
+ "@0xray",
119
+ "repertoire",
120
+ "dist",
121
+ "provider",
122
+ "memory-routing-provider.js",
123
+ );
124
+ if (existsSync(nmProvider)) return nmProvider;
125
+ if (existsSync(vendorProvider)) return vendorProvider;
126
+ try {
127
+ const pkg = JSON.parse(readFileSync(join(siblingRoot, "package.json"), "utf8"));
128
+ if ((pkg.name === "@0xray/repertoire" || pkg.name === "repertoire") && existsSync(siblingProvider)) {
129
+ return siblingProvider;
130
+ }
131
+ } catch {
132
+ /* sibling missing */
133
+ }
134
+ return null;
135
+ }
136
+
137
+ function countCuratedSignals(signalsPath) {
138
+ try {
139
+ const data = JSON.parse(readFileSync(signalsPath, "utf8"));
140
+ if (Array.isArray(data.signals)) return data.signals.length;
141
+ if (Array.isArray(data)) return data.length;
142
+ } catch {
143
+ /* unreadable */
144
+ }
145
+ return null;
146
+ }
147
+
148
+ function readMemoryRoutingConfig(root) {
149
+ const featuresPath = join(root, ".xray", "features.json");
150
+ if (!existsSync(featuresPath)) return {};
151
+ try {
152
+ return JSON.parse(readFileSync(featuresPath, "utf8")).memory_routing || {};
153
+ } catch {
154
+ return {};
155
+ }
156
+ }
157
+
158
+ function isExplicitMemoryRoutingOptOut(mr) {
159
+ return Boolean(mr) && mr.enabled === false && mr.provider === "repertoire";
160
+ }
161
+
162
+ function isLeftoverMemoryRoutingOff(mr) {
163
+ if (!mr || typeof mr !== "object") return true;
164
+ if (mr.enabled === true) return false;
165
+ if (isExplicitMemoryRoutingOptOut(mr)) return false;
166
+ const provider = mr.provider == null || mr.provider === "" ? "null" : mr.provider;
167
+ return provider === "null";
168
+ }
169
+
170
+ function isMemoryRoutingResolvedOn(mr, modulePath) {
171
+ if (!modulePath) return false;
172
+ if (isExplicitMemoryRoutingOptOut(mr)) return false;
173
+ if (mr.enabled === true && (mr.provider === "repertoire" || mr.provider === "custom")) return true;
174
+ return isLeftoverMemoryRoutingOff(mr);
175
+ }
176
+
177
+ function repertoireWorkingPath(root) {
178
+ return join(root, ".xray", "state", "repertoire-working.json");
179
+ }
180
+
181
+ function readRepertoireWorking(root) {
182
+ const dest = repertoireWorkingPath(root);
183
+ if (!existsSync(dest)) return null;
184
+ try {
185
+ const data = JSON.parse(readFileSync(dest, "utf8"));
186
+ return data && typeof data === "object" ? data : null;
187
+ } catch {
188
+ return null;
189
+ }
190
+ }
191
+
192
+ function stationSafeSignals(names) {
193
+ if (!Array.isArray(names)) return [];
194
+ const out = [];
195
+ for (const raw of names) {
196
+ const name = String(raw || "").trim();
197
+ if (!name) continue;
198
+ if (name.toLowerCase().startsWith("bedrock-")) continue;
199
+ out.push(name);
200
+ if (out.length >= 4) break;
201
+ }
202
+ return out;
203
+ }
204
+
205
+ function persistRepertoireWorking(root, snapshot) {
206
+ try {
207
+ mkdirSync(join(root, ".xray", "state"), { recursive: true });
208
+ const prev = readRepertoireWorking(root) || {};
209
+ const next = { ...prev, ...snapshot, updatedAt: new Date().toISOString() };
210
+ writeFileSync(repertoireWorkingPath(root), `${JSON.stringify(next, null, 2)}\n`);
211
+ return next;
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+
217
+ function matchStationSignalsSync(root, intent) {
218
+ if (!intent) return [];
219
+ const helper = join(HOOKS_DIR, "station-memory-match.mjs");
220
+ if (!existsSync(helper)) return [];
221
+ try {
222
+ const out = execFileSync(process.execPath, [helper, root, intent], {
223
+ encoding: "utf8",
224
+ timeout: 20000,
225
+ stdio: ["ignore", "pipe", "pipe"],
226
+ env: process.env,
227
+ });
228
+ const line = String(out).trim().split("\n").filter(Boolean).at(-1) || "[]";
229
+ const parsed = JSON.parse(line);
230
+ return Array.isArray(parsed) ? parsed : [];
231
+ } catch {
232
+ return [];
233
+ }
234
+ }
235
+
236
+ function ingestCompactFeedbackSync(root, sessionId, hookEvent, signals) {
237
+ if (!signals.length) return;
238
+ const helper = join(HOOKS_DIR, "station-memory-ingest.mjs");
239
+ if (!existsSync(helper)) return;
240
+ try {
241
+ execFileSync(
242
+ process.execPath,
243
+ [helper, root, sessionId || "station", hookEvent || "post_compact", JSON.stringify(signals)],
244
+ {
245
+ encoding: "utf8",
246
+ timeout: 20000,
247
+ stdio: ["ignore", "pipe", "pipe"],
248
+ env: process.env,
249
+ },
250
+ );
251
+ } catch {
252
+ /* working-state file is the session memory; registry ingest is best-effort */
253
+ }
254
+ }
255
+
256
+ function isCompactHook(extra) {
257
+ const hook = String(extra.hookEvent || extra.source || "");
258
+ return /compact/i.test(hook);
259
+ }
260
+
261
+ function formatWorkingLine(working) {
262
+ if (!working || typeof working !== "object") return null;
263
+ const matched = stationSafeSignals(working.matchedSignals);
264
+ if (matched.length) return `Working: ${matched.join(", ")}`;
265
+ const hook = typeof working.hookEvent === "string" ? working.hookEvent : null;
266
+ const head = working.git && working.git.head ? String(working.git.head) : null;
267
+ if (hook && head) return `Working: last ${hook} @ ${head}`;
268
+ if (hook) return `Working: last ${hook}`;
269
+ return "Working: station snapshot";
270
+ }
271
+
272
+ function buildRepertoireResume(root) {
273
+ const modulePath = resolveRepertoireProviderModule(root);
274
+ const mr = readMemoryRoutingConfig(root);
275
+ if (!modulePath) {
276
+ return "Repertoire: not installed (memory_routing stays off)";
277
+ }
278
+ let signalsPath = mr.config && mr.config.signalsPath;
279
+ if (signalsPath && !signalsPath.startsWith("/") && !/^[A-Za-z]:[\\/]/.test(signalsPath)) {
280
+ signalsPath = join(root, signalsPath);
281
+ }
282
+ if (!signalsPath) {
283
+ signalsPath = join(modulePath, "..", "..", "..", "data", "curated_signals.json");
284
+ }
285
+ const n = existsSync(signalsPath) ? countCuratedSignals(signalsPath) : null;
286
+ const count = n == null ? "" : ` — ${n} signals`;
287
+ if (isMemoryRoutingResolvedOn(mr, modulePath)) {
288
+ return `Repertoire: on${count}`;
289
+ }
290
+ return `Repertoire: present, memory_routing off${count}`;
291
+ }
292
+
293
+ function applyStationHeat(root, host, extra = {}, existing = {}) {
294
+ const prevHost = typeof existing.host === "string" ? existing.host : null;
295
+ const nextSwap = prevHost && host && prevHost !== host ? { from: prevHost, to: host } : null;
296
+ const keptSwap =
297
+ existing.hotSwap &&
298
+ typeof existing.hotSwap === "object" &&
299
+ existing.hotSwap.from &&
300
+ existing.hotSwap.to
301
+ ? existing.hotSwap
302
+ : null;
303
+ const hotSwap = nextSwap || keptSwap;
304
+ const intent =
305
+ clipIntent(extra.intent || extra.prompt || extra.userMessage || extra.user_prompt) ||
306
+ (typeof existing.intent === "string" ? existing.intent : null);
307
+ const git = readGitBrief(root);
308
+ const planLine = readPlanLine(root);
309
+ const repertoireResume =
310
+ typeof extra.repertoireResume === "string" ? extra.repertoireResume : buildRepertoireResume(root);
311
+ const swapBit = hotSwap ? `hot-swap ${hotSwap.from} → ${hotSwap.to}` : `host ${host}`;
312
+ const intentBit = intent ? `intent: ${intent}` : "intent: (none yet)";
313
+ const gitBit = git ? `git ${git.branch}@${git.head}` : "git: n/a";
314
+ const planBit = planLine ? `plan: ${planLine}` : "plan: (none)";
315
+ let matchedSignals = stationSafeSignals(extra.matchedSignals);
316
+ const priorWorking = readRepertoireWorking(root);
317
+ if (!matchedSignals.length && intent) {
318
+ if (
319
+ priorWorking &&
320
+ priorWorking.intent === intent &&
321
+ Array.isArray(priorWorking.matchedSignals) &&
322
+ priorWorking.matchedSignals.length
323
+ ) {
324
+ matchedSignals = stationSafeSignals(priorWorking.matchedSignals);
325
+ } else {
326
+ matchedSignals = stationSafeSignals(matchStationSignalsSync(root, intent));
327
+ }
328
+ }
329
+ if (!matchedSignals.length && priorWorking) {
330
+ matchedSignals = stationSafeSignals(priorWorking.matchedSignals);
331
+ }
332
+ const workingSnapshot = {
333
+ host,
334
+ intent,
335
+ git,
336
+ hotSwap,
337
+ hookEvent: extra.hookEvent || extra.source || null,
338
+ memoryRouting: repertoireResume.startsWith("Repertoire: on") ? "on" : "off",
339
+ repertoireResume,
340
+ };
341
+ if (matchedSignals.length) workingSnapshot.matchedSignals = matchedSignals;
342
+ const working = persistRepertoireWorking(root, workingSnapshot);
343
+ if (isCompactHook(extra) && matchedSignals.length) {
344
+ ingestCompactFeedbackSync(
345
+ root,
346
+ typeof extra.sessionId === "string" ? extra.sessionId : "station",
347
+ extra.hookEvent || extra.source || "post_compact",
348
+ matchedSignals,
349
+ );
350
+ }
351
+ const workingLine = formatWorkingLine(working);
352
+ const workingBit = workingLine ? workingLine : "working: (none)";
353
+ const stationLine = `${swapBit}. ${intentBit}. ${planBit}. ${gitBit}. ${repertoireResume}. ${workingBit}`;
354
+ return {
355
+ lastHost: prevHost,
356
+ hotSwap,
357
+ intent,
358
+ git,
359
+ planLine,
360
+ repertoireResume,
361
+ workingLine,
362
+ stationLine,
363
+ };
364
+ }
365
+
366
+ function formatStationMarkdown(fields) {
367
+ const host = fields.host || "unknown";
368
+ const profile = fields.suit_profile || "guided";
369
+ const lines = ["# Station", ""];
370
+ if (fields.hotSwap && fields.hotSwap.from && fields.hotSwap.to) {
371
+ lines.push(`Hot-swap: ${fields.hotSwap.from} → ${fields.hotSwap.to}`);
372
+ }
373
+ lines.push(`Host: ${host} (${profile})`);
374
+ lines.push(`Intent: ${fields.intent || "(none yet)"}`);
375
+ lines.push(`Plan: ${fields.planLine || "(none)"}`);
376
+ if (fields.git && fields.git.head) {
377
+ lines.push(`Git: ${fields.git.branch}@${fields.git.head}`);
378
+ } else {
379
+ lines.push("Git: n/a");
380
+ }
381
+ lines.push(fields.repertoireResume || "Repertoire: not installed (memory_routing stays off)");
382
+ if (fields.workingLine) {
383
+ lines.push(fields.workingLine);
384
+ }
385
+ lines.push("");
386
+ lines.push("Continue this card. Compaction and host change are the same cut. Do not cold-start.");
387
+ lines.push("Grok does not inject this file — Read it. OpenCode injects. Do not thicken the Grok exo.");
388
+ lines.push("");
389
+ return lines.join("\n");
390
+ }
391
+
392
+ function writeStationMarkdown(root, fields) {
393
+ try {
394
+ const dir = join(root, ".xray", "state");
395
+ mkdirSync(dir, { recursive: true });
396
+ const dest = stationMarkdownPath(root);
397
+ writeFileSync(dest, formatStationMarkdown(fields));
398
+ return dest;
399
+ } catch {
400
+ return null;
401
+ }
402
+ }
403
+
404
+ module.exports = {
405
+ stationMarkdownPath,
406
+ sessionBootPath,
407
+ clipIntent,
408
+ readExistingBoot,
409
+ readGitBrief,
410
+ readGitSubject,
411
+ readPlanLine,
412
+ buildRepertoireResume,
413
+ isExplicitMemoryRoutingOptOut,
414
+ isLeftoverMemoryRoutingOff,
415
+ persistRepertoireWorking,
416
+ readRepertoireWorking,
417
+ formatWorkingLine,
418
+ applyStationHeat,
419
+ formatStationMarkdown,
420
+ writeStationMarkdown,
421
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * ESM face for station-hook-runtime.cjs — Grok hooks import this.
3
+ */
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { dirname, join } from 'node:path';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const impl = require(join(dirname(fileURLToPath(import.meta.url)), 'station-hook-runtime.cjs'));
10
+
11
+ export const stationMarkdownPath = impl.stationMarkdownPath;
12
+ export const sessionBootPath = impl.sessionBootPath;
13
+ export const clipIntent = impl.clipIntent;
14
+ export const readExistingBoot = impl.readExistingBoot;
15
+ export const readGitBrief = impl.readGitBrief;
16
+ export const readGitSubject = impl.readGitSubject;
17
+ export const readPlanLine = impl.readPlanLine;
18
+ export const buildRepertoireResume = impl.buildRepertoireResume;
19
+ export const persistRepertoireWorking = impl.persistRepertoireWorking;
20
+ export const readRepertoireWorking = impl.readRepertoireWorking;
21
+ export const formatWorkingLine = impl.formatWorkingLine;
22
+ export const applyStationHeat = impl.applyStationHeat;
23
+ export const formatStationMarkdown = impl.formatStationMarkdown;
24
+ export const writeStationMarkdown = impl.writeStationMarkdown;