0xray 4.0.31 → 4.0.33

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 (98) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.mcp.json +8 -0
  3. package/.opencode/agents/api-design.yml +31 -0
  4. package/.opencode/agents/architect.yml +15 -0
  5. package/.opencode/agents/architecture-patterns.yml +14 -0
  6. package/.opencode/agents/auto-format.yml +14 -0
  7. package/.opencode/agents/backend-engineer.yml +12 -0
  8. package/.opencode/agents/boot-orchestrator.yml +14 -0
  9. package/.opencode/agents/bug-triage-specialist.yml +15 -0
  10. package/.opencode/agents/code-analyzer.yml +12 -0
  11. package/.opencode/agents/code-reviewer.yml +14 -0
  12. package/.opencode/agents/content-creator.yml +12 -0
  13. package/.opencode/agents/database-engineer.yml +12 -0
  14. package/.opencode/agents/devops-engineer.yml +12 -0
  15. package/.opencode/agents/framework-compliance-audit.yml +14 -0
  16. package/.opencode/agents/frontend-engineer.yml +12 -0
  17. package/.opencode/agents/frontend-ui-ux-engineer.yml +12 -0
  18. package/.opencode/agents/git-workflow.yml +15 -0
  19. package/.opencode/agents/growth-strategist.yml +12 -0
  20. package/.opencode/agents/hermes-agent.yml +12 -0
  21. package/.opencode/agents/inference-improve.yml +12 -0
  22. package/.opencode/agents/lint.yml +14 -0
  23. package/.opencode/agents/log-monitor.yml +12 -0
  24. package/.opencode/agents/mobile-developer.yml +12 -0
  25. package/.opencode/agents/model-health-check.yml +12 -0
  26. package/.opencode/agents/multimodal-looker.yml +12 -0
  27. package/.opencode/agents/performance-analysis.yml +12 -0
  28. package/.opencode/agents/performance-engineer.yml +12 -0
  29. package/.opencode/agents/performance-optimization.yml +12 -0
  30. package/.opencode/agents/processor-pipeline.yml +14 -0
  31. package/.opencode/agents/project-analysis.yml +12 -0
  32. package/.opencode/agents/refactorer.yml +12 -0
  33. package/.opencode/agents/researcher.yml +12 -0
  34. package/.opencode/agents/security-auditor.yml +12 -0
  35. package/.opencode/agents/security-scan.yml +12 -0
  36. package/.opencode/agents/seo-consultant.yml +12 -0
  37. package/.opencode/agents/session-management.yml +12 -0
  38. package/.opencode/agents/state-manager.yml +12 -0
  39. package/.opencode/agents/storyteller.yml +12 -0
  40. package/.opencode/agents/strategist.yml +12 -0
  41. package/.opencode/agents/tech-writer.yml +12 -0
  42. package/.opencode/agents/testing-best-practices.yml +12 -0
  43. package/.opencode/agents/testing-lead.yml +12 -0
  44. package/.opencode/agents/ui-ux-design.yml +12 -0
  45. package/.opencode/codex.codex +8 -0
  46. package/.opencode/commands/auto-format.md +99 -0
  47. package/.opencode/commands/auto-summary-capture.md +90 -0
  48. package/.opencode/commands/dependency-audit.md +184 -0
  49. package/.opencode/commands/enforcer-daily-scan.md +137 -0
  50. package/.opencode/commands/framework-compliance-audit.md +205 -0
  51. package/.opencode/commands/interactive-validator.md +75 -0
  52. package/.opencode/commands/job-summary-logger.md +68 -0
  53. package/.opencode/commands/lint.md +11 -0
  54. package/.opencode/commands/mode-switch.md +95 -0
  55. package/.opencode/commands/model-health-check.md +186 -0
  56. package/.opencode/commands/performance-analysis.md +144 -0
  57. package/.opencode/commands/pre-commit-introspection.md +185 -0
  58. package/.opencode/commands/pre-commit-introspection.sh +133 -0
  59. package/.opencode/commands/security-scan.md +157 -0
  60. package/.opencode/commands/sisyphus-validation.md +128 -0
  61. package/.opencode/commands/summary-logger.md +83 -0
  62. package/.opencode/enforcer-config.json +282 -0
  63. package/.opencode/skills/orchestrator/SKILL.md +5 -5
  64. package/.opencode/workflows/post-deployment-audit.yml +123 -0
  65. package/.xray/features.json +1 -1
  66. package/dist/CHANGELOG.md +16 -0
  67. package/dist/cli/commands/status.d.ts +4 -2
  68. package/dist/cli/commands/status.js +6 -3
  69. package/dist/cli/commands/validate.js +4 -0
  70. package/dist/cli/goggles-status.d.ts +13 -0
  71. package/dist/cli/goggles-status.js +76 -0
  72. package/dist/cli/index.js +31 -7
  73. package/dist/governance/governance-service.js +11 -1
  74. package/dist/integrations/grok/grok-cli.d.ts +3 -0
  75. package/dist/integrations/grok/grok-cli.js +41 -1
  76. package/dist/integrations/grok/hooks/grok-hook-utils.js +7 -4
  77. package/dist/integrations/grok/hooks/pre-tool-use.js +7 -1
  78. package/dist/integrations/grok/hooks/session-start.js +5 -2
  79. package/dist/integrations/hooks/goggles-mcp.mjs +338 -0
  80. package/dist/integrations/hooks/goggles-pipeline.d.mts +2 -0
  81. package/dist/integrations/hooks/goggles-pipeline.mjs +485 -100
  82. package/dist/integrations/hooks/plates.cjs +15 -1
  83. package/dist/integrations/hooks/station-hook-runtime.cjs +53 -5
  84. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  85. package/dist/mcps/orchestrator/config/agent-capabilities.js +5 -0
  86. package/dist/nucleus/thin-dispatch.d.ts +4 -0
  87. package/dist/nucleus/thin-dispatch.js +9 -1
  88. package/dist/skills/orchestrator/SKILL.md +5 -5
  89. package/package.json +2 -1
  90. package/scripts/foundry/assert-packed-dist-cli.mjs +4 -0
  91. package/scripts/foundry/mint-suit.cjs +6 -1
  92. package/scripts/mjs/run-goggles-mcp.mjs +41 -0
  93. package/scripts/node/bridge-mcp-wiring.cjs +61 -21
  94. package/scripts/node/install-bridges.cjs +24 -7
  95. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  96. package/src/mcps/orchestrator/config/agent-capabilities.ts +4 -0
  97. package/src/skills/orchestrator/SKILL.md +5 -5
  98. package/xray/features.json +1 -1
@@ -10,6 +10,7 @@
10
10
  *
11
11
  * Usage: npx 0xray status
12
12
  */
13
+ import type { GogglesStatus } from "../goggles-status.js";
13
14
  interface StatusReport {
14
15
  opencode: {
15
16
  installed: boolean;
@@ -34,8 +35,9 @@ interface StatusReport {
34
35
  outcomesCount: number;
35
36
  patternsCount: number;
36
37
  };
38
+ goggles: GogglesStatus;
37
39
  }
38
- export declare function getStatusReport(cwd?: string): StatusReport;
40
+ export declare function getStatusReport(cwd?: string, packageRoot?: string): StatusReport;
39
41
  export declare function printStatus(report: StatusReport): void;
40
- export declare function statusCommand(): Promise<void>;
42
+ export declare function statusCommand(packageRoot?: string): Promise<void>;
41
43
  export default statusCommand;
@@ -14,6 +14,7 @@ import { readdirSync, existsSync, readFileSync } from "fs";
14
14
  import { join } from "path";
15
15
  import { getConfigDir } from "../../core/config-paths.js";
16
16
  import { featuresConfigLoader } from "../../core/features-config.js";
17
+ import { formatGogglesStatus, probeGoggles } from "../goggles-status.js";
17
18
  function getSkillsList(cwd) {
18
19
  const skills = [];
19
20
  const configDir = getConfigDir(cwd);
@@ -119,7 +120,7 @@ function getInferenceStatus(cwd) {
119
120
  catch { /* ignore */ }
120
121
  return { active, lastTuning, outcomesCount, patternsCount };
121
122
  }
122
- export function getStatusReport(cwd = process.cwd()) {
123
+ export function getStatusReport(cwd = process.cwd(), packageRoot = cwd) {
123
124
  const opencodeConfigPath = join(cwd, "opencode.json");
124
125
  const cwdSkills = getSkillsList(cwd);
125
126
  const agents = getAgentsList(cwd);
@@ -134,6 +135,7 @@ export function getStatusReport(cwd = process.cwd()) {
134
135
  agents,
135
136
  health,
136
137
  inference,
138
+ goggles: probeGoggles(packageRoot, cwd),
137
139
  };
138
140
  }
139
141
  export function printStatus(report) {
@@ -166,6 +168,7 @@ export function printStatus(report) {
166
168
  console.log(`⚙️ Inference: ${inferenceStatus} (${report.inference.outcomesCount} outcomes, ${report.inference.patternsCount} patterns)`);
167
169
  console.log(`📊 Health: ${healthStatus}`);
168
170
  console.log("");
171
+ process.stdout.write(`${formatGogglesStatus(report.goggles).join("\n")}\n`);
169
172
  if (report.skills.names.length > 0) {
170
173
  console.log("Installed Skills:");
171
174
  const cols = 4;
@@ -191,9 +194,9 @@ export function printStatus(report) {
191
194
  console.log("");
192
195
  }
193
196
  }
194
- export async function statusCommand() {
197
+ export async function statusCommand(packageRoot = process.cwd()) {
195
198
  const cwd = process.cwd();
196
- const report = getStatusReport(cwd);
199
+ const report = getStatusReport(cwd, packageRoot);
197
200
  printStatus(report);
198
201
  }
199
202
  export default statusCommand;
@@ -23,6 +23,10 @@ export const VALIDATE_PACK_PATHS = [
23
23
  "dist/integrations/cursor/hooks/pre-compact.sh",
24
24
  "dist/integrations/cursor/hooks/after-file-edit.sh",
25
25
  "dist/integrations/hooks/plates.cjs",
26
+ "dist/integrations/hooks/goggles-pipeline.mjs",
27
+ "dist/integrations/hooks/goggles-planes.json",
28
+ "dist/integrations/hooks/goggles-mcp.mjs",
29
+ "scripts/mjs/run-goggles-mcp.mjs",
26
30
  "docs-site/docs/plates/memory-recall.md",
27
31
  "docs-site/docs/plates/processor.md",
28
32
  "docs-site/docs/plates/goggles.md",
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Goggles on status and health.
3
+ * Organ present is not Kind 0. Quiet is a match. No phone-home.
4
+ */
5
+ export type GogglesStatus = {
6
+ organPresent: boolean;
7
+ kind0Text: string;
8
+ kind0Quiet: boolean;
9
+ kind0Label: string;
10
+ organLine: string;
11
+ };
12
+ export declare function probeGoggles(packageRoot: string, cwd: string): GogglesStatus;
13
+ export declare function formatGogglesStatus(probe: GogglesStatus): string[];
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Goggles on status and health.
3
+ * Organ present is not Kind 0. Quiet is a match. No phone-home.
4
+ */
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ const WORN_PLANES = ["dichotomy", "syncopate", "synthesis", "digest", "triage", "loop"];
8
+ const ORGAN_PAIRS = [
9
+ ["dist/integrations/hooks/goggles-pipeline.mjs", "dist/integrations/hooks/goggles-planes.json"],
10
+ ["src/integrations/hooks/goggles-pipeline.mjs", "src/integrations/hooks/goggles-planes.json"],
11
+ ];
12
+ function organPair(packageRoot) {
13
+ for (const pair of ORGAN_PAIRS) {
14
+ if (existsSync(join(packageRoot, pair[0])) && existsSync(join(packageRoot, pair[1]))) {
15
+ return pair;
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+ function lensText(cwd) {
21
+ const file = join(cwd, ".xray", "state", "LENS.md");
22
+ if (!existsSync(file))
23
+ return { found: false, text: "" };
24
+ try {
25
+ return { found: true, text: readFileSync(file, "utf8").trim() };
26
+ }
27
+ catch {
28
+ return { found: false, text: "" };
29
+ }
30
+ }
31
+ function mapDrift(packageRoot, planesRel) {
32
+ try {
33
+ const data = JSON.parse(readFileSync(join(packageRoot, planesRel), "utf8"));
34
+ const map = Array.isArray(data.planes) ? data.planes.map((name) => String(name)) : [];
35
+ const same = map.length === WORN_PLANES.length && map.every((name, index) => name === WORN_PLANES[index]);
36
+ return same ? "" : "Actuality. Drift: worn is not the map.";
37
+ }
38
+ catch {
39
+ return "";
40
+ }
41
+ }
42
+ export function probeGoggles(packageRoot, cwd) {
43
+ const pair = organPair(packageRoot);
44
+ const organPresent = pair !== null;
45
+ const lens = lensText(cwd);
46
+ let kind0Text = "";
47
+ let kind0Label = "(no LENS yet)";
48
+ let kind0Quiet = false;
49
+ if (lens.found) {
50
+ kind0Text = lens.text;
51
+ if (!lens.text || lens.text === "quiet (match)") {
52
+ kind0Quiet = true;
53
+ kind0Label = "quiet (match)";
54
+ }
55
+ else {
56
+ kind0Label = lens.text;
57
+ }
58
+ }
59
+ else if (pair) {
60
+ const drift = mapDrift(packageRoot, pair[1]);
61
+ if (drift) {
62
+ kind0Text = drift;
63
+ kind0Label = drift;
64
+ }
65
+ }
66
+ return {
67
+ organPresent,
68
+ kind0Text,
69
+ kind0Quiet,
70
+ kind0Label,
71
+ organLine: organPresent ? "Goggles: worn" : "Goggles: not found",
72
+ };
73
+ }
74
+ export function formatGogglesStatus(probe) {
75
+ return [probe.organLine, `Kind 0: ${probe.kind0Label}`];
76
+ }
package/dist/cli/index.js CHANGED
@@ -76,20 +76,42 @@ program
76
76
  process.exit(1);
77
77
  }
78
78
  });
79
- program
80
- .command("goggles")
81
- .argument("[args...]", "plane, then examine, triage, cascade, or teardown")
82
- .description("Glimpse every plane, or one plane's way in. A hit does not open the plane.")
83
- .action((args) => {
79
+ const LOOK_HELP = "Kind 0 is quiet on a match. One outer is a reading. digest and triage return a card.";
80
+ function gogglesScript() {
84
81
  const built = join(packageRoot, "dist", "integrations", "hooks", "goggles-pipeline.mjs");
85
82
  const source = join(packageRoot, "src", "integrations", "hooks", "goggles-pipeline.mjs");
86
- const script = existsSync(built) ? built : source;
83
+ return existsSync(built) ? built : source;
84
+ }
85
+ function runGoggles(args) {
86
+ const script = gogglesScript();
87
+ if (!existsSync(script)) {
88
+ process.stderr.write("Goggles: not found\n");
89
+ process.exit(1);
90
+ }
91
+ if (process.stdout.isTTY) {
92
+ const result = spawnSync(process.execPath, [script, ...(args ?? [])], { stdio: "inherit" });
93
+ process.exit(result.status ?? 1);
94
+ }
87
95
  const result = spawnSync(process.execPath, [script, ...(args ?? [])], { encoding: "utf8" });
88
96
  if (result.stdout)
89
97
  process.stdout.write(result.stdout);
90
98
  if (result.stderr)
91
99
  process.stderr.write(result.stderr);
92
100
  process.exit(result.status ?? 1);
101
+ }
102
+ program
103
+ .command("look")
104
+ .argument("[args...]", "kind 0, one outer, or digest/triage and one card plane")
105
+ .description(LOOK_HELP)
106
+ .action((args) => {
107
+ runGoggles(args);
108
+ });
109
+ program
110
+ .command("goggles")
111
+ .argument("[args...]", "same words as look")
112
+ .description(`Same organ as look. ${LOOK_HELP}`)
113
+ .action((args) => {
114
+ runGoggles(args);
93
115
  });
94
116
  program
95
117
  .command("wear")
@@ -130,7 +152,7 @@ program
130
152
  .description("Show comprehensive xray framework status")
131
153
  .action(async () => {
132
154
  const { statusCommand } = await import("./commands/status.js");
133
- await statusCommand();
155
+ await statusCommand(packageRoot);
134
156
  });
135
157
  program
136
158
  .command("validate")
@@ -267,6 +289,8 @@ program
267
289
  }
268
290
  }
269
291
  console.log("");
292
+ const { probeGoggles, formatGogglesStatus } = await import("./goggles-status.js");
293
+ process.stdout.write(`${formatGogglesStatus(probeGoggles(packageRoot, process.cwd())).join("\n")}\n\n`);
270
294
  if (allHealthy) {
271
295
  console.log("🎉 Framework is healthy and ready to use!");
272
296
  console.log("");
@@ -21,6 +21,7 @@ import { getGovernanceIntegration, } from '../integrations/governance/index.js';
21
21
  import { applyDecisionMatrix, calculateMetamorphosisScore, mergeVotes, } from './governance-core.js';
22
22
  import { frameworkLogger } from '../core/framework-logger.js';
23
23
  import { featuresConfigLoader } from '../core/features-config.js';
24
+ import { lensPlane } from '../integrations/hooks/goggles-pipeline.mjs';
24
25
  export class GovernanceService {
25
26
  constructor() {
26
27
  // We deliberately do *not* cache the integration here.
@@ -187,12 +188,21 @@ export class GovernanceService {
187
188
  finalDecision = 'needs_revision';
188
189
  }
189
190
  }
191
+ const plane = lensPlane(`${proposal.title} ${proposal.description}`);
192
+ const about = [proposal.title, proposal.description, ...(proposal.evidence ?? [])].join('\n');
193
+ let reasoningSummary = merged.reasoningSummary;
194
+ if (plane.files.length > 0 &&
195
+ finalDecision === 'approve' &&
196
+ !plane.files.some((file) => about.includes(file))) {
197
+ finalDecision = 'needs_revision';
198
+ reasoningSummary = `${reasoningSummary} Not about ${plane.files[0]}.`.trim();
199
+ }
190
200
  return {
191
201
  proposalId: proposal.id,
192
202
  finalDecision,
193
203
  averageConfidence,
194
204
  votes,
195
- reasoningSummary: merged.reasoningSummary,
205
+ reasoningSummary,
196
206
  moralOverride,
197
207
  ...(metamorphosisScore != null ? { metamorphosisScore } : {}),
198
208
  };
@@ -24,8 +24,11 @@ export interface GrokInstallOptions {
24
24
  export declare function installForGrokCLI(options?: GrokInstallOptions): Promise<void>;
25
25
  /** Grok TUI reads <project>/.grok/config.toml, not the package copy. */
26
26
  export declare function writeProjectRepertoireMcp(projectRoot: string): string | null;
27
+ /** Grok TUI reads <project>/.grok/config.toml. The launcher fills GOGGLES_ROOT from cwd. */
28
+ export declare function writeProjectGogglesMcp(projectRoot: string): string | null;
27
29
  declare const _default: {
28
30
  installForGrokCLI: typeof installForGrokCLI;
29
31
  writeProjectRepertoireMcp: typeof writeProjectRepertoireMcp;
32
+ writeProjectGogglesMcp: typeof writeProjectGogglesMcp;
30
33
  };
31
34
  export default _default;
@@ -27,7 +27,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
27
  const packageRoot = path.resolve(__dirname, '..', '..', '..');
28
28
  const requireCjs = createRequire(import.meta.url);
29
29
  const { resolveConsumerTargetDir, patchGrokHooks } = requireCjs(path.join(packageRoot, 'scripts/node/install-bridges.cjs'));
30
- const { XRAY_MCP_SERVERS, resolveRepertoireMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
30
+ const { XRAY_MCP_SERVERS, resolveRepertoireMcp, resolveGogglesMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
31
31
  function registerGrokMcpServers(targetDir) {
32
32
  try {
33
33
  execSync('which grok', { stdio: 'ignore' });
@@ -113,6 +113,7 @@ export async function installForGrokCLI(options = {}) {
113
113
  }
114
114
  }
115
115
  writeProjectRepertoireMcp(targetDir);
116
+ writeProjectGogglesMcp(targetDir);
116
117
  mintAfterWear(targetDir);
117
118
  const primary = dests[0];
118
119
  try {
@@ -177,6 +178,7 @@ function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
177
178
  }
178
179
  }
179
180
  const repertoireMcp = resolveRepertoireMcp(targetDir);
181
+ const gogglesMcp = resolveGogglesMcp(targetDir);
180
182
  if (mcp.mcpServers) {
181
183
  if (repertoireMcp) {
182
184
  mcp.mcpServers.repertoire = {
@@ -187,6 +189,16 @@ function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
187
189
  else {
188
190
  delete mcp.mcpServers.repertoire;
189
191
  }
192
+ if (gogglesMcp) {
193
+ mcp.mcpServers.goggles = {
194
+ command: 'node',
195
+ args: [gogglesMcp],
196
+ env: { GOGGLES_ROOT: targetDir, GOGGLES_PLANES_PATH: '' },
197
+ };
198
+ }
199
+ else {
200
+ delete mcp.mcpServers.goggles;
201
+ }
190
202
  }
191
203
  fs.writeFileSync(mcpPath, `${JSON.stringify(mcp, null, 2)}\n`);
192
204
  }
@@ -217,7 +229,35 @@ enabled = true
217
229
  }
218
230
  return tomlPath;
219
231
  }
232
+ /** Grok TUI reads <project>/.grok/config.toml. The launcher fills GOGGLES_ROOT from cwd. */
233
+ export function writeProjectGogglesMcp(projectRoot) {
234
+ const gogglesMcp = resolveGogglesMcp(projectRoot);
235
+ if (!gogglesMcp)
236
+ return null;
237
+ const grokDir = path.join(projectRoot, '.grok');
238
+ fs.mkdirSync(grokDir, { recursive: true });
239
+ const tomlPath = path.join(grokDir, 'config.toml');
240
+ const block = `[mcp_servers.goggles]
241
+ command = "node"
242
+ args = [${JSON.stringify(gogglesMcp)}]
243
+ enabled = true
244
+ `;
245
+ let existing = '';
246
+ if (fs.existsSync(tomlPath)) {
247
+ existing = fs.readFileSync(tomlPath, 'utf8');
248
+ }
249
+ if (/\[mcp_servers\.goggles\]/.test(existing)) {
250
+ existing = existing.replace(/\[mcp_servers\.goggles\][\s\S]*?(?=\n\[|$)/, block.trim());
251
+ fs.writeFileSync(tomlPath, existing.endsWith('\n') ? existing : `${existing}\n`);
252
+ }
253
+ else {
254
+ const prefix = existing.trim() ? `${existing.trim()}\n\n` : '';
255
+ fs.writeFileSync(tomlPath, `${prefix}${block}`);
256
+ }
257
+ return tomlPath;
258
+ }
220
259
  export default {
221
260
  installForGrokCLI,
222
261
  writeProjectRepertoireMcp,
262
+ writeProjectGogglesMcp,
223
263
  };
@@ -330,10 +330,13 @@ export function writeSessionBoot(root, payload) {
330
330
  fs.mkdirSync(stateDir, { recursive: true });
331
331
  fs.writeFileSync(sessionBootPath(root), JSON.stringify(payload, null, 2));
332
332
  writeStationMarkdown(root, payload);
333
- try {
334
- handCard(root, payload && (payload.cardText || payload.intent));
335
- } catch {
336
- /* a missed card must not fail the boot */
333
+ const hook = String((payload && (payload.hookEvent || payload.source)) || "");
334
+ if (!/compact/i.test(hook)) {
335
+ try {
336
+ handCard(root, payload && (payload.cardText || payload.intent));
337
+ } catch {
338
+ /* a missed card must not fail the boot */
339
+ }
337
340
  }
338
341
  return sessionBootPath(root);
339
342
  } catch {
@@ -21,7 +21,7 @@ import {
21
21
  workspaceRoot,
22
22
  } from './grok-hook-utils.js';
23
23
  import { appendHookActivity } from './grok-hook-activity.js';
24
- import { cardStop } from '../../hooks/goggles-pipeline.mjs';
24
+ import { cardStop, lensBeforeResearch } from '../../hooks/goggles-pipeline.mjs';
25
25
  import { createRequire } from 'node:module';
26
26
  import { existsSync } from 'node:fs';
27
27
  import { dirname, join } from 'node:path';
@@ -154,6 +154,12 @@ async function main() {
154
154
  });
155
155
  }
156
156
 
157
+ const spoken = [cmd, content, toolInput.pattern, toolInput.query, ...(ctx.paths || [])].filter(Boolean).join('\n');
158
+ const lensStop = lensBeforeResearch(eventRoot, toolName, spoken);
159
+ if (lensStop?.decision === 'deny') {
160
+ finish(eventRoot, 'deny', lensStop.reason, null, toolName, { gate: lensStop.gate });
161
+ }
162
+
157
163
  const route = repertoireRoute([toolName, cmd, content].filter(Boolean).join(' '));
158
164
 
159
165
  if (isShellTool(toolName) && cmd) {
@@ -147,11 +147,14 @@ async function main() {
147
147
  : '0xray/grok-session-start';
148
148
 
149
149
  const intent = extractIntent(event);
150
- const matchedSignals = await matchStationSignals(eventRoot, intent);
150
+ const matchedSignals =
151
+ HOOK_EVENT === 'user_prompt_submit' ? [] : await matchStationSignals(eventRoot, intent);
152
+ const compact = COMPACT_EVENTS.has(HOOK_EVENT);
151
153
  const payload = buildSessionBootPayload(eventRoot, source, {
152
154
  hookEvent: HOOK_EVENT,
153
155
  sessionId: event.sessionId || process.env.GROK_SESSION_ID || null,
154
- ...(intent ? { intent, cardText: intent } : {}),
156
+ ...(intent ? { intent } : {}),
157
+ ...(!compact && intent ? { cardText: intent } : {}),
155
158
  ...(matchedSignals.length ? { matchedSignals } : {}),
156
159
  });
157
160