0xray 3.3.1 → 3.4.2

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 (70) hide show
  1. package/.opencode/enforcer-config.json +1 -4
  2. package/.xray/codex.json +598 -0
  3. package/.xray/config.json +25 -0
  4. package/.xray/features.json +146 -0
  5. package/.xray/features.schema.json +33 -0
  6. package/AGENTS-consumer.md +127 -10
  7. package/AGENTS.md +172 -56
  8. package/README.md +159 -82
  9. package/SKILLS.md +79 -53
  10. package/dist/AGENTS.md +172 -56
  11. package/dist/CHANGELOG.md +90 -51
  12. package/dist/README.md +159 -82
  13. package/dist/SKILLS.md +79 -53
  14. package/dist/analytics/routing-refiner.js +0 -2
  15. package/dist/core/boot-orchestrator.js +9 -1
  16. package/dist/core/features-config.js +5 -1
  17. package/dist/integrations/grok/grok-cli.js +47 -25
  18. package/dist/integrations/grok/hooks/grok-hook-activity.js +53 -0
  19. package/dist/integrations/grok/hooks/grok-hook-utils.js +261 -0
  20. package/dist/integrations/grok/hooks/pre-tool-use.js +85 -115
  21. package/dist/integrations/grok/hooks/session-start.js +52 -0
  22. package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +20 -3
  23. package/dist/integrations/hermes-agent/bridge.mjs +11 -1
  24. package/dist/mcps/config/server-config-registry.js +4 -3
  25. package/dist/mcps/orchestrator/handlers/complexity-handler.js +33 -3
  26. package/dist/mcps/orchestrator/server.js +1 -1
  27. package/dist/mcps/registry.json +0 -1
  28. package/dist/mcps/researcher.server.js +3 -0
  29. package/dist/nucleus/autonomy-kernel.js +176 -0
  30. package/dist/nucleus/index.js +1 -0
  31. package/dist/orchestrator/universal-registry-bridge.js +1 -1
  32. package/dist/skills/boot-orchestrator/SKILL.md +1 -5
  33. package/dist/skills/orchestrator/SKILL.md +35 -12
  34. package/dist/skills/registry.json +0 -1
  35. package/dist/skills/researcher/SKILL.md +17 -4
  36. package/dist/skills/strategist/SKILL.md +12 -0
  37. package/dist/skills/testing-strategy/SKILL.md +13 -2
  38. package/package.json +23 -8
  39. package/scripts/node/consumer-install-smoke.mjs +163 -0
  40. package/scripts/node/enforce-agents-md.mjs +0 -26
  41. package/scripts/node/enforce-version-compliance.sh +0 -1
  42. package/scripts/node/enforce-version-compliance.ts +0 -10
  43. package/scripts/node/install-bridges.cjs +154 -39
  44. package/scripts/node/pre-publish-guard.js +108 -165
  45. package/scripts/node/reconcile-version.mjs +147 -0
  46. package/scripts/node/release-gate.mjs +66 -0
  47. package/scripts/node/release.js +5 -147
  48. package/scripts/node/release.mjs +106 -181
  49. package/scripts/node/setup.cjs +21 -26
  50. package/scripts/node/sync-versions.mjs +12 -134
  51. package/scripts/node/universal-version-manager.js +22 -4
  52. package/scripts/node/validate-release-docs.mjs +299 -0
  53. package/scripts/node/version-manager.mjs +225 -49
  54. package/src/integrations/grok/plugin/0xray/hooks/hooks.json +20 -3
  55. package/src/mcps/config/__tests__/server-config-registry.test.ts +9 -0
  56. package/src/mcps/config/server-config-registry.ts +5 -3
  57. package/src/mcps/orchestrator/handlers/complexity-handler.ts +54 -3
  58. package/src/mcps/orchestrator/server.ts +1 -1
  59. package/src/mcps/registry.json +0 -1
  60. package/src/mcps/researcher.server.ts +4 -0
  61. package/src/opencode/enforcer-config.json +1 -4
  62. package/src/skills/boot-orchestrator/SKILL.md +1 -5
  63. package/src/skills/orchestrator/SKILL.md +35 -12
  64. package/src/skills/registry.json +0 -1
  65. package/src/skills/researcher/SKILL.md +17 -4
  66. package/src/skills/strategist/SKILL.md +12 -0
  67. package/src/skills/testing-strategy/SKILL.md +13 -2
  68. package/xray/config.json +25 -0
  69. package/xray/features.json +146 -0
  70. package/xray/features.schema.json +33 -0
@@ -18,9 +18,44 @@ import { frameworkLogger } from '../../core/framework-logger.js';
18
18
  import fs from 'fs';
19
19
  import path from 'path';
20
20
  import { execSync } from 'child_process';
21
+ import { createRequire } from 'module';
21
22
  import { syncBuiltinSkills } from '../../cli/commands/skill-install.js';
22
23
  // ESM-compatible __dirname (this file is compiled to ESM)
23
24
  const __dirname = path.dirname(new URL(import.meta.url).pathname);
25
+ const packageRoot = path.resolve(__dirname, '..', '..', '..');
26
+ const requireCjs = createRequire(import.meta.url);
27
+ const { resolveConsumerTargetDir } = requireCjs(path.join(packageRoot, 'scripts/node/install-bridges.cjs'));
28
+ /** Canonical 7-server MCP surface — matches install-bridges.cjs and package .mcp.json */
29
+ const XRAY_MCP_SERVERS = [
30
+ { name: 'xray-governance', mcpCmd: 'governance', env: { XRAY_FORCE_MCP_GOVERNANCE: 'true' } },
31
+ { name: 'xray-skills', mcpCmd: 'skills', env: {} },
32
+ { name: 'xray-orchestrator', mcpCmd: 'orchestrator', env: {} },
33
+ { name: 'xray-enforcer', mcpCmd: 'enforcer', env: {} },
34
+ { name: 'xray-researcher', mcpCmd: 'researcher', env: {} },
35
+ { name: 'xray-code-review', mcpCmd: 'code-review', env: {} },
36
+ { name: 'xray-architect-tools', mcpCmd: 'architect-tools', env: {} },
37
+ ];
38
+ function registerGrokMcpServers(targetDir) {
39
+ try {
40
+ execSync('which grok', { stdio: 'ignore' });
41
+ }
42
+ catch {
43
+ console.log('[Grok] grok CLI not on PATH — plugin .mcp.json still configured');
44
+ return;
45
+ }
46
+ for (const s of XRAY_MCP_SERVERS) {
47
+ try {
48
+ const envEntries = { ...s.env, XRAY_ROOT: targetDir };
49
+ const envFlags = Object.entries(envEntries)
50
+ .map(([k, v]) => `--env "${k}=${v}"`)
51
+ .join(' ');
52
+ execSync(`grok mcp add ${s.name} --command npx --args "-y" "0xray" "mcp" "${s.mcpCmd}" ${envFlags}`, { stdio: 'pipe' });
53
+ }
54
+ catch {
55
+ // already registered or grok config conflict — non-blocking
56
+ }
57
+ }
58
+ }
24
59
  export async function installForGrokCLI(options = {}) {
25
60
  frameworkLogger.log('grok-integration', 'install-start', 'info', { options });
26
61
  const home = process.env.HOME || process.env.USERPROFILE || '';
@@ -39,15 +74,18 @@ export async function installForGrokCLI(options = {}) {
39
74
  console.log(`[Grok] Dry run: Would copy plugin from ${sourceDir} → ${targetPluginDir}`);
40
75
  return;
41
76
  }
77
+ const targetDir = resolveConsumerTargetDir(packageRoot, process.cwd());
42
78
  try {
43
- if (fs.existsSync(targetPluginDir) && !options.force) {
79
+ const pluginExists = fs.existsSync(targetPluginDir);
80
+ if (pluginExists && !options.force) {
44
81
  console.log('[Grok] 0xray Grok plugin is already installed.');
45
- console.log('Use --force to reinstall.');
46
- return;
82
+ console.log('Use --force to reinstall plugin files.');
83
+ }
84
+ else {
85
+ fs.cpSync(sourceDir, targetPluginDir, { recursive: true, force: true });
86
+ frameworkLogger.log('grok-integration', 'plugin-copied', 'info', { destination: targetPluginDir });
87
+ console.log(`\x1b[32m✓ Copied Grok plugin to ${targetPluginDir}\x1b[0m`);
47
88
  }
48
- fs.cpSync(sourceDir, targetPluginDir, { recursive: true, force: true });
49
- frameworkLogger.log('grok-integration', 'plugin-copied', 'info', { destination: targetPluginDir });
50
- console.log(`\x1b[32m✓ Copied Grok plugin to ${targetPluginDir}\x1b[0m`);
51
89
  // Sync builtin skills to Grok plugin skills dir
52
90
  const grokSkillsDir = path.join(targetPluginDir, 'skills');
53
91
  const skillsCopied = syncBuiltinSkills(grokSkillsDir);
@@ -70,25 +108,9 @@ export async function installForGrokCLI(options = {}) {
70
108
  console.log('\nPlease run this command to fully trust the plugin:');
71
109
  console.log(` grok plugins trust "${targetPluginDir}"`);
72
110
  }
73
- // Register MCP servers via grok mcp add (most reliable mechanism)
74
- const govMcpPath = path.resolve(__dirname, '..', '..', '..', 'dist/mcps/governance.server.js');
75
- const skillsMcpPath = path.resolve(__dirname, '..', '..', '..', 'dist/mcps/knowledge-skills/skill-invocation.server.js');
76
- const orchMcpPath = path.resolve(__dirname, '..', '..', '..', 'dist/mcps/orchestrator.server.js');
77
- const enforcerMcpPath = path.resolve(__dirname, '..', '..', '..', 'dist/mcps/enforcer-tools.server.js');
78
- try {
79
- execSync(`grok mcp add xray-governance --command node --args "${govMcpPath}" --env "XRAY_FORCE_MCP_GOVERNANCE=true" --env "XRAY_ROOT=${process.cwd()}"`, { stdio: 'pipe' });
80
- execSync(`grok mcp add xray-skills --command node --args "${skillsMcpPath}" --env "XRAY_ROOT=${process.cwd()}"`, { stdio: 'pipe' });
81
- execSync(`grok mcp add xray-orchestrator --command node --args "${orchMcpPath}" --env "XRAY_ROOT=${process.cwd()}"`, { stdio: 'pipe' });
82
- execSync(`grok mcp add xray-enforcer --command node --args "${enforcerMcpPath}" --env "XRAY_ROOT=${process.cwd()}"`, { stdio: 'pipe' });
83
- console.log('\x1b[32m✓ Registered xray MCP servers with Grok CLI\x1b[0m');
84
- }
85
- catch {
86
- console.log('\nCould not auto-register MCP servers. Run manually:');
87
- console.log(` grok mcp add xray-governance --command node --args "${govMcpPath}"`);
88
- console.log(` grok mcp add xray-skills --command node --args "${skillsMcpPath}"`);
89
- console.log(` grok mcp add xray-orchestrator --command node --args "${orchMcpPath}"`);
90
- console.log(` grok mcp add xray-enforcer --command node --args "${enforcerMcpPath}"`);
91
- }
111
+ // Register MCP servers via grok mcp add (npx -y 0xray mcp — matches install-bridges)
112
+ registerGrokMcpServers(targetDir);
113
+ console.log('\x1b[32m✓ Registered 7 xray MCP servers with Grok CLI (npx)\x1b[0m');
92
114
  console.log('\n✅ 0xRay is now installed as a first-class Grok CLI plugin!');
93
115
  console.log('Restart Grok or run `grok` to load the new hooks and MCP servers.');
94
116
  }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sync activity.log writer for Grok hooks (hooks exit before frameworkLogger flush).
4
+ * Format matches frameworkLogger.bufferEntry output.
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+
10
+ function formatLine(component, action, status, details, jobId) {
11
+ const ts = new Date().toISOString();
12
+ const jobPart = jobId ? `[${jobId}] ` : '';
13
+ const detailsPart = details
14
+ ? ` | ${(() => {
15
+ try {
16
+ return JSON.stringify(details);
17
+ } catch {
18
+ return String(details);
19
+ }
20
+ })()}`
21
+ : '';
22
+ return `${ts} ${jobPart}[${component}] ${action} - ${status.toUpperCase()}${detailsPart}\n`;
23
+ }
24
+
25
+ export function appendHookActivity(root, component, action, status, details = {}) {
26
+ const workspace = root || process.cwd();
27
+ const logDir = path.join(workspace, 'logs', 'framework');
28
+ const logFile = path.join(logDir, 'activity.log');
29
+ try {
30
+ fs.mkdirSync(logDir, { recursive: true });
31
+ const jobId = `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
32
+ fs.appendFileSync(logFile, formatLine(component, action, status, details, jobId));
33
+ return jobId;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ export function activityLogHasPattern(root, pattern, tailBytes = 65536) {
40
+ const logFile = path.join(root, 'logs', 'framework', 'activity.log');
41
+ if (!fs.existsSync(logFile)) return false;
42
+ try {
43
+ const stat = fs.statSync(logFile);
44
+ const start = Math.max(0, stat.size - tailBytes);
45
+ const fd = fs.openSync(logFile, 'r');
46
+ const buf = Buffer.alloc(stat.size - start);
47
+ fs.readSync(fd, buf, 0, buf.length, start);
48
+ fs.closeSync(fd);
49
+ return pattern.test(buf.toString('utf8'));
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Shared utilities for Grok CLI hooks — codex load, features, surface checks.
4
+ * Pure JS (no TS build) so hooks run from dist/ without compilation surprises.
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+
10
+ export const LEAD_DEV_RULES = [
11
+ 'Phased plan + todos; assign best subagent; monitor output',
12
+ 'Lead dev loops test→fix until green; no permission pings',
13
+ 'Per-suite test triage; full suite last',
14
+ 'Lead stays main; subagents execute; update todos',
15
+ 'Read all output; triage fix rerun',
16
+ 'No buck-passing — resolve every error',
17
+ 'Zero open errors at phase completion',
18
+ ];
19
+
20
+ /** Blocking codex patterns detectable in proposed edits (term → regex + message) */
21
+ export const CODEX_BLOCK_PATTERNS = [
22
+ { terms: [11], pattern: /:\s*any\b|as\s+any\b/g, message: 'Codex 11 Type Safety: no `any` types' },
23
+ { terms: [11], pattern: /@ts-ignore|@ts-expect-error/g, message: 'Codex 11 Type Safety: no @ts-ignore / @ts-expect-error' },
24
+ { terms: [29], pattern: /\beval\s*\(/g, message: 'Codex 29 Security: eval() prohibited' },
25
+ { terms: [2], pattern: /\bTODO\b|\bFIXME\b|\bSTUB\b/g, message: 'Codex 2: no TODO/FIXME/STUB in production code' },
26
+ { terms: [7], pattern: /console\.log\s*\(/g, message: 'Codex 7: no console.log debugging' },
27
+ ];
28
+
29
+ /** New-surface paths blocked when no_new_surface is on (codex 69) */
30
+ export const SURFACE_DENY_PATTERNS = [
31
+ { pattern: /(?:^|\/)src\/mcps\/[^/]+\.server\.(ts|js)$/i, message: 'Codex 69: new MCP server — rewire existing mcps/' },
32
+ { pattern: /(?:^|\/)src\/skills\/[^/]+\/SKILL\.md$/i, message: 'Codex 69: new skill — extend orchestrator skill or existing mcps' },
33
+ { pattern: /(?:^|\/)src\/mcps\/orchestrator\/handlers\/[^/]+-handler\.(ts|js)$/i, message: 'Codex 69: new orchestrator handler — merge into complexity-handler' },
34
+ { pattern: /(?:^|\/)src\/nucleus\/autonomy-kernel\.(ts|js)$/i, message: 'Codex 69: parallel autonomy kernel — use existing nucleus' },
35
+ { pattern: /(?:^|\/)src\/mcps\/[^/]+\/handlers\/autonomy-handler\.(ts|js)$/i, message: 'Codex 69: autonomy-handler removed — use complexity-handler' },
36
+ ];
37
+
38
+ const SUBAGENT_TOOLS = new Set([
39
+ 'spawn_subagent',
40
+ 'task',
41
+ 'Task',
42
+ ]);
43
+
44
+ const WRITE_TOOLS = new Set([
45
+ 'search_replace',
46
+ 'write',
47
+ 'Write',
48
+ 'Edit',
49
+ 'MultiEdit',
50
+ 'edit_notebook',
51
+ 'EditNotebook',
52
+ ]);
53
+
54
+ export function workspaceRoot() {
55
+ return (
56
+ process.env.GROK_WORKSPACE_ROOT ||
57
+ process.env.CLAUDE_PROJECT_DIR ||
58
+ process.env.XRAY_ROOT ||
59
+ process.cwd()
60
+ );
61
+ }
62
+
63
+ export function resolveFeaturesPath(root = workspaceRoot()) {
64
+ for (const p of [
65
+ path.join(root, '.xray', 'features.json'),
66
+ path.join(process.cwd(), '.xray', 'features.json'),
67
+ ]) {
68
+ if (fs.existsSync(p)) return p;
69
+ }
70
+ return null;
71
+ }
72
+
73
+ export function resolveCodexPath(root = workspaceRoot()) {
74
+ for (const p of [
75
+ path.join(root, '.xray', 'codex.json'),
76
+ path.join(process.cwd(), '.xray', 'codex.json'),
77
+ ]) {
78
+ if (fs.existsSync(p)) return p;
79
+ }
80
+ return null;
81
+ }
82
+
83
+ export function loadFeatures() {
84
+ const featuresPath = resolveFeaturesPath();
85
+ if (!featuresPath) return { lead_dev_mode: true, no_new_surface: true, per_suite_triage: true };
86
+ try {
87
+ const data = JSON.parse(fs.readFileSync(featuresPath, 'utf8'));
88
+ const orch = data.multi_agent_orchestration ?? {};
89
+ return {
90
+ lead_dev_mode: orch.enabled !== false && orch.lead_dev_mode !== false,
91
+ no_new_surface: orch.no_new_surface !== false,
92
+ per_suite_test_triage: orch.per_suite_test_triage !== false,
93
+ };
94
+ } catch {
95
+ return { lead_dev_mode: true, no_new_surface: true, per_suite_triage: true };
96
+ }
97
+ }
98
+
99
+ export function loadBlockingCodexTerms() {
100
+ const codexPath = resolveCodexPath();
101
+ if (!codexPath) return [];
102
+ try {
103
+ const data = JSON.parse(fs.readFileSync(codexPath, 'utf8'));
104
+ const terms = data.terms ?? {};
105
+ return Object.values(terms).filter(
106
+ (t) => t.enforcementLevel === 'blocking' || t.zeroTolerance === true,
107
+ );
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+
113
+ export function leadDevPlanPath(root = workspaceRoot()) {
114
+ return path.join(root, '.xray', 'state', 'lead-dev-plan.json');
115
+ }
116
+
117
+ export function hasFreshLeadDevPlan(maxAgeMs = 4 * 60 * 60 * 1000) {
118
+ const planPath = leadDevPlanPath();
119
+ if (!fs.existsSync(planPath)) return false;
120
+ try {
121
+ const stat = fs.statSync(planPath);
122
+ if (Date.now() - stat.mtimeMs > maxAgeMs) return false;
123
+ const plan = JSON.parse(fs.readFileSync(planPath, 'utf8'));
124
+ return plan.active === true;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ export function extractToolContext(event) {
131
+ const toolName = event.toolName || process.env.TOOL_NAME || 'unknown';
132
+ const toolInput = event.toolInput ?? {};
133
+ const paths = [];
134
+ let content = '';
135
+
136
+ if (toolInput.path) paths.push(String(toolInput.path));
137
+ if (toolInput.file_path) paths.push(String(toolInput.file_path));
138
+ if (toolInput.target_notebook) paths.push(String(toolInput.target_notebook));
139
+ if (Array.isArray(toolInput.paths)) paths.push(...toolInput.paths.map(String));
140
+
141
+ if (toolInput.new_string) content += String(toolInput.new_string);
142
+ if (toolInput.contents) content += String(toolInput.contents);
143
+ if (toolInput.command) content += String(toolInput.command);
144
+ if (toolInput.prompt) content += String(toolInput.prompt);
145
+
146
+ return { toolName, toolInput, paths, content, cmd: String(toolInput.command || '') };
147
+ }
148
+
149
+ export function checkCodexPatterns(content) {
150
+ if (!content) return null;
151
+ for (const { message, pattern } of CODEX_BLOCK_PATTERNS) {
152
+ if (pattern.test(content)) return message;
153
+ pattern.lastIndex = 0;
154
+ }
155
+ return null;
156
+ }
157
+
158
+ export function checkSurfaceArea(paths, root = workspaceRoot()) {
159
+ for (const rel of paths) {
160
+ const normalized = rel.replace(/\\/g, '/');
161
+ const abs = path.isAbsolute(rel) ? rel : path.join(root, rel);
162
+ if (fs.existsSync(abs)) continue;
163
+ for (const { pattern, message } of SURFACE_DENY_PATTERNS) {
164
+ if (pattern.test(normalized)) return `${message} (blocked: ${normalized})`;
165
+ }
166
+ }
167
+ return null;
168
+ }
169
+
170
+ export function checkSubagentGate(toolName, features) {
171
+ if (!features.lead_dev_mode) return null;
172
+ if (!SUBAGENT_TOOLS.has(toolName)) return null;
173
+ if (hasFreshLeadDevPlan()) return null;
174
+ return (
175
+ 'Codex 59/67: call xray-orchestrator analyze-complexity first — ' +
176
+ 'writes .xray/state/lead-dev-plan.json required before spawn_subagent'
177
+ );
178
+ }
179
+
180
+ export function checkFullTestSuite(cmd, features) {
181
+ if (!features.per_suite_test_triage) return null;
182
+ const cmdText = cmd.toLowerCase();
183
+ const isFullTestSuite =
184
+ /\b(npm\s+(run\s+)?test|vitest\s+run|pnpm\s+test|yarn\s+test)\b/.test(cmdText) &&
185
+ !/--\s+\S+\.test/.test(cmdText) &&
186
+ !/vitest\s+run\s+\S+\.test/.test(cmdText);
187
+ if (!isFullTestSuite) return null;
188
+ return (
189
+ 'Lead-dev test protocol: run individual test files first; ' +
190
+ 'full suite only after per-suite passes (override with focused test path)'
191
+ );
192
+ }
193
+
194
+ export function isWriteTool(toolName) {
195
+ return WRITE_TOOLS.has(toolName) || /write|edit|replace/i.test(toolName);
196
+ }
197
+
198
+ export function isShellTool(toolName) {
199
+ return /terminal|bash|shell|run_terminal/i.test(toolName);
200
+ }
201
+
202
+ export async function readStdinJson() {
203
+ const chunks = [];
204
+ for await (const chunk of process.stdin) chunks.push(chunk);
205
+ const raw = Buffer.concat(chunks).toString('utf8').trim();
206
+ if (!raw) return {};
207
+ try {
208
+ return JSON.parse(raw);
209
+ } catch {
210
+ return {};
211
+ }
212
+ }
213
+
214
+ export function sessionBootPath(root = workspaceRoot()) {
215
+ return path.join(root, '.xray', 'state', 'session-boot.json');
216
+ }
217
+
218
+ export function buildSessionBootPayload(root, source = '0xray/grok-session-start', extra = {}) {
219
+ const features = loadFeatures();
220
+ const blockingTerms = loadBlockingCodexTerms();
221
+ return {
222
+ hook: source,
223
+ lead_dev_mode: features.lead_dev_mode,
224
+ no_new_surface: features.no_new_surface,
225
+ codexBlockingTermCount: blockingTerms.length,
226
+ codexTerms: [59, 67, 68, 69],
227
+ rules: features.lead_dev_mode ? LEAD_DEV_RULES : [],
228
+ mcpIntake: 'xray-orchestrator → analyze-complexity (required before spawn_subagent)',
229
+ enforcement: 'PreToolUse hook — codex patterns + surface area + spawn gate',
230
+ workspaceRoot: root,
231
+ sessionId: process.env.GROK_SESSION_ID || null,
232
+ timestamp: new Date().toISOString(),
233
+ source,
234
+ ...extra,
235
+ };
236
+ }
237
+
238
+ export function writeSessionBoot(root, payload) {
239
+ try {
240
+ const stateDir = path.join(root, '.xray', 'state');
241
+ fs.mkdirSync(stateDir, { recursive: true });
242
+ fs.writeFileSync(sessionBootPath(root), JSON.stringify(payload, null, 2));
243
+ return sessionBootPath(root);
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+
249
+ /** Boot on SessionStart, UserPromptSubmit, or first PreToolUse if missing. */
250
+ export function ensureSessionBoot(root = workspaceRoot(), source = '0xray/grok-boot') {
251
+ const bootPath = sessionBootPath(root);
252
+ if (fs.existsSync(bootPath)) {
253
+ try {
254
+ const existing = JSON.parse(fs.readFileSync(bootPath, 'utf8'));
255
+ if (existing.lead_dev_mode !== undefined) return bootPath;
256
+ } catch {
257
+ /* rewrite corrupt boot */
258
+ }
259
+ }
260
+ return writeSessionBoot(root, buildSessionBootPayload(root, source));
261
+ }
@@ -1,134 +1,104 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Grok CLI PreToolUse Hook Handler for 0xRay — First Class Citizen
4
- *
5
- * Real governance enforcement hook.
6
- * When Grok is about to execute a tool (write_file, edit, terminal cmd, etc.),
7
- * this script is spawned and runs the Dynamo Solar SSOT decision matrix.
3
+ * Grok CLI PreToolUse — ironclad OS gate
4
+ * Contract: stdin JSON → stdout {"decision":"allow"} | {"decision":"deny","reason":"..."}
8
5
  */
9
6
 
10
- import fs from 'fs';
11
- import path from 'path';
7
+ import {
8
+ checkCodexPatterns,
9
+ checkFullTestSuite,
10
+ checkSubagentGate,
11
+ checkSurfaceArea,
12
+ ensureSessionBoot,
13
+ isShellTool,
14
+ isWriteTool,
15
+ loadFeatures,
16
+ readStdinJson,
17
+ workspaceRoot,
18
+ } from './grok-hook-utils.js';
19
+ import { appendHookActivity } from './grok-hook-activity.js';
12
20
 
13
- const log = (msg) => { try { console.error(`[0xRay:GrokHook] ${msg}`); } catch { /* noop */ } };
21
+ function finish(root, decision, reason, hint, toolName) {
22
+ const out = { decision };
23
+ if (reason) out.reason = reason;
24
+ if (hint) out.hint = hint;
25
+ console.log(JSON.stringify(out));
26
+ appendHookActivity(root, 'grok-pre-tool-use', decision, decision === 'deny' ? 'error' : 'info', {
27
+ tool: toolName,
28
+ reason: reason || hint || null,
29
+ livePath: true,
30
+ });
31
+ process.exit(0);
32
+ }
14
33
 
15
- function findGovernanceCore() {
16
- const here = path.dirname(new URL(import.meta.url).pathname);
34
+ async function main() {
35
+ const root = workspaceRoot();
36
+ let toolName = 'unknown';
17
37
 
18
- // Priority: explicit dev root
19
- const devRoot = process.env.XRAY_ROOT;
20
- if (devRoot) {
21
- const devCandidate = path.resolve(devRoot, 'dist/governance/governance-core.js');
22
- if (fs.existsSync(devCandidate)) return devCandidate;
23
- }
38
+ try {
39
+ const event = await readStdinJson();
40
+ const eventRoot = event.workspaceRoot || event.cwd || root;
41
+ ensureSessionBoot(eventRoot, '0xray/grok-pre-tool-use-boot');
24
42
 
25
- const candidates = [
26
- // Published package layout
27
- path.resolve(here, '../../../governance/governance-core.js'),
28
- path.resolve(here, '../../../../governance/governance-core.js'),
29
- // node_modules installed layout
30
- path.resolve(process.cwd(), 'node_modules/0xray/dist/governance/governance-core.js'),
31
- // Dev layout fallbacks (no extra dist/)
32
- path.resolve(here, '../../../../../governance/governance-core.js'),
33
- path.resolve(process.cwd(), 'dist/governance/governance-core.js'),
34
- // Strong dev fallback: walk up looking for package.json
35
- ...walkUpForCore(here),
36
- ];
37
-
38
- for (const c of candidates) {
39
- if (fs.existsSync(c)) return c;
40
- }
41
- return null;
42
- }
43
+ const features = loadFeatures();
44
+ const ctx = extractFromEvent(event);
45
+ toolName = ctx.toolName;
46
+ const { paths, content, cmd } = ctx;
43
47
 
44
- function walkUpForCore(startDir, maxLevels = 8) {
45
- const results = [];
46
- let current = startDir;
47
- for (let i = 0; i < maxLevels; i++) {
48
- const pkgPath = path.resolve(current, 'package.json');
49
- if (fs.existsSync(pkgPath)) {
50
- try {
51
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
52
- if (pkg.name === '0xray') {
53
- results.push(path.resolve(current, 'dist/governance/governance-core.js'));
54
- results.push(path.resolve(current, 'governance/governance-core.js'));
55
- }
56
- } catch {}
48
+ const subagentBlock = checkSubagentGate(toolName, features);
49
+ if (subagentBlock) finish(eventRoot, 'deny', subagentBlock, null, toolName);
50
+
51
+ if (features.no_new_surface && isWriteTool(toolName) && paths.length) {
52
+ const surfaceBlock = checkSurfaceArea(paths, eventRoot);
53
+ if (surfaceBlock) finish(eventRoot, 'deny', surfaceBlock, null, toolName);
57
54
  }
58
- const parent = path.dirname(current);
59
- if (parent === current) break;
60
- current = parent;
61
- }
62
- return results;
63
- }
64
55
 
65
- function deriveResonance(toolName, extra = '') {
66
- const text = (toolName + ' ' + extra).toLowerCase();
67
- let score = 0.85; // baseline good
68
- const penalties = [
69
- ['any', -0.25],
70
- ['eval(', -0.35],
71
- ['console.log', -0.15],
72
- ['// @ts-ignore', -0.2],
73
- ['require(', -0.1],
74
- ];
75
- for (const [pat, pen] of penalties) {
76
- if (text.includes(pat)) score += pen;
77
- }
78
- return Math.max(0.1, Math.min(0.99, score));
79
- }
56
+ if (isWriteTool(toolName) && content) {
57
+ const codexBlock = checkCodexPatterns(content);
58
+ if (codexBlock) finish(eventRoot, 'deny', codexBlock, null, toolName);
59
+ }
80
60
 
81
- async function main() {
82
- try {
83
- const toolName = process.env.TOOL_NAME || process.env.HOOK_TOOL || 'unknown_tool';
84
- const cwd = process.env.PWD || process.cwd();
85
- const extraContext = process.env.HOOK_ARGS || process.env.TOOL_ARGS || '';
86
-
87
- log(`PreToolUse: ${toolName}`);
88
-
89
- const corePath = findGovernanceCore();
90
- let recommendation = 'ALLOW';
91
- let resonance = deriveResonance(toolName, extraContext);
92
- let govDetails = null;
93
-
94
- if (corePath) {
95
- try {
96
- const core = await import(`file://${corePath}`);
97
- if (typeof core.applyDecisionMatrix === 'function') {
98
- const result = core.applyDecisionMatrix({
99
- resonance,
100
- isotopicRatio: resonance > 0.8 ? 0.96 : 0.7,
101
- vortexVolume: 1_000_000,
102
- historicalCoherence: 0.82,
103
- solarActivity: 'active',
104
- });
105
- govDetails = result;
106
- recommendation = result.recommendation || 'NEEDS_REVISION';
107
- log(`Solar decision: ${recommendation} (resonance=${resonance.toFixed(2)})`);
108
- }
109
- } catch (e) {
110
- log(`Governance core error: ${e.message}`);
111
- }
112
- } else {
113
- log('Governance core not located — using safe default');
61
+ if (isShellTool(toolName) && cmd) {
62
+ const destructive =
63
+ /\brm\s+-rf\s+\/\b|\bmkfs\b|\bdd\s+if=|:()\s*\{\s*:\|&\s*\}\s*;:/i.test(cmd);
64
+ if (destructive) finish(eventRoot, 'deny', 'Blocked destructive shell command', null, toolName);
65
+
66
+ const testHint = checkFullTestSuite(cmd, features);
67
+ if (testHint) finish(eventRoot, 'allow', null, testHint, toolName);
114
68
  }
115
69
 
116
- const decision = {
117
- tool: toolName,
118
- decision: recommendation === 'PASS' ? 'allow' : 'allow_with_governance_note',
119
- resonance: Number(resonance.toFixed(3)),
120
- solar_recommendation: recommendation,
121
- gov: govDetails,
122
- timestamp: new Date().toISOString(),
123
- source: '0xray/grok-pre-tool-use',
124
- };
125
-
126
- console.log(JSON.stringify(decision));
127
- process.exit(0); // non-blocking rollout; future versions can exit(1) on strong reject
70
+ finish(eventRoot, 'allow', null, null, toolName);
128
71
  } catch (err) {
129
- log(`Hook failure (non-fatal): ${err.message}`);
130
- process.exit(0);
72
+ appendHookActivity(root, 'grok-pre-tool-use', 'hook-error', 'error', {
73
+ tool: toolName,
74
+ error: err.message,
75
+ });
76
+ finish(root, 'allow', null, null, toolName);
131
77
  }
132
78
  }
133
79
 
80
+ function extractFromEvent(event) {
81
+ const toolName = event.toolName || process.env.TOOL_NAME || 'unknown';
82
+ const toolInput = event.toolInput ?? {};
83
+ const paths = [];
84
+ let content = '';
85
+
86
+ if (toolInput.path) paths.push(String(toolInput.path));
87
+ if (toolInput.file_path) paths.push(String(toolInput.file_path));
88
+ if (toolInput.target_notebook) paths.push(String(toolInput.target_notebook));
89
+ if (Array.isArray(toolInput.paths)) paths.push(...toolInput.paths.map(String));
90
+
91
+ if (toolInput.new_string) content += String(toolInput.new_string);
92
+ if (toolInput.contents) content += String(toolInput.contents);
93
+ if (toolInput.command) content += String(toolInput.command);
94
+ if (toolInput.prompt) content += String(toolInput.prompt);
95
+
96
+ return {
97
+ toolName,
98
+ paths,
99
+ content,
100
+ cmd: String(toolInput.command || ''),
101
+ };
102
+ }
103
+
134
104
  main();