0xray 3.3.1 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/enforcer-config.json +1 -4
- package/dist/CHANGELOG.md +12 -0
- package/dist/analytics/routing-refiner.js +0 -2
- package/dist/core/boot-orchestrator.js +9 -1
- package/dist/core/features-config.js +0 -1
- package/dist/integrations/grok/grok-cli.js +47 -25
- package/dist/integrations/hermes-agent/bridge.mjs +11 -1
- package/dist/mcps/registry.json +0 -1
- package/dist/orchestrator/universal-registry-bridge.js +1 -1
- package/dist/skills/registry.json +0 -1
- package/package.json +1 -1
- 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 +135 -33
- package/scripts/node/pre-publish-guard.js +20 -16
- package/scripts/node/setup.cjs +10 -5
- package/scripts/node/universal-version-manager.js +18 -4
- package/src/mcps/registry.json +0 -1
- package/src/opencode/enforcer-config.json +1 -4
- package/src/skills/registry.json +0 -1
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"version": "3.1.0",
|
|
4
|
-
"description": "Codex-compliant framework configuration for Credible UI project",
|
|
2
|
+
"description": "Codex-compliant framework configuration",
|
|
5
3
|
"thresholds": {
|
|
6
4
|
"bundleSize": {
|
|
7
5
|
"maxSize": "3MB",
|
|
@@ -220,7 +218,6 @@
|
|
|
220
218
|
}
|
|
221
219
|
},
|
|
222
220
|
"codex": {
|
|
223
|
-
"version": "3.1.0",
|
|
224
221
|
"terms": [
|
|
225
222
|
1,
|
|
226
223
|
2,
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Conventional Commits](https://www.conventionalcommits.org/).
|
|
6
6
|
|
|
7
|
+
## [3.4.1] - 2026-06-17
|
|
8
|
+
|
|
9
|
+
### ✨ Features
|
|
10
|
+
- Unified 4-platform bridge installer (`install-bridges.cjs`) runs on consumer `npm i 0xray` postinstall — OpenCode, Grok, Hermes, OpenClaw
|
|
11
|
+
- All 7 MCP servers registered via `npx -y 0xray mcp <cmd>` (no brittle `dist/` paths)
|
|
12
|
+
- `npx 0xray grok install` aligned with postinstall: 7 servers via npx, idempotent skill/MCP sync on re-run
|
|
13
|
+
- Grok skills synced to both `~/.grok/plugins/0xray/skills/` and `~/.grok/skills/` (Grok Build / Cursor)
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug Fixes
|
|
16
|
+
- `setup.cjs` consumer detection uses `isConsumerInstall()` — works for both `node_modules/0xray` and `node_modules/xray`
|
|
17
|
+
- Hermes bridge honors `XRAY_ROOT` env and `xray-consumer-root.txt` marker for correct project root resolution
|
|
18
|
+
|
|
7
19
|
## [3.3.0] - 2026-06-18
|
|
8
20
|
|
|
9
21
|
### 🔄 Changes
|
|
@@ -35,7 +35,6 @@ class RoutingRefiner {
|
|
|
35
35
|
const optimizations = this.suggestMappingOptimizations(promptAnalysis, performanceReport);
|
|
36
36
|
const warnings = this.generateWarnings(newMappings, optimizations);
|
|
37
37
|
return {
|
|
38
|
-
version: "3.1.0",
|
|
39
38
|
generatedAt: new Date(),
|
|
40
39
|
summary: {
|
|
41
40
|
newMappings: newMappings.length,
|
|
@@ -247,7 +246,6 @@ class RoutingRefiner {
|
|
|
247
246
|
lines.push("╚════════════════════════════════════════════════════════════╝");
|
|
248
247
|
lines.push("");
|
|
249
248
|
lines.push(`Generated: ${report.configurationUpdate.generatedAt.toISOString()}`);
|
|
250
|
-
lines.push(`Version: ${report.configurationUpdate.version}`);
|
|
251
249
|
lines.push("");
|
|
252
250
|
lines.push("=== Summary ===");
|
|
253
251
|
lines.push(`New Mappings: ${report.configurationUpdate.summary.newMappings}`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Boot Orchestrator (min documented consumer runtime compat shim from prior 0xRay releases; primary Xray paths + .xray fallbacks only; 1-line per Scope Rule).
|
|
3
3
|
*/
|
|
4
|
+
import { readFileSync } from "fs";
|
|
4
5
|
import { XrayContextLoader } from "./context-loader.js";
|
|
5
6
|
import { XrayStateManager } from "../state/state-manager.js";
|
|
6
7
|
import { ProcessorManager } from "../processors/processor-manager.js";
|
|
@@ -721,7 +722,14 @@ export class BootOrchestrator {
|
|
|
721
722
|
try {
|
|
722
723
|
// Consumer runtime compat shim from prior 0xRay releases (1-line min per Scope Rule; primary xray paths + .xray fallbacks)
|
|
723
724
|
const xrayConfig = {
|
|
724
|
-
version:
|
|
725
|
+
version: (() => {
|
|
726
|
+
try {
|
|
727
|
+
return JSON.parse(readFileSync(path.join(process.cwd(), "package.json"), "utf-8")).version;
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
return "unknown";
|
|
731
|
+
}
|
|
732
|
+
})(),
|
|
725
733
|
codex_enabled: true,
|
|
726
734
|
codex_version: "v1.7.5",
|
|
727
735
|
codex_terms: [
|
|
@@ -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
|
}
|
|
@@ -53,9 +53,19 @@ let frameworkLoadAttempted = false;
|
|
|
53
53
|
|
|
54
54
|
// ── Project root detection ───────────────────────────────────
|
|
55
55
|
function findProjectRoot() {
|
|
56
|
-
const envHome = process.env.XRAY_HOME;
|
|
56
|
+
const envHome = process.env.XRAY_HOME || process.env.XRAY_ROOT;
|
|
57
57
|
if (envHome && existsSync(join(envHome, "package.json"))) return envHome;
|
|
58
58
|
|
|
59
|
+
const consumerMarker = join(__dirname, "xray-consumer-root.txt");
|
|
60
|
+
if (existsSync(consumerMarker)) {
|
|
61
|
+
try {
|
|
62
|
+
const marked = readFileSync(consumerMarker, "utf-8").trim();
|
|
63
|
+
if (marked && existsSync(join(marked, "package.json"))) return marked;
|
|
64
|
+
} catch {
|
|
65
|
+
// fall through
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
const candidates = [
|
|
60
70
|
process.cwd(),
|
|
61
71
|
join(homedir(), "dev", "xray"),
|
package/dist/mcps/registry.json
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* AGENTS.md Enforcement Script
|
|
4
|
-
*
|
|
5
|
-
* Validates that AGENTS.md exists, is up-to-date, and follows the required format.
|
|
6
|
-
* This script should be run in CI/CD, pre-commit hooks, and local validation.
|
|
7
|
-
*
|
|
8
|
-
* @version 1.0.0
|
|
9
|
-
* @framework xray v2
|
|
10
3
|
*/
|
|
11
4
|
|
|
12
5
|
import fs from 'fs';
|
|
@@ -62,7 +55,6 @@ class AgentsMdEnforcer {
|
|
|
62
55
|
}
|
|
63
56
|
|
|
64
57
|
/**
|
|
65
|
-
* Run all validation checks
|
|
66
58
|
*/
|
|
67
59
|
async validate() {
|
|
68
60
|
console.log('🔍 AGENTS.md Enforcement Check');
|
|
@@ -148,14 +140,12 @@ class AgentsMdEnforcer {
|
|
|
148
140
|
}
|
|
149
141
|
|
|
150
142
|
/**
|
|
151
|
-
* Check if AGENTS.md file exists
|
|
152
143
|
*/
|
|
153
144
|
checkFileExists() {
|
|
154
145
|
return fs.existsSync(this.agentsPath);
|
|
155
146
|
}
|
|
156
147
|
|
|
157
148
|
/**
|
|
158
|
-
* Check for required sections
|
|
159
149
|
*/
|
|
160
150
|
checkRequiredSections(content) {
|
|
161
151
|
const missing = [];
|
|
@@ -188,7 +178,6 @@ class AgentsMdEnforcer {
|
|
|
188
178
|
}
|
|
189
179
|
|
|
190
180
|
/**
|
|
191
|
-
* Check for required agent definitions
|
|
192
181
|
*/
|
|
193
182
|
checkRequiredAgents(content) {
|
|
194
183
|
// Be flexible - check for agent names with or without @ prefix
|
|
@@ -199,7 +188,6 @@ class AgentsMdEnforcer {
|
|
|
199
188
|
}
|
|
200
189
|
|
|
201
190
|
/**
|
|
202
|
-
* Extract version from header
|
|
203
191
|
*/
|
|
204
192
|
extractVersion(content) {
|
|
205
193
|
const match = content.match(/\*\*Version\*\*:\s*(\d+\.\d+\.\d+)/);
|
|
@@ -207,7 +195,6 @@ class AgentsMdEnforcer {
|
|
|
207
195
|
}
|
|
208
196
|
|
|
209
197
|
/**
|
|
210
|
-
* Extract date from header
|
|
211
198
|
*/
|
|
212
199
|
extractDate(content) {
|
|
213
200
|
const match = content.match(/\*\*Updated\*\*:\s*(\d{4}-\d{2}-\d{2})/);
|
|
@@ -215,7 +202,6 @@ class AgentsMdEnforcer {
|
|
|
215
202
|
}
|
|
216
203
|
|
|
217
204
|
/**
|
|
218
|
-
* Calculate how many days old the file is
|
|
219
205
|
*/
|
|
220
206
|
calculateDaysOld(dateString) {
|
|
221
207
|
const date = new Date(dateString);
|
|
@@ -225,18 +211,12 @@ class AgentsMdEnforcer {
|
|
|
225
211
|
}
|
|
226
212
|
|
|
227
213
|
/**
|
|
228
|
-
* Calculate content hash
|
|
229
214
|
*/
|
|
230
215
|
calculateHash(content) {
|
|
231
216
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
232
217
|
}
|
|
233
218
|
|
|
234
219
|
/**
|
|
235
|
-
* Count codex terms (numbered rules or sections with multiple items)
|
|
236
|
-
* Looks for patterns like:
|
|
237
|
-
* - #### 1. Something
|
|
238
|
-
* - 1. **Something**
|
|
239
|
-
* - ## CRITICAL RULES (count as one term block)
|
|
240
220
|
*/
|
|
241
221
|
countCodexTerms(content) {
|
|
242
222
|
// Count #### numbered items
|
|
@@ -257,7 +237,6 @@ class AgentsMdEnforcer {
|
|
|
257
237
|
}
|
|
258
238
|
|
|
259
239
|
/**
|
|
260
|
-
* Log individual check result
|
|
261
240
|
*/
|
|
262
241
|
logResult(check, status, message) {
|
|
263
242
|
const icon = status === 'PASS' ? '✅' : status === 'FAIL' ? '❌' : status === 'WARN' ? '⚠️' : 'ℹ️';
|
|
@@ -266,7 +245,6 @@ class AgentsMdEnforcer {
|
|
|
266
245
|
}
|
|
267
246
|
|
|
268
247
|
/**
|
|
269
|
-
* Generate and display report
|
|
270
248
|
*/
|
|
271
249
|
report(exitCode) {
|
|
272
250
|
console.log('\n================================');
|
|
@@ -309,7 +287,6 @@ class AgentsMdEnforcer {
|
|
|
309
287
|
}
|
|
310
288
|
|
|
311
289
|
/**
|
|
312
|
-
* Auto-generate AGENTS.md from current agent configuration
|
|
313
290
|
*/
|
|
314
291
|
async generate() {
|
|
315
292
|
console.log('📝 Generating AGENTS.md...\n');
|
|
@@ -325,7 +302,6 @@ class AgentsMdEnforcer {
|
|
|
325
302
|
}
|
|
326
303
|
|
|
327
304
|
/**
|
|
328
|
-
* Load template file
|
|
329
305
|
*/
|
|
330
306
|
async loadTemplate() {
|
|
331
307
|
const templatePath = path.join(__dirname, '..', '..', '..', 'docs', 'AGENTS_TEMPLATE.md');
|
|
@@ -342,7 +318,6 @@ class AgentsMdEnforcer {
|
|
|
342
318
|
}
|
|
343
319
|
|
|
344
320
|
/**
|
|
345
|
-
* Discover agents from configuration
|
|
346
321
|
*/
|
|
347
322
|
async discoverAgents() {
|
|
348
323
|
const agents = [];
|
|
@@ -366,7 +341,6 @@ class AgentsMdEnforcer {
|
|
|
366
341
|
}
|
|
367
342
|
|
|
368
343
|
/**
|
|
369
|
-
* Populate template with agent data
|
|
370
344
|
*/
|
|
371
345
|
populateTemplate(template, agents) {
|
|
372
346
|
const now = new Date().toISOString().split('T')[0];
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Version Compliance Enforcement Script
|
|
4
|
-
*
|
|
5
|
-
* Thin CLI wrapper around VersionComplianceProcessor.
|
|
6
|
-
* Called by CI/CD, pre-commit hooks, and manual runs.
|
|
7
|
-
*
|
|
8
|
-
* This script should NOT contain enforcement logic -
|
|
9
|
-
* all logic lives in src/processors/version-compliance-processor.ts
|
|
10
|
-
*
|
|
11
|
-
* @version 1.0.0
|
|
12
|
-
* @deprecated Use VersionComplianceProcessor directly in new code
|
|
13
3
|
*/
|
|
14
4
|
|
|
15
5
|
import path from 'path';
|
|
@@ -10,19 +10,37 @@ const os = require("os");
|
|
|
10
10
|
const { execSync } = require("child_process");
|
|
11
11
|
|
|
12
12
|
const SKIP_DIRS = new Set(["node_modules", "logs"]);
|
|
13
|
-
const MERGE_FILES = new Set(["
|
|
13
|
+
const MERGE_FILES = new Set(["enforcer-config.json"]);
|
|
14
14
|
const KEEP_IF_EXISTS = new Set([".yml", ".yaml", ".md"]);
|
|
15
15
|
|
|
16
|
+
/** Canonical 7-server MCP surface — matches package .mcp.json and Grok plugin */
|
|
17
|
+
const XRAY_MCP_SERVERS = [
|
|
18
|
+
{ name: "xray-governance", mcpCmd: "governance", env: { XRAY_FORCE_MCP_GOVERNANCE: "true" } },
|
|
19
|
+
{ name: "xray-skills", mcpCmd: "skills", env: {} },
|
|
20
|
+
{ name: "xray-orchestrator", mcpCmd: "orchestrator", env: {} },
|
|
21
|
+
{ name: "xray-enforcer", mcpCmd: "enforcer", env: {} },
|
|
22
|
+
{ name: "xray-researcher", mcpCmd: "researcher", env: {} },
|
|
23
|
+
{ name: "xray-code-review", mcpCmd: "code-review", env: {} },
|
|
24
|
+
{ name: "xray-architect-tools", mcpCmd: "architect-tools", env: {} },
|
|
25
|
+
];
|
|
26
|
+
|
|
16
27
|
function resolveConsumerTargetDir(packageRoot, fallbackDir) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return
|
|
28
|
+
const resolved = path.resolve(packageRoot);
|
|
29
|
+
const inNodeModules =
|
|
30
|
+
resolved.includes(`${path.sep}node_modules${path.sep}`) ||
|
|
31
|
+
resolved.endsWith(`${path.sep}node_modules`);
|
|
32
|
+
|
|
33
|
+
if (!inNodeModules) {
|
|
34
|
+
return fallbackDir || process.env.PWD || process.cwd();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let current = resolved;
|
|
38
|
+
while (path.basename(current) !== "node_modules") {
|
|
39
|
+
const parent = path.dirname(current);
|
|
40
|
+
if (parent === current) return fallbackDir || process.cwd();
|
|
41
|
+
current = parent;
|
|
24
42
|
}
|
|
25
|
-
return
|
|
43
|
+
return path.dirname(current);
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
function isConsumerInstall(packageRoot, targetDir) {
|
|
@@ -108,6 +126,91 @@ function copyPluginDir(src, dest) {
|
|
|
108
126
|
return true;
|
|
109
127
|
}
|
|
110
128
|
|
|
129
|
+
function buildConsumerMcpConfig(targetDir) {
|
|
130
|
+
const mcpServers = {};
|
|
131
|
+
for (const s of XRAY_MCP_SERVERS) {
|
|
132
|
+
mcpServers[s.name] = {
|
|
133
|
+
command: "npx",
|
|
134
|
+
args: ["-y", "0xray", "mcp", s.mcpCmd],
|
|
135
|
+
env: { ...s.env, XRAY_ROOT: targetDir },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { mcpServers };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function deployProjectMcpJson(targetDir, log) {
|
|
142
|
+
const destPath = path.join(targetDir, ".mcp.json");
|
|
143
|
+
const consumerConfig = buildConsumerMcpConfig(targetDir);
|
|
144
|
+
let existing = { mcpServers: {} };
|
|
145
|
+
if (fs.existsSync(destPath)) {
|
|
146
|
+
try {
|
|
147
|
+
existing = JSON.parse(fs.readFileSync(destPath, "utf8"));
|
|
148
|
+
} catch {
|
|
149
|
+
existing = { mcpServers: {} };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const merged = {
|
|
153
|
+
...existing,
|
|
154
|
+
mcpServers: { ...(existing.mcpServers || {}), ...consumerConfig.mcpServers },
|
|
155
|
+
};
|
|
156
|
+
fs.writeFileSync(destPath, JSON.stringify(merged, null, 2) + "\n");
|
|
157
|
+
log("mcp-config", "project .mcp.json deployed (7 xray servers)", "info");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function writePluginMcpJson(pluginDir, targetDir, log, label) {
|
|
161
|
+
if (!fs.existsSync(pluginDir)) return;
|
|
162
|
+
const dest = path.join(pluginDir, ".mcp.json");
|
|
163
|
+
fs.writeFileSync(dest, JSON.stringify(buildConsumerMcpConfig(targetDir), null, 2) + "\n");
|
|
164
|
+
log(label, "plugin .mcp.json patched for consumer", "info");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function mergeOpencodeJson(targetDir, packageRoot, log) {
|
|
168
|
+
const rootOpencode = path.join(packageRoot, "opencode.json");
|
|
169
|
+
const userOpencode = path.join(targetDir, "opencode.json");
|
|
170
|
+
if (!fs.existsSync(rootOpencode)) return;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const srcData = JSON.parse(fs.readFileSync(rootOpencode, "utf8"));
|
|
174
|
+
if (fs.existsSync(userOpencode)) {
|
|
175
|
+
const destData = JSON.parse(fs.readFileSync(userOpencode, "utf8"));
|
|
176
|
+
const merged = { ...destData };
|
|
177
|
+
if (srcData.agent) merged.agent = srcData.agent;
|
|
178
|
+
if (srcData.mcp) merged.mcp = { ...destData.mcp, ...srcData.mcp };
|
|
179
|
+
if (srcData.compaction) merged.compaction = srcData.compaction;
|
|
180
|
+
fs.writeFileSync(userOpencode, JSON.stringify(merged, null, 2) + "\n");
|
|
181
|
+
} else {
|
|
182
|
+
fs.copyFileSync(rootOpencode, userOpencode);
|
|
183
|
+
}
|
|
184
|
+
log("opencode-bridge", "opencode.json merged", "info");
|
|
185
|
+
} catch (e) {
|
|
186
|
+
log("opencode-bridge", "opencode.json merge failed", "warn", { error: e.message });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function patchGrokHooks(pluginDir, packageRoot, targetDir, log, label) {
|
|
191
|
+
const hooksPath = path.join(pluginDir, "hooks", "hooks.json");
|
|
192
|
+
const hookScript = path.join(packageRoot, "dist", "integrations", "grok", "hooks", "pre-tool-use.js");
|
|
193
|
+
if (!fs.existsSync(hooksPath)) return;
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const hooks = JSON.parse(fs.readFileSync(hooksPath, "utf8"));
|
|
197
|
+
const preTool = hooks.hooks?.PreToolUse?.[0]?.hooks?.[0];
|
|
198
|
+
if (preTool && fs.existsSync(hookScript)) {
|
|
199
|
+
preTool.command = "node";
|
|
200
|
+
preTool.args = [hookScript];
|
|
201
|
+
preTool.env = {
|
|
202
|
+
...(preTool.env || {}),
|
|
203
|
+
XRAY_ROOT: targetDir,
|
|
204
|
+
XRAY_AI_PATH: packageRoot,
|
|
205
|
+
};
|
|
206
|
+
fs.writeFileSync(hooksPath, JSON.stringify(hooks, null, 2) + "\n");
|
|
207
|
+
log(label, "hooks.json patched → enforcement gate", "info");
|
|
208
|
+
}
|
|
209
|
+
} catch (e) {
|
|
210
|
+
log(label, "hooks.json patch failed", "warn", { error: e.message });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
111
214
|
function installOpencodeBridge(targetDir, packageRoot, log) {
|
|
112
215
|
const opencodeSource = path.join(packageRoot, ".opencode");
|
|
113
216
|
const opencodeDest = path.join(targetDir, ".opencode");
|
|
@@ -149,37 +252,25 @@ function findGrokPluginSource(packageRoot) {
|
|
|
149
252
|
return candidates.find((p) => fs.existsSync(p));
|
|
150
253
|
}
|
|
151
254
|
|
|
152
|
-
function registerGrokMcpServers(
|
|
255
|
+
function registerGrokMcpServers(targetDir, log) {
|
|
153
256
|
try {
|
|
154
257
|
execSync("which grok", { stdio: "ignore" });
|
|
155
258
|
} catch {
|
|
156
|
-
log("grok-bridge", "grok CLI not on PATH —
|
|
259
|
+
log("grok-bridge", "grok CLI not on PATH — plugin .mcp.json still configured", "info");
|
|
157
260
|
return;
|
|
158
261
|
}
|
|
159
262
|
|
|
160
|
-
const
|
|
161
|
-
const skillsServer = path.join(packageRoot, "dist/mcps/knowledge-skills/skill-invocation.server.js");
|
|
162
|
-
const orchServer = path.join(packageRoot, "dist/mcps/orchestrator.server.js");
|
|
163
|
-
const enforcerServer = path.join(packageRoot, "dist/mcps/enforcer-tools.server.js");
|
|
164
|
-
|
|
165
|
-
const servers = [
|
|
166
|
-
["xray-governance", govServer, `XRAY_FORCE_MCP_GOVERNANCE=true`],
|
|
167
|
-
["xray-skills", skillsServer, null],
|
|
168
|
-
["xray-orchestrator", orchServer, null],
|
|
169
|
-
["xray-enforcer", enforcerServer, null],
|
|
170
|
-
];
|
|
171
|
-
|
|
172
|
-
for (const [name, serverPath, extraEnv] of servers) {
|
|
173
|
-
if (!fs.existsSync(serverPath)) continue;
|
|
263
|
+
for (const s of XRAY_MCP_SERVERS) {
|
|
174
264
|
try {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
265
|
+
const envEntries = { ...s.env, XRAY_ROOT: targetDir };
|
|
266
|
+
const envFlags = Object.entries(envEntries)
|
|
267
|
+
.map(([k, v]) => `--env "${k}=${v}"`)
|
|
268
|
+
.join(" ");
|
|
178
269
|
execSync(
|
|
179
|
-
`grok mcp add ${name} --command
|
|
270
|
+
`grok mcp add ${s.name} --command npx --args "-y" "0xray" "mcp" "${s.mcpCmd}" ${envFlags}`,
|
|
180
271
|
{ stdio: "pipe" }
|
|
181
272
|
);
|
|
182
|
-
log("grok-bridge", `registered ${name}`, "info");
|
|
273
|
+
log("grok-bridge", `registered ${s.name} (npx)`, "info");
|
|
183
274
|
} catch {
|
|
184
275
|
// already registered or grok config conflict — non-blocking
|
|
185
276
|
}
|
|
@@ -217,6 +308,8 @@ function installGrokBridge(targetDir, packageRoot, log) {
|
|
|
217
308
|
if (copyPluginDir(sourceDir, dest)) {
|
|
218
309
|
const rel = dest.startsWith(home) ? dest.replace(home, "~") : path.relative(targetDir, dest);
|
|
219
310
|
log("grok-bridge", "plugin copied", "info", { path: rel || dest });
|
|
311
|
+
writePluginMcpJson(dest, targetDir, log, "grok-bridge");
|
|
312
|
+
patchGrokHooks(dest, packageRoot, targetDir, log, "grok-bridge");
|
|
220
313
|
const copied = syncBuiltinSkills(path.join(dest, "skills"), packageRoot);
|
|
221
314
|
if (copied > 0) log("grok-bridge", `plugin skills synced (${copied})`, "info", { path: rel });
|
|
222
315
|
}
|
|
@@ -229,10 +322,10 @@ function installGrokBridge(targetDir, packageRoot, log) {
|
|
|
229
322
|
log("grok-bridge", `global skills synced (${globalCopied})`, "info", { path: "~/.grok/skills/" });
|
|
230
323
|
}
|
|
231
324
|
|
|
232
|
-
registerGrokMcpServers(
|
|
325
|
+
registerGrokMcpServers(targetDir, log);
|
|
233
326
|
}
|
|
234
327
|
|
|
235
|
-
function installHermesBridge(packageRoot, log) {
|
|
328
|
+
function installHermesBridge(targetDir, packageRoot, log) {
|
|
236
329
|
const sources = [
|
|
237
330
|
path.join(packageRoot, "dist", "integrations", "hermes-agent"),
|
|
238
331
|
path.join(packageRoot, "src", "integrations", "hermes-agent"),
|
|
@@ -256,8 +349,15 @@ function installHermesBridge(packageRoot, log) {
|
|
|
256
349
|
}
|
|
257
350
|
log("hermes-bridge", "plugin copied", "info", { path: "~/.hermes/plugins/xray-hermes" });
|
|
258
351
|
|
|
352
|
+
writePluginMcpJson(targetPluginDir, targetDir, log, "hermes-bridge");
|
|
353
|
+
|
|
259
354
|
const copied = syncBuiltinSkills(path.join(targetPluginDir, "skills"), packageRoot);
|
|
260
355
|
if (copied > 0) log("hermes-bridge", `skills synced (${copied})`, "info");
|
|
356
|
+
|
|
357
|
+
// Marker so bridge.mjs resolves the consumer project on hook invocation
|
|
358
|
+
const rootMarker = path.join(targetPluginDir, "xray-consumer-root.txt");
|
|
359
|
+
fs.writeFileSync(rootMarker, targetDir + "\n");
|
|
360
|
+
log("hermes-bridge", "consumer root marker written", "info");
|
|
261
361
|
}
|
|
262
362
|
|
|
263
363
|
function installOpenclawBridge(targetDir, packageRoot, log) {
|
|
@@ -347,9 +447,11 @@ function installAllBridges(opts) {
|
|
|
347
447
|
log("install-bridges", "starting 4-platform install", "info");
|
|
348
448
|
|
|
349
449
|
deployXrayConfig(targetDir, packageRoot, log);
|
|
450
|
+
deployProjectMcpJson(targetDir, log);
|
|
451
|
+
mergeOpencodeJson(targetDir, packageRoot, log);
|
|
350
452
|
installOpencodeBridge(targetDir, packageRoot, log);
|
|
351
453
|
installGrokBridge(targetDir, packageRoot, log);
|
|
352
|
-
installHermesBridge(packageRoot, log);
|
|
454
|
+
installHermesBridge(targetDir, packageRoot, log);
|
|
353
455
|
installOpenclawBridge(targetDir, packageRoot, log);
|
|
354
456
|
installGitHooks(packageRoot, log);
|
|
355
457
|
|
|
@@ -92,6 +92,7 @@ function checkGitStatus() {
|
|
|
92
92
|
'logs/',
|
|
93
93
|
'backups/',
|
|
94
94
|
'Users/',
|
|
95
|
+
'.xray/inference/',
|
|
95
96
|
];
|
|
96
97
|
|
|
97
98
|
const realChanges = lines.filter(l => {
|
|
@@ -128,32 +129,35 @@ function checkGitStatus() {
|
|
|
128
129
|
|
|
129
130
|
function checkVersionSync() {
|
|
130
131
|
log('Checking version synchronization...', 'step');
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
|
|
133
|
+
const pkgPath = path.join(rootDir, 'package.json');
|
|
134
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
135
|
+
const pkgVersion = pkg.version;
|
|
136
|
+
|
|
133
137
|
const vmPath = path.join(rootDir, 'scripts/node/universal-version-manager.js');
|
|
134
138
|
const vmContent = fs.readFileSync(vmPath, 'utf-8');
|
|
135
|
-
const
|
|
136
|
-
|
|
139
|
+
const usesPackageSsot =
|
|
140
|
+
vmContent.includes('readPackageVersion()') &&
|
|
141
|
+
/version:\s*PACKAGE_VERSION/.test(vmContent);
|
|
142
|
+
|
|
143
|
+
if (usesPackageSsot) {
|
|
144
|
+
log(`Version SSOT: package.json (${pkgVersion})`, 'success');
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const versionMatch = vmContent.match(/framework:\s*\{[\s\S]*?version:\s*"([^"]+)"/);
|
|
137
149
|
if (!versionMatch) {
|
|
138
|
-
errors.push('Cannot find version in version manager');
|
|
150
|
+
errors.push('Cannot find framework version in version manager');
|
|
139
151
|
return false;
|
|
140
152
|
}
|
|
141
|
-
|
|
153
|
+
|
|
142
154
|
const vmVersion = versionMatch[1];
|
|
143
|
-
|
|
144
|
-
// Load package.json
|
|
145
|
-
const pkgPath = path.join(rootDir, 'package.json');
|
|
146
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
147
|
-
const pkgVersion = pkg.version;
|
|
148
|
-
|
|
149
155
|
if (pkgVersion !== vmVersion) {
|
|
150
|
-
// package.json is often 1 ahead of UVM after npm version patch — non-blocking
|
|
151
156
|
warnings.push(`Version mismatch: package.json (${pkgVersion}) != version manager (${vmVersion})`);
|
|
152
157
|
log(`Version mismatch: package.json=${pkgVersion}, version-manager=${vmVersion}`, 'warn');
|
|
153
|
-
|
|
158
|
+
} else {
|
|
159
|
+
log(`Versions aligned: ${pkgVersion}`, 'success');
|
|
154
160
|
}
|
|
155
|
-
|
|
156
|
-
log(`Versions aligned: ${pkgVersion}`, 'success');
|
|
157
161
|
return true;
|
|
158
162
|
}
|
|
159
163
|
|
package/scripts/node/setup.cjs
CHANGED
|
@@ -6,7 +6,7 @@ const path = require("path");
|
|
|
6
6
|
const packageRoot = path.join(__dirname, "..", "..");
|
|
7
7
|
const homeDir = require("os").homedir();
|
|
8
8
|
|
|
9
|
-
const { resolveConsumerTargetDir } = require("./install-bridges.cjs");
|
|
9
|
+
const { resolveConsumerTargetDir, isConsumerInstall } = require("./install-bridges.cjs");
|
|
10
10
|
let targetDir = resolveConsumerTargetDir(packageRoot, process.env.PWD || process.cwd());
|
|
11
11
|
|
|
12
12
|
const resolvedPackage = path.resolve(packageRoot);
|
|
@@ -155,8 +155,13 @@ if (fs.existsSync(distSource)) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// 6. Convert MCP paths in consumer opencode.json
|
|
158
|
-
const isConsumer =
|
|
159
|
-
|
|
158
|
+
const isConsumer = isConsumerInstall(packageRoot, targetDir);
|
|
159
|
+
const consumerPkgName = isConsumer
|
|
160
|
+
? ["0xray", "xray"].find((name) =>
|
|
161
|
+
fs.existsSync(path.join(targetDir, "node_modules", name, "package.json"))
|
|
162
|
+
)
|
|
163
|
+
: null;
|
|
164
|
+
if (!hasHermes && isConsumer && consumerPkgName) {
|
|
160
165
|
const mainOpencodePath = path.join(targetDir, "opencode.json");
|
|
161
166
|
if (fs.existsSync(mainOpencodePath)) {
|
|
162
167
|
try {
|
|
@@ -167,13 +172,13 @@ if (!hasHermes && isConsumer) {
|
|
|
167
172
|
if (server.command && typeof server.command === "string") {
|
|
168
173
|
const normalized = server.command.replace(
|
|
169
174
|
/^[.]{0,2}\/dist\/mcps\//,
|
|
170
|
-
|
|
175
|
+
`node_modules/${consumerPkgName}/dist/mcps/`
|
|
171
176
|
);
|
|
172
177
|
if (normalized !== server.command) { server.command = normalized; modified = true; }
|
|
173
178
|
}
|
|
174
179
|
if (Array.isArray(server.command)) {
|
|
175
180
|
server.command = server.command.map(a =>
|
|
176
|
-
a.replace(/^[.]{0,2}\/dist\/mcps\//,
|
|
181
|
+
a.replace(/^[.]{0,2}\/dist\/mcps\//, `node_modules/${consumerPkgName}/dist/mcps/`)
|
|
177
182
|
);
|
|
178
183
|
modified = true;
|
|
179
184
|
}
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* - Historical docs (docs/reflections, docs/archive)
|
|
18
18
|
* - Test files with assertions
|
|
19
19
|
*
|
|
20
|
-
* @version 2.0.0
|
|
21
20
|
* @since 2026-01-15
|
|
22
21
|
* @updated 2026-03-09 - Enhanced with documentation management, validation, and backup
|
|
23
22
|
*/
|
|
@@ -28,6 +27,19 @@ const require = createRequire(import.meta.url);
|
|
|
28
27
|
import fs from "fs";
|
|
29
28
|
import path from "path";
|
|
30
29
|
import { execSync } from "child_process";
|
|
30
|
+
import { fileURLToPath } from "url";
|
|
31
|
+
|
|
32
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const rootDir = path.resolve(__dirname, "../..");
|
|
34
|
+
|
|
35
|
+
function readPackageVersion() {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf-8"));
|
|
38
|
+
return pkg.version;
|
|
39
|
+
} catch {
|
|
40
|
+
return "0.0.0";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
31
43
|
|
|
32
44
|
/**
|
|
33
45
|
* Auto-calculate framework counts from filesystem
|
|
@@ -133,12 +145,14 @@ const CALCULATED_COUNTS = calculateCounts();
|
|
|
133
145
|
* Framework version information - SINGLE SOURCE OF TRUTH
|
|
134
146
|
* Version manager updates this, then propagates to all files
|
|
135
147
|
*/
|
|
148
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
149
|
+
|
|
136
150
|
const OFFICIAL_VERSIONS = {
|
|
137
|
-
// Framework version
|
|
151
|
+
// Framework version — SSOT: package.json
|
|
138
152
|
framework: {
|
|
139
|
-
version:
|
|
153
|
+
version: PACKAGE_VERSION,
|
|
140
154
|
displayName: "xray: Self-Healing AI Governance OS",
|
|
141
|
-
lastUpdated: "2026-06-
|
|
155
|
+
lastUpdated: "2026-06-17",
|
|
142
156
|
// Counts (auto-calculated, but can be overridden)
|
|
143
157
|
...CALCULATED_COUNTS,
|
|
144
158
|
},
|
package/src/mcps/registry.json
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"version": "3.1.0",
|
|
4
|
-
"description": "Codex-compliant framework configuration for Credible UI project",
|
|
2
|
+
"description": "Codex-compliant framework configuration",
|
|
5
3
|
"thresholds": {
|
|
6
4
|
"bundleSize": {
|
|
7
5
|
"maxSize": "3MB",
|
|
@@ -220,7 +218,6 @@
|
|
|
220
218
|
}
|
|
221
219
|
},
|
|
222
220
|
"codex": {
|
|
223
|
-
"version": "3.1.0",
|
|
224
221
|
"terms": [
|
|
225
222
|
1,
|
|
226
223
|
2,
|