0xray 4.0.1 → 4.0.3

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 (59) hide show
  1. package/.xray/features.json +1 -1
  2. package/AGENTS-consumer.md +4 -4
  3. package/AGENTS.md +3 -3
  4. package/README.md +1 -1
  5. package/SKILLS.md +1 -1
  6. package/dist/AGENTS.md +3 -3
  7. package/dist/CHANGELOG.md +43 -0
  8. package/dist/README.md +1 -1
  9. package/dist/SKILLS.md +1 -1
  10. package/dist/cli/commands/foundry-mint-wear.d.ts +3 -0
  11. package/dist/cli/commands/foundry-mint-wear.js +17 -0
  12. package/dist/cli/commands/hermes-install.js +2 -0
  13. package/dist/cli/commands/openclaw-install.js +2 -0
  14. package/dist/cli/commands/opencode-install.js +10 -2
  15. package/dist/cli/commands/skill-install.js +4 -0
  16. package/dist/enforcement/enforcer-tools.d.ts +7 -0
  17. package/dist/enforcement/enforcer-tools.js +18 -109
  18. package/dist/integrations/grok/grok-cli.js +2 -0
  19. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  20. package/dist/processors/implementations/version-compliance-processor.d.ts +3 -34
  21. package/dist/processors/implementations/version-compliance-processor.js +8 -135
  22. package/package.json +15 -14
  23. package/scripts/foundry/CHANGELOG.md +38 -0
  24. package/scripts/foundry/LICENSE +21 -0
  25. package/scripts/foundry/README.md +69 -0
  26. package/scripts/foundry/ci-monitor.mjs +115 -0
  27. package/scripts/foundry/cli.js +2 -0
  28. package/scripts/foundry/cli.mjs +55 -0
  29. package/scripts/foundry/docs-build.mjs +38 -0
  30. package/scripts/foundry/hooks.mjs +36 -0
  31. package/scripts/foundry/mill-root.mjs +62 -0
  32. package/scripts/foundry/mint-suit.cjs +397 -0
  33. package/scripts/foundry/mint.mjs +33 -0
  34. package/scripts/foundry/package.json +54 -0
  35. package/scripts/foundry/plant/agents/mill.yml +5 -0
  36. package/scripts/foundry/plant/skills/mill/SKILL.md +22 -0
  37. package/scripts/foundry/reconcile-version.mjs +150 -0
  38. package/scripts/foundry/release-gate.mjs +100 -0
  39. package/scripts/foundry/release.mjs +266 -0
  40. package/scripts/foundry/validate-release-docs.mjs +316 -0
  41. package/scripts/foundry/version-manager.mjs +444 -0
  42. package/scripts/node/ci-cd-auto-fix.cjs +12 -257
  43. package/scripts/node/consumer-install-smoke.mjs +17 -0
  44. package/scripts/node/enforce-version-compliance.sh +5 -20
  45. package/scripts/node/enforce-version-compliance.ts +13 -113
  46. package/scripts/node/foundry-shim.mjs +19 -0
  47. package/scripts/node/github-actions-monitor.cjs +13 -18
  48. package/scripts/node/install-bridges.cjs +43 -29
  49. package/scripts/node/postinstall.cjs +36 -1
  50. package/scripts/node/reconcile-version.mjs +3 -142
  51. package/scripts/node/release-gate.mjs +3 -74
  52. package/scripts/node/release.mjs +3 -171
  53. package/scripts/node/validate-release-docs.mjs +3 -285
  54. package/scripts/node/version-manager.mjs +3 -738
  55. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  56. package/xray/features.json +1 -1
  57. package/scripts/node/release.js +0 -17
  58. package/scripts/node/sync-versions.mjs +0 -18
  59. package/scripts/node/universal-version-manager.js +0 -1043
@@ -1,263 +1,18 @@
1
1
  #!/usr/bin/env node
2
-
3
- /**
4
- * CI/CD Auto-Fix Script
5
- *
6
- * Automatically fixes common CI/CD issues detected by the monitoring pipeline.
7
- *
8
- * Usage: node scripts/node/ci-cd-auto-fix.cjs
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const { execSync } = require('child_process');
14
-
15
- const rootDir = path.resolve(__dirname, '../..');
16
-
17
- // Colors for output
18
- const RED = '\x1b[0;31m';
19
- const GREEN = '\x1b[0;32m';
20
- const YELLOW = '\x1b[1;33m';
21
- const BLUE = '\x1b[0;34m';
22
- const NC = '\x1b[0m';
23
-
24
- const log = {
25
- info: (msg) => console.log(`${BLUE}ℹ️ ${msg}${NC}`),
26
- success: (msg) => console.log(`${GREEN}✅ ${msg}${NC}`),
27
- error: (msg) => console.log(`${RED}❌ ${msg}${NC}`),
28
- warn: (msg) => console.log(`${YELLOW}⚠️ ${msg}${NC}`),
29
- };
30
-
31
- const MAX_ITERATIONS = parseInt(process.env.MAX_ITERATIONS || '3');
32
- const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
33
- const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY || '';
34
-
35
- /**
36
- * Check if running in GitHub Actions
37
- */
38
- function isGitHubActions() {
39
- return !!process.env.GITHUB_ACTIONS;
40
- }
41
-
42
- /**
43
- * Run a command and return output
44
- */
45
- function runCommand(cmd, options = {}) {
46
- try {
47
- const output = execSync(cmd, {
48
- cwd: rootDir,
49
- encoding: 'utf8',
50
- stdio: 'pipe',
51
- ...options,
52
- });
53
- return { success: true, output };
54
- } catch (error) {
55
- return { success: false, error: error.message, output: error.stdout || '' };
56
- }
57
- }
58
-
59
- /**
60
- * Fix TypeScript errors
61
- */
62
- function fixTypeErrors() {
63
- log.info('Checking TypeScript compilation...');
64
- const result = runCommand('npx tsc --noEmit 2>&1 || true');
65
-
66
- if (result.success || result.output.includes('error TS')) {
67
- const tsErrors = result.output.match(/error TS\d+:/g) || [];
68
- if (tsErrors.length > 0) {
69
- log.warn(`Found ${tsErrors.length} TypeScript errors`);
70
- // Try to run typecheck with fix
71
- const fixResult = runCommand('npm run typecheck 2>&1 || true');
72
- if (fixResult.success) {
73
- log.success('TypeScript errors resolved');
74
- return true;
75
- }
76
- }
77
- }
78
- return false;
79
- }
80
-
81
2
  /**
82
- * Fix ESLint errors
3
+ * Mill does not auto-push CI fixes. Report only.
4
+ *
5
+ * node scripts/node/ci-cd-auto-fix.cjs
83
6
  */
84
- function fixLintErrors() {
85
- log.info('Checking ESLint...');
86
- const result = runCommand('npm run lint 2>&1 || true');
87
-
88
- if (result.output.includes('error') || result.output.includes('warning')) {
89
- log.warn('ESLint issues found, attempting fix...');
90
- const fixResult = runCommand('npm run lint:fix 2>&1 || npx eslint --fix . 2>&1 || true');
91
- if (fixResult.success) {
92
- log.success('ESLint errors resolved');
93
- return true;
94
- }
95
- }
96
- return false;
97
- }
98
7
 
99
- /**
100
- * Fix test failures
101
- */
102
- function fixTestFailures() {
103
- log.info('Running tests to identify failures...');
104
- const result = runCommand('npm test 2>&1 || true');
105
-
106
- if (!result.success || result.output.includes('failed') || result.output.includes('FAIL')) {
107
- log.warn('Test failures detected');
108
-
109
- // Check for specific failure patterns
110
- const patterns = [
111
- { pattern: /console\.(log|warn|error)/, fix: 'Run lint:fix to remove console statements' },
112
- { pattern: /any\s*\|/, fix: 'Replace any types with proper interfaces' },
113
- { pattern: /undefined/, fix: 'Add null checks or default values' },
114
- ];
115
-
116
- for (const { pattern, fix } of patterns) {
117
- if (pattern.test(result.output)) {
118
- log.info(`Fix suggestion: ${fix}`);
119
- }
120
- }
121
- return false;
122
- }
123
-
124
- log.success('All tests passing');
125
- return true;
126
- }
127
-
128
- /**
129
- * Fix missing dependencies
130
- */
131
- function fixDependencies() {
132
- log.info('Checking dependencies...');
133
-
134
- const result = runCommand('npm ci 2>&1 || npm install 2>&1');
135
- if (result.success) {
136
- log.success('Dependencies up to date');
137
- return true;
138
- }
139
-
140
- log.error('Dependency issues may require manual intervention');
141
- return false;
142
- }
143
-
144
- /**
145
- * Fix Git hooks
146
- */
147
- function fixGitHooks() {
148
- log.info('Setting up Git hooks...');
149
- const result = runCommand('npm run prepare 2>&1 || npx husky install 2>&1 || true');
150
-
151
- if (result.success) {
152
- log.success('Git hooks configured');
153
- return true;
154
- }
155
- return false;
156
- }
157
-
158
- /**
159
- * Fix consumer path issues
160
- */
161
- function fixConsumerPaths() {
162
- log.info('Checking consumer path configuration...');
163
-
164
- const prepareResult = runCommand('npm run prepare-consumer 2>&1 || true');
165
- if (prepareResult.output.includes('No development paths')) {
166
- log.success('Paths already correct');
167
- return true;
168
- }
169
-
170
- // Run prepare-consumer to fix paths
171
- const fixResult = runCommand('npm run prepare-consumer 2>&1');
172
- if (fixResult.success || fixResult.output.includes('Complete')) {
173
- log.success('Consumer paths fixed');
174
- return true;
175
- }
176
-
177
- return false;
178
- }
179
-
180
- /**
181
- * Generate health report
182
- */
183
- function generateHealthReport() {
184
- const report = {
185
- timestamp: new Date().toISOString(),
186
- version: require(path.join(rootDir, 'package.json')).version,
187
- status: 'healthy',
188
- fixes_applied: [],
189
- issues_remaining: [],
190
- };
191
-
192
- const logsDir = path.join(rootDir, '.opencode', 'logs');
193
- if (!fs.existsSync(logsDir)) {
194
- fs.mkdirSync(logsDir, { recursive: true });
195
- }
196
-
197
- const reportPath = path.join(logsDir, 'ci-cd-monitor-report.json');
198
- fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
199
-
200
- log.info(`Health report generated: ${reportPath}`);
201
- }
202
-
203
- /**
204
- * Main auto-fix routine
205
- */
206
- async function autoFix() {
207
- log.info('Starting CI/CD Auto-Fix...');
208
- log.info(`Max iterations: ${MAX_ITERATIONS}`);
209
- console.log('');
210
-
211
- let fixed = false;
212
- const fixes = [];
213
-
214
- // Run all fix routines
215
- const routines = [
216
- { name: 'Dependencies', fn: fixDependencies },
217
- { name: 'Consumer Paths', fn: fixConsumerPaths },
218
- { name: 'Git Hooks', fn: fixGitHooks },
219
- { name: 'TypeScript', fn: fixTypeErrors },
220
- { name: 'ESLint', fn: fixLintErrors },
221
- { name: 'Tests', fn: fixTestFailures },
222
- ];
223
-
224
- for (const { name, fn } of routines) {
225
- console.log('');
226
- log.info(`Running: ${name}...`);
227
- try {
228
- if (fn()) {
229
- fixes.push(name);
230
- fixed = true;
231
- }
232
- } catch (error) {
233
- log.error(`${name} failed: ${error.message}`);
234
- }
235
- }
236
-
237
- console.log('');
238
- console.log('══════════════════════════════════════════════════');
239
-
240
- if (fixed) {
241
- log.success(`CI/CD Auto-Fix Complete! ${fixes.length} issue(s) resolved.`);
242
- console.log('');
243
- console.log('Fixes applied:');
244
- fixes.forEach((fix, i) => console.log(` ${i + 1}. ${fix}`));
245
- } else {
246
- log.warn('No issues resolved (or none detected)');
247
- }
248
-
249
- // Generate health report
250
- generateHealthReport();
251
-
252
- console.log('══════════════════════════════════════════════════');
253
-
254
- return fixed;
255
- }
8
+ const { spawnSync } = require("child_process");
9
+ const path = require("path");
256
10
 
257
- // Run auto-fix
258
- autoFix().then((fixed) => {
259
- process.exit(fixed ? 0 : 0); // Always exit 0 for now
260
- }).catch((error) => {
261
- log.error(`Auto-fix failed: ${error.message}`);
262
- process.exit(1);
11
+ const mill = path.join(__dirname, "../foundry/ci-monitor.mjs");
12
+ const root = process.env.FOUNDRY_ROOT || process.cwd();
13
+ const result = spawnSync(process.execPath, [mill, "--report", ...process.argv.slice(2)], {
14
+ stdio: "inherit",
15
+ cwd: root,
16
+ env: { ...process.env, FOUNDRY_ROOT: root },
263
17
  });
18
+ process.exit(result.status === null ? 1 : result.status);
@@ -208,6 +208,9 @@ function main() {
208
208
  const requiredFiles = [
209
209
  "scripts/node/install-bridges.cjs",
210
210
  "scripts/node/postinstall.cjs",
211
+ "scripts/foundry/mint-suit.cjs",
212
+ "scripts/foundry/hooks.mjs",
213
+ "scripts/foundry/plant/skills/mill/SKILL.md",
211
214
  "package.json",
212
215
  "xray/features.json",
213
216
  "xray/features.schema.json",
@@ -221,6 +224,20 @@ function main() {
221
224
  console.log(` ✅ ${rel}`);
222
225
  }
223
226
 
227
+ const millSkillHanger = path.join(tmpRoot, ".opencode", "skills", "mill", "SKILL.md");
228
+ if (!fs.existsSync(millSkillHanger)) {
229
+ throw new Error("empty garment missing mill plant skill on hanger");
230
+ }
231
+ const hangerSkills = path.join(tmpRoot, ".opencode", "skills");
232
+ const hangerNames = fs.existsSync(hangerSkills)
233
+ ? fs.readdirSync(hangerSkills).filter((n) =>
234
+ fs.existsSync(path.join(hangerSkills, n, "SKILL.md")),
235
+ )
236
+ : [];
237
+ if (hangerNames.length > 3) {
238
+ throw new Error(`costume dump on empty garment: ${hangerNames.join(",")}`);
239
+ }
240
+
224
241
  const installedPkg = JSON.parse(fs.readFileSync(path.join(nmRoot, "package.json"), "utf-8"));
225
242
  if (installedPkg.version !== version) {
226
243
  throw new Error(`Installed version ${installedPkg.version} != packed ${version}`);
@@ -1,21 +1,6 @@
1
1
  #!/bin/bash
2
-
3
- # Version Compliance Enforcement Shell Wrapper
4
- #
5
- # This shell script wraps the TypeScript version compliance enforcement.
6
- # It provides the CLI interface expected by CI/CD pipelines.
7
- #
8
- # Usage: ./scripts/node/enforce-version-compliance.sh
9
- #
10
- # @since 2026-01-15
11
-
12
- set -e
13
-
14
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
- PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
16
-
17
- cd "$PROJECT_ROOT"
18
-
19
- # Use npx tsx to run TypeScript files directly
20
- # tsx is a zero-config TypeScript execution engine
21
- npx tsx "$SCRIPT_DIR/enforce-version-compliance.ts"
2
+ # Foundry mill: docs + version SSOT. Not UVM.
3
+ set -euo pipefail
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5
+ cd "$ROOT"
6
+ exec node scripts/foundry/validate-release-docs.mjs "$@"
@@ -1,116 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
+ * Foundry mill wrapper. Version SSOT is package.json (reconcile-version).
4
+ * Prose is verified by validate-release-docs — not rewritten here.
3
5
  */
4
-
5
- import path from 'path';
6
- import { fileURLToPath } from 'url';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
-
11
- // Colors for terminal output
12
- const RED = '\x1b[0;31m';
13
- const GREEN = '\x1b[0;32m';
14
- const YELLOW = '\x1b[1;33m';
15
- const BLUE = '\x1b[0;34m';
16
- const NC = '\x1b[0m';
17
-
18
- async function main() {
19
- console.log(`${BLUE}🔍 ENFORCER AGENT: Version Compliance Check${NC}`);
20
- console.log(`${BLUE}==============================================${NC}`);
21
- console.log('');
22
-
23
- try {
24
- // Import and run the processor - use file URL for ESM compatibility
25
- // __dirname is the script's directory (scripts/node/), go up 2 levels to project root
26
- const scriptDir = path.join(__dirname);
27
- const projectRoot = path.join(scriptDir, '..', '..');
28
- const processorPath = path.join(projectRoot, 'dist', 'processors', 'version-compliance-processor.js');
29
- const { VersionComplianceProcessor } = await import(`file://${processorPath}`);
30
- const processor = new VersionComplianceProcessor(projectRoot);
31
-
32
- const result = await processor.validateVersionCompliance();
33
-
34
- // Display results
35
- console.log(`${BLUE}📊 Version Audit:${NC}`);
36
- console.log(` NPM Published: ${BLUE}${result.npmVersion}${NC}`);
37
- console.log(` package.json: ${BLUE}${result.pkgVersion}${NC}`);
38
- console.log(` Version Manager: ${BLUE}${result.uvmVersion}${NC}`);
39
- console.log('');
40
-
41
- // Rule 1: UVM 1 ahead of NPM
42
- console.log('1️⃣ Checking: Version Manager 1 Ahead Rule');
43
- if (result.npmVersion !== 'NOT_PUBLISHED' && result.npmVersion !== 'ERROR') {
44
- const expectedUvm = incrementPatch(result.npmVersion);
45
- if (result.uvmVersion === expectedUvm) {
46
- console.log(` ${GREEN}✅ PASS:${NC} Version manager is 1 ahead (${result.uvmVersion} > ${result.npmVersion})`);
47
- } else {
48
- console.log(` ${RED}❌ VIOLATION:${NC} Version manager not 1 ahead of npm`);
49
- console.log(` NPM: ${result.npmVersion}`);
50
- console.log(` UVM: ${result.uvmVersion}`);
51
- console.log(` Expected: ${expectedUvm}`);
52
- }
53
- } else {
54
- console.log(` ${YELLOW}⚠️ NPM version not available (first publish)${NC}`);
55
- }
56
- console.log('');
57
-
58
- // Rule 2: package.json sync
59
- console.log('2️⃣ Checking: package.json Synchronization');
60
- if (result.pkgVersion === result.uvmVersion) {
61
- console.log(` ${GREEN}✅ PASS:${NC} package.json matches UVM (${result.pkgVersion})`);
62
- } else {
63
- console.log(` ${YELLOW}⚠️ WARNING:${NC} package.json version mismatch`);
64
- console.log(` package.json: ${result.pkgVersion}`);
65
- console.log(` UVM: ${result.uvmVersion}`);
66
- console.log(` ℹ️ Run: npm version [patch|minor|major] to sync`);
67
- }
68
- console.log('');
69
-
70
- // Display errors and warnings summary
71
- console.log(`${BLUE}==============================================${NC}`);
72
-
73
- if (result.errors.length > 0) {
74
- console.log(`${RED}❌ COMPLIANCE CHECK FAILED${NC}`);
75
- console.log(` Errors: ${result.errors.length}`);
76
- console.log(` Warnings: ${result.warnings.length}`);
77
- console.log('');
78
- console.log(`${RED}🚫 BLOCKED: Cannot proceed with commit/publish${NC}`);
79
- console.log(' Violations:');
80
- result.errors.forEach(e => console.log(` ${RED}•${NC} ${e}`));
81
-
82
- if (result.fixes && result.fixes.length > 0) {
83
- console.log('');
84
- console.log(`${YELLOW}🔧 FIXES:${NC}`);
85
- result.fixes.forEach(f => {
86
- console.log(` ${YELLOW}•${NC} ${f.description}`);
87
- console.log(` Command: ${f.command}`);
88
- });
89
- }
90
-
91
- process.exit(1);
92
- } else {
93
- console.log(`${GREEN}✅ COMPLIANCE CHECK PASSED${NC}`);
94
- if (result.warnings.length > 0) {
95
- console.log(` Warnings: ${result.warnings.length} (non-blocking)`);
96
- result.warnings.forEach(w => console.log(` ${YELLOW}⚠️${NC} ${w}`));
97
- }
98
- console.log('');
99
- console.log(`${GREEN}🚀 APPROVED: Ready for commit/publish${NC}`);
100
- process.exit(0);
101
- }
102
-
103
- } catch (error) {
104
- console.error(`${RED}❌ Fatal error running version compliance:${NC}`, error);
105
- process.exit(1);
106
- }
107
- }
108
-
109
- // Helper to increment patch version
110
- function incrementPatch(version: string): string {
111
- const parts = version.split('.').map(Number);
112
- parts[2] = parts[2] + 1;
113
- return parts.join('.');
114
- }
115
-
116
- main();
6
+ import { spawnSync } from "node:child_process";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
11
+ const result = spawnSync(
12
+ process.execPath,
13
+ [path.join(root, "scripts/foundry/validate-release-docs.mjs"), ...process.argv.slice(2)],
14
+ { cwd: root, stdio: "inherit" },
15
+ );
16
+ process.exit(result.status === null ? 1 : result.status);
@@ -0,0 +1,19 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ import path from "node:path";
4
+
5
+ /** Run the mill script when this wrapper is the CLI entry. */
6
+ export function millMain(importMetaUrl, millBasename) {
7
+ const here = fileURLToPath(importMetaUrl);
8
+ if (!process.argv[1] || path.resolve(process.argv[1]) !== here) {
9
+ return;
10
+ }
11
+ const mill = path.resolve(path.dirname(here), "../foundry", millBasename);
12
+ const root = process.env.FOUNDRY_ROOT || process.cwd();
13
+ const result = spawnSync(process.execPath, [mill, ...process.argv.slice(2)], {
14
+ stdio: "inherit",
15
+ cwd: root,
16
+ env: { ...process.env, FOUNDRY_ROOT: root },
17
+ });
18
+ process.exit(result.status === null ? 1 : result.status);
19
+ }
@@ -1,23 +1,18 @@
1
1
  #!/usr/bin/env node
2
-
3
2
  /**
4
- * GitHub Actions Monitor
5
- *
6
- * For consumer projects without real GitHub Actions, this returns success.
7
- * In production, this would check actual GitHub Actions workflow status.
8
- *
9
- * Usage: node scripts/node/github-actions-monitor.cjs --commit <sha>
3
+ * Shim onto the mill CI monitor. Not PPE.
4
+ *
5
+ * node scripts/node/github-actions-monitor.cjs [--commit SHA] [--report]
10
6
  */
11
7
 
12
- const args = process.argv.slice(2);
13
- const commitIndex = args.indexOf('--commit');
14
- const commitSha = commitIndex >= 0 ? args[commitIndex + 1] : 'unknown';
15
-
16
- console.log(`Checking GitHub Actions status for commit: ${commitSha}`);
17
-
18
- // For consumer projects without GitHub Actions, always return success
19
- // Real CI environments should override this with actual GitHub API calls
20
- console.log('SUCCESS: All workflows passed');
21
- console.log('No failed jobs');
8
+ const { spawnSync } = require("child_process");
9
+ const path = require("path");
22
10
 
23
- process.exit(0);
11
+ const mill = path.join(__dirname, "../foundry/ci-monitor.mjs");
12
+ const root = process.env.FOUNDRY_ROOT || process.cwd();
13
+ const result = spawnSync(process.execPath, [mill, ...process.argv.slice(2)], {
14
+ stdio: "inherit",
15
+ cwd: root,
16
+ env: { ...process.env, FOUNDRY_ROOT: root },
17
+ });
18
+ process.exit(result.status === null ? 1 : result.status);
@@ -8,6 +8,7 @@ const fs = require("fs");
8
8
  const path = require("path");
9
9
  const os = require("os");
10
10
  const { execSync } = require("child_process");
11
+ const { wantsCostume } = require("../foundry/mint-suit.cjs");
11
12
  const {
12
13
  wireHermesBridge,
13
14
  wireOpencodeBridge,
@@ -92,6 +93,11 @@ function syncBuiltinSkills(targetSkillsDir, packageRoot) {
92
93
  return copied;
93
94
  }
94
95
 
96
+ function syncCostumeSkills(targetSkillsDir, packageRoot, targetDir) {
97
+ if (targetDir && !wantsCostume(targetDir)) return 0;
98
+ return syncBuiltinSkills(targetSkillsDir, packageRoot);
99
+ }
100
+
95
101
  function copyTree(src, dest, relPath = "") {
96
102
  if (!fs.existsSync(src)) return;
97
103
  if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
@@ -267,38 +273,46 @@ function writeGrokDiscoveredHooks(targetDir, hooksPath, log, label) {
267
273
  }
268
274
  }
269
275
 
276
+ function copyOpencodePlugin(packageRoot, opencodeDest, log) {
277
+ const pluginSource = path.join(packageRoot, "dist", "plugin", "xray-codex-injection.js");
278
+ const pluginDest = path.join(opencodeDest, "plugin", "xray-codex-injection.js");
279
+ if (!fs.existsSync(pluginSource)) return;
280
+ const pluginDestDir = path.dirname(pluginDest);
281
+ if (!fs.existsSync(pluginDestDir)) fs.mkdirSync(pluginDestDir, { recursive: true });
282
+ const shouldCopy =
283
+ !fs.existsSync(pluginDest) ||
284
+ fs.statSync(pluginSource).mtime > fs.statSync(pluginDest).mtime;
285
+ if (shouldCopy) {
286
+ fs.copyFileSync(pluginSource, pluginDest);
287
+ log("opencode-bridge", "plugin updated", "info");
288
+ }
289
+ }
290
+
270
291
  function installOpencodeBridge(targetDir, packageRoot, log) {
271
292
  const opencodeSource = path.join(packageRoot, ".opencode");
272
293
  const opencodeDest = path.join(targetDir, ".opencode");
273
- if (!fs.existsSync(opencodeSource)) {
274
- log("opencode-bridge", "skipped", "warn", { reason: ".opencode source missing" });
275
- return;
276
- }
294
+ const costume = wantsCostume(targetDir);
277
295
 
278
- if (!fs.existsSync(opencodeDest)) {
279
- copyTree(opencodeSource, opencodeDest);
280
- log("opencode-bridge", "copied .opencode tree", "info");
281
- } else {
282
- log("opencode-bridge", ".opencode exists — merging skills/plugin only", "info");
283
- }
284
-
285
- const pluginSource = path.join(packageRoot, "dist", "plugin", "xray-codex-injection.js");
286
- const pluginDest = path.join(opencodeDest, "plugin", "xray-codex-injection.js");
287
- if (fs.existsSync(pluginSource)) {
288
- const pluginDestDir = path.dirname(pluginDest);
289
- if (!fs.existsSync(pluginDestDir)) fs.mkdirSync(pluginDestDir, { recursive: true });
290
- const shouldCopy =
291
- !fs.existsSync(pluginDest) ||
292
- fs.statSync(pluginSource).mtime > fs.statSync(pluginDest).mtime;
293
- if (shouldCopy) {
294
- fs.copyFileSync(pluginSource, pluginDest);
295
- log("opencode-bridge", "plugin updated", "info");
296
+ if (costume) {
297
+ if (!fs.existsSync(opencodeSource)) {
298
+ log("opencode-bridge", "skipped", "warn", { reason: ".opencode source missing" });
299
+ return;
296
300
  }
301
+ if (!fs.existsSync(opencodeDest)) {
302
+ copyTree(opencodeSource, opencodeDest);
303
+ log("opencode-bridge", "copied .opencode tree", "info");
304
+ } else {
305
+ log("opencode-bridge", ".opencode exists — merging skills/plugin only", "info");
306
+ }
307
+ copyOpencodePlugin(packageRoot, opencodeDest, log);
308
+ const copied = syncCostumeSkills(path.join(opencodeDest, "skills"), packageRoot, targetDir);
309
+ if (copied > 0) log("opencode-bridge", `skills synced (${copied})`, "info", { path: ".opencode/skills/" });
310
+ } else {
311
+ log("opencode-bridge", "empty garment — plugin only, mill mill-fill mill plant", "info");
312
+ fs.mkdirSync(opencodeDest, { recursive: true });
313
+ copyOpencodePlugin(packageRoot, opencodeDest, log);
297
314
  }
298
315
 
299
- const copied = syncBuiltinSkills(path.join(opencodeDest, "skills"), packageRoot);
300
- if (copied > 0) log("opencode-bridge", `skills synced (${copied})`, "info", { path: ".opencode/skills/" });
301
-
302
316
  try {
303
317
  const count = wireOpencodeBridge(targetDir);
304
318
  log("opencode-bridge", `opencode.json mcp wired (${count} servers)`, "info");
@@ -376,14 +390,14 @@ function installGrokBridge(targetDir, packageRoot, log) {
376
390
  log("grok-bridge", "plugin copied", "info", { path: rel || dest });
377
391
  writePluginMcpJson(dest, targetDir, log, "grok-bridge");
378
392
  patchGrokHooks(dest, packageRoot, targetDir, log, "grok-bridge");
379
- const copied = syncBuiltinSkills(path.join(dest, "skills"), packageRoot);
393
+ const copied = syncCostumeSkills(path.join(dest, "skills"), packageRoot, targetDir);
380
394
  if (copied > 0) log("grok-bridge", `plugin skills synced (${copied})`, "info", { path: rel });
381
395
  }
382
396
  }
383
397
 
384
398
  if (!ephemeral) {
385
399
  const grokGlobalSkills = path.join(home, ".grok", "skills");
386
- const globalCopied = syncBuiltinSkills(grokGlobalSkills, packageRoot);
400
+ const globalCopied = syncCostumeSkills(grokGlobalSkills, packageRoot, targetDir);
387
401
  if (globalCopied > 0) {
388
402
  log("grok-bridge", `global skills synced (${globalCopied})`, "info", { path: "~/.grok/skills/" });
389
403
  }
@@ -421,7 +435,7 @@ function installHermesBridge(targetDir, packageRoot, log) {
421
435
 
422
436
  writePluginMcpJson(targetPluginDir, targetDir, log, "hermes-bridge");
423
437
 
424
- const copied = syncBuiltinSkills(path.join(targetPluginDir, "skills"), packageRoot);
438
+ const copied = syncCostumeSkills(path.join(targetPluginDir, "skills"), packageRoot, targetDir);
425
439
  if (copied > 0) log("hermes-bridge", `skills synced (${copied})`, "info");
426
440
 
427
441
  const rootMarker = path.join(targetPluginDir, "xray-consumer-root.txt");
@@ -473,7 +487,7 @@ function installOpenclawBridge(targetDir, packageRoot, log) {
473
487
  log("openclaw-bridge", "config created", "info", { path: ".xray/config/openclaw.json" });
474
488
  }
475
489
 
476
- const copied = syncBuiltinSkills(path.join(os.homedir(), ".openclaw", "skills"), packageRoot);
490
+ const copied = syncCostumeSkills(path.join(os.homedir(), ".openclaw", "skills"), packageRoot, targetDir);
477
491
  if (copied > 0) log("openclaw-bridge", `skills synced (${copied})`, "info", { path: "~/.openclaw/skills/" });
478
492
 
479
493
  try {