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.
- package/.opencode/enforcer-config.json +1 -4
- package/.xray/codex.json +598 -0
- package/.xray/config.json +25 -0
- package/.xray/features.json +146 -0
- package/.xray/features.schema.json +33 -0
- package/AGENTS-consumer.md +127 -10
- package/AGENTS.md +172 -56
- package/README.md +159 -82
- package/SKILLS.md +79 -53
- package/dist/AGENTS.md +172 -56
- package/dist/CHANGELOG.md +90 -51
- package/dist/README.md +159 -82
- package/dist/SKILLS.md +79 -53
- package/dist/analytics/routing-refiner.js +0 -2
- package/dist/core/boot-orchestrator.js +9 -1
- package/dist/core/features-config.js +5 -1
- package/dist/integrations/grok/grok-cli.js +47 -25
- package/dist/integrations/grok/hooks/grok-hook-activity.js +53 -0
- package/dist/integrations/grok/hooks/grok-hook-utils.js +261 -0
- package/dist/integrations/grok/hooks/pre-tool-use.js +85 -115
- package/dist/integrations/grok/hooks/session-start.js +52 -0
- package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +20 -3
- package/dist/integrations/hermes-agent/bridge.mjs +11 -1
- package/dist/mcps/config/server-config-registry.js +4 -3
- package/dist/mcps/orchestrator/handlers/complexity-handler.js +33 -3
- package/dist/mcps/orchestrator/server.js +1 -1
- package/dist/mcps/registry.json +0 -1
- package/dist/mcps/researcher.server.js +3 -0
- package/dist/nucleus/autonomy-kernel.js +176 -0
- package/dist/nucleus/index.js +1 -0
- package/dist/orchestrator/universal-registry-bridge.js +1 -1
- package/dist/skills/boot-orchestrator/SKILL.md +1 -5
- package/dist/skills/orchestrator/SKILL.md +35 -12
- package/dist/skills/registry.json +0 -1
- package/dist/skills/researcher/SKILL.md +17 -4
- package/dist/skills/strategist/SKILL.md +12 -0
- package/dist/skills/testing-strategy/SKILL.md +13 -2
- package/package.json +23 -8
- package/scripts/node/consumer-install-smoke.mjs +163 -0
- package/scripts/node/enforce-agents-md.mjs +0 -26
- package/scripts/node/enforce-version-compliance.sh +0 -1
- package/scripts/node/enforce-version-compliance.ts +0 -10
- package/scripts/node/install-bridges.cjs +154 -39
- package/scripts/node/pre-publish-guard.js +108 -165
- package/scripts/node/reconcile-version.mjs +147 -0
- package/scripts/node/release-gate.mjs +66 -0
- package/scripts/node/release.js +5 -147
- package/scripts/node/release.mjs +106 -181
- package/scripts/node/setup.cjs +21 -26
- package/scripts/node/sync-versions.mjs +12 -134
- package/scripts/node/universal-version-manager.js +22 -4
- package/scripts/node/validate-release-docs.mjs +299 -0
- package/scripts/node/version-manager.mjs +225 -49
- package/src/integrations/grok/plugin/0xray/hooks/hooks.json +20 -3
- package/src/mcps/config/__tests__/server-config-registry.test.ts +9 -0
- package/src/mcps/config/server-config-registry.ts +5 -3
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +54 -3
- package/src/mcps/orchestrator/server.ts +1 -1
- package/src/mcps/registry.json +0 -1
- package/src/mcps/researcher.server.ts +4 -0
- package/src/opencode/enforcer-config.json +1 -4
- package/src/skills/boot-orchestrator/SKILL.md +1 -5
- package/src/skills/orchestrator/SKILL.md +35 -12
- package/src/skills/registry.json +0 -1
- package/src/skills/researcher/SKILL.md +17 -4
- package/src/skills/strategist/SKILL.md +12 -0
- package/src/skills/testing-strategy/SKILL.md +13 -2
- package/xray/config.json +25 -0
- package/xray/features.json +146 -0
- 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
|
-
|
|
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
|
-
|
|
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 (
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
16
|
-
const
|
|
34
|
+
async function main() {
|
|
35
|
+
const root = workspaceRoot();
|
|
36
|
+
let toolName = 'unknown';
|
|
17
37
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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();
|