0xray 4.0.41 → 4.1.0

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 (148) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.opencode/skills/orchestrator/SKILL.md +12 -0
  3. package/.plugin/plugin.json +1 -1
  4. package/.xray/features.json +5 -5
  5. package/.xray/features.schema.json +1 -1
  6. package/AGENTS-consumer.md +10 -6
  7. package/AGENTS.md +19 -8
  8. package/LICENSE +1 -1
  9. package/README.md +65 -19
  10. package/SKILLS.md +5 -1
  11. package/dist/AGENTS.md +19 -8
  12. package/dist/CHANGELOG.md +85 -0
  13. package/dist/LICENSE +1 -1
  14. package/dist/README.md +65 -19
  15. package/dist/SKILLS.md +5 -1
  16. package/dist/cli/commands/openclaw-install.js +1 -1
  17. package/dist/cli/commands/plugin-commands.d.ts +4 -0
  18. package/dist/cli/commands/plugin-commands.js +84 -41
  19. package/dist/cli/index.js +12 -0
  20. package/dist/core/config-loader.js +4 -4
  21. package/dist/core/features-config.d.ts +1 -1
  22. package/dist/core/features-config.js +3 -3
  23. package/dist/enforcement/index.d.ts +4 -0
  24. package/dist/enforcement/index.js +4 -0
  25. package/dist/enforcement/validators/code-quality-validators.js +6 -4
  26. package/dist/governance/llm-governance-provider.d.ts +7 -1
  27. package/dist/governance/llm-governance-provider.js +72 -14
  28. package/dist/governance/local-confer.d.ts +4 -1
  29. package/dist/governance/local-confer.js +22 -1
  30. package/dist/integrations/cursor/hooks/hooks.json +6 -3
  31. package/dist/integrations/cursor/hooks/pre-tool-use.js +100 -37
  32. package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +9 -1
  33. package/dist/integrations/grok/grok-cli.js +10 -37
  34. package/dist/integrations/grok/hooks/grok-hook-utils.js +8 -1
  35. package/dist/integrations/grok/hooks/pre-tool-use.js +2 -58
  36. package/dist/integrations/hermes-agent/bridge.mjs +14 -1
  37. package/dist/integrations/hooks/confer-hook-runtime.mjs +23 -1
  38. package/dist/integrations/hooks/delegation-gate-runtime.mjs +3 -2
  39. package/dist/integrations/hooks/goggles-mcp.mjs +35 -17
  40. package/dist/integrations/hooks/goggles-pipeline.mjs +46 -1
  41. package/dist/integrations/hooks/goggles-planes.json +11 -7
  42. package/dist/integrations/hooks/pre-tool-speak.mjs +76 -0
  43. package/dist/integrations/openclaw/hooks/pre-tool-gate-runtime.mjs +21 -4
  44. package/dist/integrations/openclaw/plugin/xray-pre-tool/index.js +17 -2
  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 -2
  48. package/dist/mcps/config/server-config-registry.js +8 -6
  49. package/dist/mcps/governance.server.d.ts +1 -0
  50. package/dist/mcps/governance.server.js +46 -0
  51. package/dist/mcps/knowledge-skills/code-review.server.js +7 -0
  52. package/dist/mcps/knowledge-skills/security-audit.server.js +5 -3
  53. package/dist/mcps/mcp-client.js +1 -1
  54. package/dist/mcps/orchestrator/handlers/complexity-handler.js +2 -2
  55. package/dist/mcps/researcher.server.js +12 -3
  56. package/dist/monitoring/{nudge-watchdog.d.ts → nudge-monitor.d.ts} +14 -5
  57. package/dist/monitoring/{nudge-watchdog.js → nudge-monitor.js} +42 -17
  58. package/dist/nucleus/autonomy-kernel.d.ts +2 -0
  59. package/dist/nucleus/autonomy-kernel.js +17 -13
  60. package/dist/nucleus/confer-host.d.ts +35 -0
  61. package/dist/nucleus/confer-host.js +55 -0
  62. package/dist/nucleus/confer.d.ts +19 -3
  63. package/dist/nucleus/confer.js +111 -55
  64. package/dist/nucleus/lead-dev-plan-persistence.js +4 -2
  65. package/dist/nucleus/suit-temperament.d.ts +1 -1
  66. package/dist/nucleus/suit-temperament.js +2 -2
  67. package/dist/nucleus/synthesis-consult-receipt.d.ts +7 -1
  68. package/dist/nucleus/synthesis-consult-receipt.js +86 -35
  69. package/dist/plugin/xray-codex-injection.js +18 -0
  70. package/dist/processors/implementations/nudge-processor.d.ts +1 -1
  71. package/dist/processors/implementations/nudge-processor.js +4 -4
  72. package/dist/reporting/framework-reporting-system.js +2 -5
  73. package/dist/reporting/log-parser.d.ts +2 -0
  74. package/dist/reporting/log-parser.js +7 -6
  75. package/dist/skills/orchestrator/SKILL.md +12 -0
  76. package/docs-site/docs/plates/activity-log.md +1 -1
  77. package/docs-site/docs/plates/bifrost.md +48 -0
  78. package/docs-site/docs/plates/boot.md +2 -2
  79. package/docs-site/docs/plates/centrifuge.md +1 -1
  80. package/docs-site/docs/plates/cursor-compact.md +1 -1
  81. package/docs-site/docs/plates/glossary.md +7 -3
  82. package/docs-site/docs/plates/goggles.md +13 -2
  83. package/docs-site/docs/plates/governance.md +1 -1
  84. package/docs-site/docs/plates/grok-compact.md +1 -1
  85. package/docs-site/docs/plates/grokbot.md +3 -1
  86. package/docs-site/docs/plates/host-pack.md +1 -1
  87. package/docs-site/docs/plates/house.md +1 -1
  88. package/docs-site/docs/plates/index.md +4 -2
  89. package/docs-site/docs/plates/inference-files.md +1 -1
  90. package/docs-site/docs/plates/kept-line.md +1 -1
  91. package/docs-site/docs/plates/kits.md +1 -1
  92. package/docs-site/docs/plates/lens-gate.md +3 -1
  93. package/docs-site/docs/plates/lens-page.md +1 -1
  94. package/docs-site/docs/plates/memory-recall.md +1 -1
  95. package/docs-site/docs/plates/notes-page.md +2 -2
  96. package/docs-site/docs/plates/orchestration.md +1 -1
  97. package/docs-site/docs/plates/package-face.md +1 -1
  98. package/docs-site/docs/plates/payload-heat.md +1 -1
  99. package/docs-site/docs/plates/pickup-stamp.md +1 -1
  100. package/docs-site/docs/plates/pre-tool.md +1 -1
  101. package/docs-site/docs/plates/processor.md +1 -1
  102. package/docs-site/docs/plates/record-map.md +1 -1
  103. package/docs-site/docs/plates/reflection-page.md +1 -1
  104. package/docs-site/docs/plates/reporting.md +1 -1
  105. package/docs-site/docs/plates/review.md +1 -1
  106. package/docs-site/docs/plates/routing.md +1 -1
  107. package/docs-site/docs/plates/session-capture.md +1 -1
  108. package/docs-site/docs/plates/site-manual.md +1 -1
  109. package/docs-site/docs/plates/stamp-plate.md +1 -1
  110. package/docs-site/docs/plates/station-card.md +1 -1
  111. package/docs-site/docs/plates/station-heat.md +1 -1
  112. package/docs-site/docs/plates/suit-organs.md +1 -1
  113. package/docs-site/docs/plates/suit-settings.md +1 -1
  114. package/docs-site/docs/plates/suit-wear.md +1 -1
  115. package/docs-site/docs/plates/suit.md +3 -3
  116. package/docs-site/docs/plates/trail-state.md +1 -1
  117. package/docs-site/docs/plates/work-fresh.md +1 -1
  118. package/docs-site/docs/plates/write-home.md +1 -1
  119. package/llms.txt +16 -6
  120. package/package.json +4 -4
  121. package/scripts/foundry/README.md +2 -2
  122. package/scripts/foundry/release-gate.mjs +111 -15
  123. package/scripts/foundry/release.mjs +8 -0
  124. package/scripts/mjs/verify-confer-core.mjs +153 -29
  125. package/scripts/mjs/verify-pipeline-facets.mjs +1 -1
  126. package/scripts/mjs/verify-user-aside-core.mjs +3 -4
  127. package/scripts/node/bridge-mcp-wiring.cjs +236 -5
  128. package/scripts/node/confer-switch.cjs +66 -0
  129. package/scripts/node/install-bridges.cjs +44 -53
  130. package/scripts/node/pack-tmp-suit-proof.mjs +14 -6
  131. package/src/integrations/openclaw/plugin/xray-pre-tool/index.js +17 -2
  132. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  133. package/src/mcps/architect-tools.server.ts +26 -2
  134. package/src/mcps/config/__tests__/server-config-registry.test.ts +15 -6
  135. package/src/mcps/config/server-config-registry.ts +9 -6
  136. package/src/mcps/governance.server.ts +49 -0
  137. package/src/mcps/knowledge-skills/code-review.server.ts +8 -0
  138. package/src/mcps/knowledge-skills/security-audit.server.ts +11 -12
  139. package/src/mcps/mcp-client.ts +1 -1
  140. package/src/mcps/orchestrator/handlers/complexity-handler.ts +2 -1
  141. package/src/mcps/researcher.server.ts +19 -10
  142. package/src/skills/orchestrator/SKILL.md +12 -0
  143. package/vendor/@0xray/repertoire/README.md +1 -1
  144. package/vendor/@0xray/repertoire/scripts/run-live-synthesis-checkpoint.mjs +5 -51
  145. package/xray/agents_template.md +2 -2
  146. package/xray/features.json +5 -5
  147. package/xray/features.schema.json +1 -1
  148. package/docs-site/docs/plates/live-mesh.md +0 -41
@@ -1,10 +1,13 @@
1
- import type { GovernanceRole, GovernanceVote } from "./llm-governance-provider.js";
1
+ import type { GovernanceMiss, GovernanceRole, GovernanceVote } from "./llm-governance-provider.js";
2
2
  export type LocalConferInput = {
3
3
  role: GovernanceRole;
4
4
  highConfidenceTrapPresent?: boolean | undefined;
5
5
  recommendedAgent?: string | null | undefined;
6
6
  llmConfigured?: boolean | undefined;
7
+ miss?: GovernanceMiss | undefined;
7
8
  };
8
9
  /** Receipt when nested LLM is absent or returned no vote. Never approve — analyze_proposal feeds mergeVotes. */
9
10
  export declare function localConferVote(input: LocalConferInput): GovernanceVote;
11
+ /** Stop a model from planting a second DECISION or Verdict line inside the reasoning body. */
12
+ export declare function sanitizeGovernanceReasoning(reasoning: string): string;
10
13
  export declare function formatGovernanceVoteText(vote: GovernanceVote, extra?: string): string;
@@ -1,6 +1,13 @@
1
1
  /** Receipt when nested LLM is absent or returned no vote. Never approve — analyze_proposal feeds mergeVotes. */
2
2
  export function localConferVote(input) {
3
3
  const role = input.role;
4
+ if (input.miss && input.miss !== "not-configured") {
5
+ return {
6
+ decision: "abstain",
7
+ confidence: 0.5,
8
+ reasoning: `Local confer (${role}); nested LLM did not produce a vote (${input.miss}).`,
9
+ };
10
+ }
4
11
  if (input.llmConfigured) {
5
12
  return {
6
13
  decision: "abstain",
@@ -22,6 +29,20 @@ export function localConferVote(input) {
22
29
  reasoning: `Local confer (${role}); nested LLM not configured.`,
23
30
  };
24
31
  }
32
+ function abstainedWithoutUsableVote(vote) {
33
+ if (vote.decision !== "abstain")
34
+ return false;
35
+ return /nested LLM not configured|did not produce a vote|returned no vote/i.test(vote.reasoning);
36
+ }
37
+ /** Stop a model from planting a second DECISION or Verdict line inside the reasoning body. */
38
+ export function sanitizeGovernanceReasoning(reasoning) {
39
+ return reasoning
40
+ .split("\n")
41
+ .map((line) => /^\s*(?:[-*•]\s*)?(?:Verdict:|DECISION:)/i.test(line) ? `> ${line.trim()}` : line)
42
+ .join("\n");
43
+ }
25
44
  export function formatGovernanceVoteText(vote, extra = "") {
26
- return `DECISION: ${vote.decision}\nCONFIDENCE: ${vote.confidence.toFixed(2)}\nREASONING: ${vote.reasoning}${extra}`;
45
+ const reviewLine = abstainedWithoutUsableVote(vote) ? "\nVerdict: UNREVIEWED" : "";
46
+ const reasoning = sanitizeGovernanceReasoning(vote.reasoning);
47
+ return `DECISION: ${vote.decision}\nCONFIDENCE: ${vote.confidence.toFixed(2)}\nREASONING: ${reasoning}${reviewLine}${extra}`;
27
48
  }
@@ -3,7 +3,8 @@
3
3
  "hooks": {
4
4
  "preToolUse": [
5
5
  {
6
- "command": ".cursor/hooks/pre-tool-use.sh"
6
+ "command": ".cursor/hooks/pre-tool-use.sh",
7
+ "failClosed": true
7
8
  }
8
9
  ],
9
10
  "preCompact": [
@@ -18,12 +19,14 @@
18
19
  ],
19
20
  "beforeShellExecution": [
20
21
  {
21
- "command": ".cursor/hooks/before-shell-execution.sh"
22
+ "command": ".cursor/hooks/before-shell-execution.sh",
23
+ "failClosed": true
22
24
  }
23
25
  ],
24
26
  "beforeReadFile": [
25
27
  {
26
- "command": ".cursor/hooks/before-read-file.sh"
28
+ "command": ".cursor/hooks/before-read-file.sh",
29
+ "failClosed": true
27
30
  }
28
31
  ]
29
32
  }
@@ -5,23 +5,40 @@
5
5
  * Does not rely on sessionStart (unavailable on managed cloud).
6
6
  */
7
7
 
8
- import {
9
- appendHookActivity,
10
- checkCodexPatterns,
11
- checkFullTestSuite,
12
- cursorGateRoot,
13
- cursorHeatRoots,
14
- cursorSessionId,
15
- cursorToolContext,
16
- cursorWorkspaceRoot,
17
- ensureCursorSessionBoot,
18
- evaluatePreToolGate,
19
- isShellTool,
20
- isWriteTool,
21
- loadDelegationGateFeatures,
22
- loadFeatures,
23
- readStdinJson,
24
- } from './cursor-hook-utils.js';
8
+ // Gate modules load inside main()'s try (dynamic import): a missing or broken module must deny, not crash
9
+ // before the handler with no JSON (Cursor would then fail open). Nothing here may import them statically.
10
+ let appendHookActivity = () => {};
11
+
12
+ /** Deny JSON on stdout and exit 0. Used when the gate cannot run at all. */
13
+ function denyHard(reason) {
14
+ try {
15
+ process.stdout.write(`${JSON.stringify({ permission: 'deny', user_message: reason, agent_message: reason })}\n`);
16
+ } finally {
17
+ process.exit(0);
18
+ }
19
+ }
20
+
21
+ export function cursorHookErrorPermission(err) {
22
+ if (err?.code === 'EACCES') return 'allow'; // code only: a message mentioning EACCES is not a permission error
23
+ return 'deny';
24
+ }
25
+
26
+ // Bootstrap: a fresh checkout has no build yet, so gate modules can be missing.
27
+ // Only the exact build commands may run then, so the hook cannot block the build that creates it.
28
+ const BOOTSTRAP_CMDS = new Set(['npm ci', 'npm run build', 'npm ci && npm run build', 'npm install && npm run build']);
29
+ export function cursorBootstrapPermission(err, event) {
30
+ if (err?.code !== 'ERR_MODULE_NOT_FOUND') return 'deny';
31
+ const cmd = String(event?.tool_input?.command ?? event?.command ?? '').trim().replace(/\s+/g, ' ');
32
+ return BOOTSTRAP_CMDS.has(cmd) ? 'allow' : 'deny';
33
+ }
34
+
35
+ async function readRawEvent() {
36
+ try {
37
+ let raw = '';
38
+ for await (const c of process.stdin) raw += c;
39
+ return JSON.parse(raw);
40
+ } catch { return null; }
41
+ }
25
42
 
26
43
  function finish(root, permission, reason, hint, toolName, extra = {}) {
27
44
  const out = { permission };
@@ -32,22 +49,38 @@ function finish(root, permission, reason, hint, toolName, extra = {}) {
32
49
  if (hint && permission === 'allow') {
33
50
  out.user_message = hint;
34
51
  }
52
+ if (extra.hookSpecificOutput) out.hookSpecificOutput = extra.hookSpecificOutput;
35
53
  console.log(JSON.stringify(out));
36
- appendHookActivity(root, 'cursor-pre-tool-use', permission, permission === 'deny' ? 'error' : 'info', {
37
- tool: toolName,
38
- reason: reason || hint || null,
39
- gate: extra.gate || null,
40
- livePath: true,
41
- });
54
+ try {
55
+ appendHookActivity(root, 'cursor-pre-tool-use', permission, permission === 'deny' ? 'error' : 'info', {
56
+ tool: toolName,
57
+ reason: reason || hint || null,
58
+ gate: extra.gate || null,
59
+ livePath: true,
60
+ });
61
+ } catch { /* the verdict is already on stdout; activity logging must not change it */ }
42
62
  process.exit(0);
43
63
  }
44
64
 
45
65
  async function main() {
46
- const fallbackRoot = cursorWorkspaceRoot();
66
+ let fallbackRoot = process.cwd();
47
67
  let toolName = 'unknown';
48
68
 
49
69
  try {
70
+ const u = await import('./cursor-hook-utils.js');
71
+ const { lensDecision, repertoireRoute } = await import('../../hooks/pre-tool-speak.mjs');
72
+ const {
73
+ checkCodexPatterns, checkFullTestSuite, cursorGateRoot, cursorHeatRoots, cursorSessionId, cursorToolContext,
74
+ cursorWorkspaceRoot, ensureCursorSessionBoot, evaluatePreToolGate, isShellTool, isWriteTool,
75
+ loadDelegationGateFeatures, loadFeatures, readStdinJson,
76
+ } = u;
77
+ appendHookActivity = u.appendHookActivity;
78
+ fallbackRoot = cursorWorkspaceRoot();
50
79
  const event = await readStdinJson();
80
+ // readStdinJson returns {} for empty or malformed stdin; Cursor always sends a JSON object, so deny those.
81
+ if (!event || typeof event !== 'object' || Array.isArray(event) || Object.keys(event).length === 0) {
82
+ finish(fallbackRoot, 'deny', 'preToolUse: empty or malformed hook input — denied', null, toolName, { gate: 'bad-input' });
83
+ }
51
84
  const eventRoot = cursorWorkspaceRoot(event);
52
85
  const sessionId = cursorSessionId(event);
53
86
  const heatRoots = cursorHeatRoots(event);
@@ -60,7 +93,7 @@ async function main() {
60
93
  const gateFeatures = loadDelegationGateFeatures(gateRoot, 'cursor');
61
94
  const ctx = cursorToolContext(event);
62
95
  toolName = ctx.toolName;
63
- const { content, cmd, toolInput } = ctx;
96
+ const { content, cmd, toolInput, paths } = ctx;
64
97
 
65
98
  const gateBlock = evaluatePreToolGate(toolName, toolInput, {
66
99
  projectRoot: gateRoot,
@@ -77,35 +110,65 @@ async function main() {
77
110
  if (extraBlock) finish(gateRoot, 'deny', extraBlock, null, toolName);
78
111
  }
79
112
 
113
+ const spoken = [content, cmd, toolInput.pattern, toolInput.query, ...(paths || [])].filter(Boolean).join('\n');
114
+ const lensStop = lensDecision(gateRoot, toolName, spoken);
115
+ if (lensStop?.decision === 'deny') {
116
+ finish(gateRoot, 'deny', lensStop.reason, null, toolName, { gate: lensStop.gate });
117
+ }
118
+ const route = repertoireRoute([toolName, cmd, content].filter(Boolean).join(' '));
119
+
80
120
  if (isShellTool(toolName) && cmd) {
81
121
  const testHint = checkFullTestSuite(cmd, features);
82
- if (testHint) finish(gateRoot, 'allow', null, testHint, toolName);
122
+ if (testHint) finish(gateRoot, 'allow', null, testHint, toolName, { ...(route || {}) });
83
123
  }
84
124
 
85
125
  if (gateBlock.reason) {
86
126
  finish(gateRoot, 'allow', gateBlock.reason, gateBlock.hint, toolName, {
87
127
  gate: gateBlock.gate,
88
128
  warn: true,
129
+ ...(route || {}),
89
130
  });
90
131
  }
91
132
 
92
- finish(gateRoot, 'allow', null, null, toolName);
133
+ finish(gateRoot, 'allow', null, null, toolName, { ...(route || {}) });
93
134
  } catch (err) {
94
- appendHookActivity(fallbackRoot, 'cursor-pre-tool-use', 'hook-error', 'error', {
95
- tool: toolName,
96
- error: err.message,
97
- failOpen: true,
98
- });
99
- // Dest EACCES on a Cloud workspace wrapper must not deny every tool.
135
+ if (err?.code === 'ERR_MODULE_NOT_FOUND' && cursorBootstrapPermission(err, await readRawEvent()) === 'allow') {
136
+ console.log(JSON.stringify({ permission: 'allow', user_message: 'preToolUse: build not present — bootstrap build allowed' }));
137
+ process.exit(0);
138
+ }
139
+ const failOpen = cursorHookErrorPermission(err) === 'allow';
140
+ try {
141
+ appendHookActivity(fallbackRoot, 'cursor-pre-tool-use', 'hook-error', 'error', {
142
+ tool: toolName,
143
+ error: err.message,
144
+ failOpen,
145
+ });
146
+ } catch { /* logging must not turn a deny into a crash */ }
147
+ if (failOpen) {
148
+ // Dest EACCES on a Cloud workspace wrapper must not deny every tool.
149
+ finish(
150
+ fallbackRoot,
151
+ 'allow',
152
+ `preToolUse hook error — fail open: ${err.message}`,
153
+ null,
154
+ toolName,
155
+ { gate: 'hook-error-fail-open' },
156
+ );
157
+ }
100
158
  finish(
101
159
  fallbackRoot,
102
- 'allow',
103
- `preToolUse hook error — fail open: ${err.message}`,
160
+ 'deny',
161
+ `preToolUse hook error blocked the tool: ${err.message}`,
104
162
  null,
105
163
  toolName,
106
- { gate: 'hook-error-fail-open' },
164
+ { gate: 'hook-error' },
107
165
  );
108
166
  }
109
167
  }
110
168
 
111
- main();
169
+ const launchedAsCli = Boolean(process.argv[1] && process.argv[1].endsWith('pre-tool-use.js'));
170
+ if (launchedAsCli) {
171
+ process.on('uncaughtException', (err) => denyHard(`preToolUse hook crashed — denied: ${err?.message || err}`));
172
+ process.on('unhandledRejection', (err) => denyHard(`preToolUse hook crashed — denied: ${err?.message || err}`));
173
+ main().catch((err) => denyHard(`preToolUse hook crashed — denied: ${err?.message || err}`));
174
+ }
@@ -110,7 +110,15 @@ if [ -n "$MILL" ]; then
110
110
  fi
111
111
 
112
112
  if [ -z "$NODE_BIN" ] || [ -z "$MILL" ] || [ ! -f "${MILL}/${REL}" ]; then
113
- printf '{"permission":"allow","user_message":"xray-cloud-hook: mill missing — fail open"}\n'
113
+ # Gate events fail closed: no node or no hook file means the gate cannot run, so the tool is denied.
114
+ case "$EVENT" in
115
+ preToolUse|beforeShellExecution|beforeReadFile)
116
+ printf '{"permission":"deny","user_message":"xray-cloud-hook: node or hook file missing — denied (fail closed)","agent_message":"xray-cloud-hook: node or hook file missing — denied (fail closed)"}\n'
117
+ ;;
118
+ *)
119
+ printf '{"permission":"allow","user_message":"xray-cloud-hook: node or hook file missing — %s skipped"}\n' "$EVENT"
120
+ ;;
121
+ esac
114
122
  exit 0
115
123
  fi
116
124
 
@@ -17,7 +17,6 @@
17
17
  import { frameworkLogger } from '../../core/framework-logger.js';
18
18
  import fs from 'fs';
19
19
  import path from 'path';
20
- import { execSync } from 'child_process';
21
20
  import { createRequire } from 'module';
22
21
  import { fileURLToPath } from 'node:url';
23
22
  import { syncBuiltinSkills } from '../../cli/commands/skill-install.js';
@@ -27,27 +26,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
26
  const packageRoot = path.resolve(__dirname, '..', '..', '..');
28
27
  const requireCjs = createRequire(import.meta.url);
29
28
  const { resolveConsumerTargetDir, patchGrokHooks } = requireCjs(path.join(packageRoot, 'scripts/node/install-bridges.cjs'));
30
- const { XRAY_MCP_SERVERS, resolveRepertoireMcp, resolveGogglesMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
31
- function registerGrokMcpServers(targetDir) {
32
- try {
33
- execSync('which grok', { stdio: 'ignore' });
34
- }
35
- catch {
36
- console.log('[Grok] grok CLI not on PATH — plugin .mcp.json still configured');
37
- return;
38
- }
39
- for (const s of XRAY_MCP_SERVERS) {
40
- try {
41
- const envEntries = { ...s.env, XRAY_ROOT: targetDir };
42
- const envFlags = Object.entries(envEntries)
43
- .map(([k, v]) => `--env "${k}=${v}"`)
44
- .join(' ');
45
- execSync(`grok mcp add ${s.name} --command npx --args "-y" "0xray" "mcp" "${s.mcpCmd}" ${envFlags}`, { stdio: 'pipe' });
46
- }
47
- catch {
48
- // already registered or grok config conflict — non-blocking
49
- }
50
- }
29
+ const { resolveRepertoireMcp, resolveGogglesMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
30
+ function registerGrokMcpServers(targetDir, env) {
31
+ const wiring = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
32
+ const tomlPath = wiring.writeProjectSuitMcp(targetDir);
33
+ const trustPath = wiring.trustGrokFolder(targetDir, env);
34
+ process.stdout.write(`Project Grok MCP written to ${tomlPath}\n`);
35
+ process.stdout.write(`Grok folder trust written to ${trustPath}\n`);
51
36
  }
52
37
  export async function installForGrokCLI(options = {}) {
53
38
  frameworkLogger.log('grok-integration', 'install-start', 'info', {
@@ -115,21 +100,9 @@ export async function installForGrokCLI(options = {}) {
115
100
  writeProjectRepertoireMcp(targetDir);
116
101
  writeProjectGogglesMcp(targetDir);
117
102
  mintAfterWear(targetDir);
118
- const primary = dests[0];
119
- try {
120
- execSync(`grok plugins trust "${primary}"`, { stdio: 'ignore' });
121
- console.log('\x1b[32m✓ Auto-trusted the 0xray plugin with Grok CLI\x1b[0m');
122
- }
123
- catch {
124
- console.log('\nPlease run this command to fully trust the plugin:');
125
- console.log(` grok plugins trust "${primary}"`);
126
- }
127
- if (isolated) {
128
- registerGrokMcpServers(targetDir);
129
- console.log('\x1b[32m✓ Registered 7 xray MCP servers with Grok CLI (npx)\x1b[0m');
130
- }
131
- else {
132
- console.log('\x1b[32m✓ Project-scoped Grok plugin worn (machine ~/.grok/plugins/0xray not written)\x1b[0m');
103
+ registerGrokMcpServers(targetDir, env);
104
+ if (!isolated) {
105
+ process.stdout.write('Project-scoped Grok plugin worn (machine ~/.grok/plugins/0xray not written)\n');
133
106
  }
134
107
  console.log('\n✅ 0xRay is now installed as a first-class Grok CLI plugin!');
135
108
  console.log('Restart Grok or run `grok` to load the new hooks and MCP servers.');
@@ -19,7 +19,7 @@ import {
19
19
  updatePlanTodoStatusInPlace,
20
20
  loadDelegationGateFeatures,
21
21
  } from '../../hooks/delegation-gate-runtime.mjs';
22
- import { isConferPendingForSession } from '../../hooks/confer-hook-runtime.mjs';
22
+ import { isConferPendingForSession, readConferReviewHint } from '../../hooks/confer-hook-runtime.mjs';
23
23
  import { getActiveUserAsideBoot } from '../../hooks/user-aside-hook-runtime.mjs';
24
24
  import {
25
25
  applyStationHeat,
@@ -308,6 +308,7 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
308
308
  const sessionId =
309
309
  extra.sessionId || process.env.GROK_SESSION_ID || process.env.GROK_SESSION || null;
310
310
  const conferPending = loadConferPending(root, sessionId);
311
+ const conferReview = readConferReviewHint(root);
311
312
  const userAsideBoot = getActiveUserAsideBoot(root, sessionId);
312
313
  const gateFeatures = loadDelegationGateFeatures(root, 'grok');
313
314
  const frontier = gateFeatures.ceremony === 'lite';
@@ -332,6 +333,12 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
332
333
  ...heat,
333
334
  ...(siblingRoots.length > 0 ? { siblingWorkspaceRoots: siblingRoots } : {}),
334
335
  ...(conferPending ? { conferPending: true, conferTrigger: 'analyze-complexity at synthesis checkpoint' } : {}),
336
+ ...(conferReview
337
+ ? {
338
+ conferReview,
339
+ conferReviewHint: 'UNREVIEWED — abstain without a governance LLM blocks the consult todo',
340
+ }
341
+ : {}),
335
342
  ...(userAsideBoot ?? {}),
336
343
  sessionId,
337
344
  ...extra,
@@ -22,65 +22,9 @@ import {
22
22
  } from './grok-hook-utils.js';
23
23
  import { appendHookActivity } from './grok-hook-activity.js';
24
24
  import { cardStop, lensBeforeResearch } from '../../hooks/goggles-pipeline.mjs';
25
- import { createRequire } from 'node:module';
26
- import { existsSync } from 'node:fs';
27
- import { dirname, join } from 'node:path';
28
- import { fileURLToPath } from 'node:url';
29
-
30
- const hookDir = dirname(fileURLToPath(import.meta.url));
31
- let scoreAndRouteFn;
32
-
33
- function loadScoreAndRoute() {
34
- if (scoreAndRouteFn !== undefined) return scoreAndRouteFn;
35
- scoreAndRouteFn = null;
36
- const require = createRequire(import.meta.url);
37
- const candidates = [
38
- join(hookDir, '../../../nucleus/thin-dispatch.js'),
39
- join(hookDir, '../../../../dist/nucleus/thin-dispatch.js'),
40
- ];
41
- for (const candidate of candidates) {
42
- if (!existsSync(candidate)) continue;
43
- try {
44
- const loaded = require(candidate);
45
- if (typeof loaded.scoreAndRoute === 'function') {
46
- scoreAndRouteFn = loaded.scoreAndRoute;
47
- return scoreAndRouteFn;
48
- }
49
- } catch {
50
- /* next built copy */
51
- }
52
- }
53
- return null;
54
- }
55
-
56
- /** Speak only when the organ changes the agent. A match that leaves the agent is quiet. */
57
- export function repertoireDecisionFields(routed) {
58
- if (!routed?.memoryRouting?.overridden || !routed.agent) return null;
59
- const signals = Array.isArray(routed.memoryRouting.signals)
60
- ? routed.memoryRouting.signals.slice(0, 4).filter(Boolean)
61
- : [];
62
- const why = signals.length ? ` on ${signals.join(', ')}` : '';
63
- return {
64
- agent: routed.agent,
65
- gate: 'repertoire',
66
- hookSpecificOutput: {
67
- hookEventName: 'PreToolUse',
68
- additionalContext: `Repertoire changed the route to ${routed.agent}${why}. Follow that agent for this work.`,
69
- },
70
- };
71
- }
25
+ import { repertoireDecisionFields, repertoireRoute } from '../../hooks/pre-tool-speak.mjs';
72
26
 
73
- function repertoireRoute(operation) {
74
- const text = String(operation || '').slice(0, 500).trim();
75
- if (!text) return null;
76
- try {
77
- const scoreAndRoute = loadScoreAndRoute();
78
- if (!scoreAndRoute) return null;
79
- return repertoireDecisionFields(scoreAndRoute(text, {}));
80
- } catch {
81
- return null;
82
- }
83
- }
27
+ export { repertoireDecisionFields };
84
28
 
85
29
  function finish(root, decision, reason, hint, toolName, extra = {}) {
86
30
  const out = { decision, ...extra };
@@ -914,9 +914,22 @@ async function handleDelegationGate(command, projectRoot, logDir) {
914
914
  logDir,
915
915
  `[delegation-gate] deny tool=${tool} gate=${outcome.gate} reason=${outcome.reason}`,
916
916
  );
917
+ return { phase: "pre", ...outcome };
917
918
  }
918
919
 
919
- return { phase: "pre", ...outcome };
920
+ const spoken = `${tool} ${JSON.stringify(args)}`.slice(0, 500);
921
+ const { lensDecision, repertoireRoute } = await import("../hooks/pre-tool-speak.mjs");
922
+ const lens = lensDecision(projectRoot, tool, spoken);
923
+ if (lens) {
924
+ return {
925
+ allow: false,
926
+ phase: "pre",
927
+ gate: lens.gate || "lens",
928
+ reason: lens.reason,
929
+ };
930
+ }
931
+ const route = repertoireRoute(spoken);
932
+ return { phase: "pre", ...outcome, ...(route || {}) };
920
933
  } catch (error) {
921
934
  const message = `delegation-gate failed: ${error.message || error}`;
922
935
  logToActivity(logDir, `[delegation-gate] error ${message}`);
@@ -3,7 +3,7 @@
3
3
  * Lazy-load confer SSOT for Grok session-boot hints.
4
4
  */
5
5
  import { createRequire } from 'node:module';
6
- import { existsSync } from 'node:fs';
6
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
7
7
  import { dirname, join, resolve } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
 
@@ -46,4 +46,26 @@ export function isConferPendingForSession(projectRoot, sessionId = null) {
46
46
  const mod = loadConfer();
47
47
  if (!mod?.isConferPending) return false;
48
48
  return mod.isConferPending(projectRoot, sessionId);
49
+ }
50
+
51
+ /** 'UNREVIEWED' when a consult receipt recorded abstain-without-model. */
52
+ export function readConferReviewHint(projectRoot) {
53
+ const stateDir = join(projectRoot, '.xray', 'state');
54
+ if (!existsSync(stateDir)) return null;
55
+ let names;
56
+ try {
57
+ names = readdirSync(stateDir);
58
+ } catch {
59
+ return null;
60
+ }
61
+ for (const name of names) {
62
+ if (!name.startsWith('synthesis-consult-') || !name.endsWith('.json')) continue;
63
+ try {
64
+ const receipt = JSON.parse(readFileSync(join(stateDir, name), 'utf8'));
65
+ if (receipt && receipt.verdict === 'UNREVIEWED') return 'UNREVIEWED';
66
+ } catch {
67
+ /* unreadable receipt is not a hint */
68
+ }
69
+ }
70
+ return null;
49
71
  }
@@ -68,8 +68,9 @@ function loadDelegationGate() {
68
68
  ].filter(Boolean);
69
69
  const found = candidates.find((p) => existsSync(p));
70
70
  if (!found) {
71
- throw new Error(
72
- 'delegation-gate.js missing — run npm run build in 0xray or npm install 0xray',
71
+ throw Object.assign(
72
+ new Error('delegation-gate.js missing — run npm run build in 0xray or npm install 0xray'),
73
+ { code: 'ERR_MODULE_NOT_FOUND' },
73
74
  );
74
75
  }
75
76
  return createRequire(import.meta.url)(found);
@@ -3,7 +3,7 @@
3
3
  * Tools: look, status_lens. Same organ as the CLI.
4
4
  */
5
5
  import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
6
- import { basename, dirname, join, resolve } from 'node:path';
6
+ import { dirname, join, resolve } from 'node:path';
7
7
  import { pathToFileURL, fileURLToPath } from 'node:url';
8
8
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -12,17 +12,9 @@ import * as bundled from './goggles-pipeline.mjs';
12
12
 
13
13
  const HERE = fileURLToPath(new URL('.', import.meta.url));
14
14
  const SERVER_NAME = 'goggles';
15
- const CARD_PLANES = [
16
- 'ground',
17
- 'routing',
18
- 'house',
19
- 'boot',
20
- 'governance',
21
- 'memory-recall',
22
- 'orchestration',
23
- 'processor',
24
- 'reporting',
25
- ];
15
+ /** Card planes come from the organ, not a hand-kept copy. */
16
+ const CARD_PLANES = Array.isArray(bundled.CARD_PLANES) ? bundled.CARD_PLANES : [];
17
+ const CARD_OUTERS = ['digest', 'triage'];
26
18
  const SCOPES = ['ecosystem', 'part', 'one flow', 'one artifact'];
27
19
 
28
20
  const LOOK_SCHEMA = {
@@ -31,7 +23,7 @@ const LOOK_SCHEMA = {
31
23
  properties: {
32
24
  kind: {
33
25
  type: 'string',
34
- description: 'Optional. Use "0" or "actuality" for the lens. Omit when naming an outer.',
26
+ description: 'Use "0" or "actuality" for the lens. Send kind, outer, or plane: an empty look is refused. Never with outer: kind and outer together is refused.',
35
27
  enum: ['0', 'actuality'],
36
28
  },
37
29
  outer: {
@@ -46,7 +38,7 @@ const LOOK_SCHEMA = {
46
38
  },
47
39
  plane: {
48
40
  type: 'string',
49
- description: 'Card plane for digest or triage only. Not the lens.',
41
+ description: 'Card plane for digest or triage only. Not the lens. A plane with any other outer is refused.',
50
42
  enum: CARD_PLANES,
51
43
  },
52
44
  },
@@ -55,7 +47,7 @@ const LOOK_SCHEMA = {
55
47
  export const TOOLS = [
56
48
  {
57
49
  name: 'look',
58
- description: 'Kind 0 is quiet on a match. It checks the six outer names. One outer is a reading. digest and triage return a card.',
50
+ description: 'Kind 0 is quiet on a match. It checks the six outer names, then sweeps every card plane for missing shipped files and names the first drift. One outer is a reading. digest and triage return a card. Refused: an empty look; kind with outer; a plane with an outer other than digest or triage.',
59
51
  inputSchema: LOOK_SCHEMA,
60
52
  },
61
53
  {
@@ -152,6 +144,15 @@ function checkArgs(args) {
152
144
  if (args.plane != null && !CARD_PLANES.includes(args.plane)) {
153
145
  return deny('invalid_args', 'plane is a card plane.');
154
146
  }
147
+ if (args.kind == null && args.outer == null && args.plane == null) {
148
+ return deny('invalid_args', 'Name a kind, one outer, or one plane.');
149
+ }
150
+ if (args.kind != null && args.outer != null) {
151
+ return deny('invalid_args', 'Send kind or outer, not both.');
152
+ }
153
+ if (args.plane != null && args.outer != null && !CARD_OUTERS.includes(args.outer)) {
154
+ return deny('invalid_args', 'plane goes with digest or triage only.');
155
+ }
155
156
  return null;
156
157
  }
157
158
 
@@ -161,7 +162,7 @@ function publicCard(view) {
161
162
  if (Array.isArray(view.empty) && view.empty.length) notes.push(`Empty: ${view.empty.join(', ')}`);
162
163
  if (view.exam) notes.push(view.exam);
163
164
  }
164
- const skills = view.skills ? basename(String(view.skills)) : '';
165
+ const skills = view.skills ? String(view.skills) : '';
165
166
  const cascade = view.cascade
166
167
  ? {
167
168
  plane: view.cascade.plane,
@@ -227,7 +228,8 @@ export async function answerLook(raw, root) {
227
228
  const here = projectRoot(root);
228
229
  if (pureKind0(args)) {
229
230
  api.maintainLens(here);
230
- const line = api.actualityLine();
231
+ const line = api.actualityLine() || cardDrift(api, here);
232
+ if (line) writeLens(api, here, line);
231
233
  if (!line) return quiet('kind0');
232
234
  return { ok: true, quiet: false, mode: 'kind0', content: line };
233
235
  }
@@ -269,6 +271,22 @@ export async function answerLook(raw, root) {
269
271
  };
270
272
  }
271
273
 
274
+ /** Kind 0 card sweep: triage's file check on every card plane, shipped paths only. */
275
+ function cardDrift(api, here) {
276
+ if (typeof api.shippedDrift !== 'function') return '';
277
+ try {
278
+ return api.shippedDrift(here);
279
+ } catch {
280
+ return '';
281
+ }
282
+ }
283
+
284
+ function writeLens(api, here, line) {
285
+ const file = api.lensPath(here);
286
+ mkdirSync(dirname(file), { recursive: true });
287
+ writeFileSync(file, `${line}\n`);
288
+ }
289
+
272
290
  function lensBody(root) {
273
291
  const file = join(root, '.xray', 'state', 'LENS.md');
274
292
  if (!existsSync(file)) return { found: false, text: '' };