0xray 4.0.10 → 4.0.12

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 (85) hide show
  1. package/.grok-plugin/plugin.json +20 -0
  2. package/.xray/features.json +1 -1
  3. package/AGENTS-consumer.md +1 -1
  4. package/AGENTS.md +1 -1
  5. package/README.md +2 -2
  6. package/SKILLS.md +1 -1
  7. package/dist/AGENTS.md +1 -1
  8. package/dist/CHANGELOG.md +19 -0
  9. package/dist/README.md +2 -2
  10. package/dist/SKILLS.md +1 -1
  11. package/dist/governance/llm-governance-provider.js +20 -4
  12. package/dist/integrations/grok/grok-cli.d.ts +3 -0
  13. package/dist/integrations/grok/grok-cli.js +79 -73
  14. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  15. package/llms.txt +1 -1
  16. package/package.json +3 -2
  17. package/scripts/foundry/CHANGELOG.md +1 -1
  18. package/scripts/foundry/README.md +2 -2
  19. package/scripts/foundry/inspect.mjs +22 -6
  20. package/scripts/foundry/mint-suit.cjs +24 -0
  21. package/scripts/foundry/plant/skills/inspect/SKILL.md +1 -1
  22. package/scripts/node/install-bridges.cjs +39 -24
  23. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  24. package/xray/features.json +1 -1
  25. package/.opencode/agents/api-design.yml +0 -31
  26. package/.opencode/agents/architect.yml +0 -15
  27. package/.opencode/agents/architecture-patterns.yml +0 -14
  28. package/.opencode/agents/auto-format.yml +0 -14
  29. package/.opencode/agents/backend-engineer.yml +0 -12
  30. package/.opencode/agents/boot-orchestrator.yml +0 -14
  31. package/.opencode/agents/bug-triage-specialist.yml +0 -15
  32. package/.opencode/agents/code-analyzer.yml +0 -12
  33. package/.opencode/agents/code-reviewer.yml +0 -14
  34. package/.opencode/agents/content-creator.yml +0 -12
  35. package/.opencode/agents/database-engineer.yml +0 -12
  36. package/.opencode/agents/devops-engineer.yml +0 -12
  37. package/.opencode/agents/framework-compliance-audit.yml +0 -14
  38. package/.opencode/agents/frontend-engineer.yml +0 -12
  39. package/.opencode/agents/frontend-ui-ux-engineer.yml +0 -12
  40. package/.opencode/agents/git-workflow.yml +0 -15
  41. package/.opencode/agents/growth-strategist.yml +0 -12
  42. package/.opencode/agents/hermes-agent.yml +0 -12
  43. package/.opencode/agents/inference-improve.yml +0 -12
  44. package/.opencode/agents/lint.yml +0 -14
  45. package/.opencode/agents/log-monitor.yml +0 -12
  46. package/.opencode/agents/mobile-developer.yml +0 -12
  47. package/.opencode/agents/model-health-check.yml +0 -12
  48. package/.opencode/agents/multimodal-looker.yml +0 -12
  49. package/.opencode/agents/performance-analysis.yml +0 -12
  50. package/.opencode/agents/performance-engineer.yml +0 -12
  51. package/.opencode/agents/performance-optimization.yml +0 -12
  52. package/.opencode/agents/processor-pipeline.yml +0 -14
  53. package/.opencode/agents/project-analysis.yml +0 -12
  54. package/.opencode/agents/refactorer.yml +0 -12
  55. package/.opencode/agents/researcher.yml +0 -12
  56. package/.opencode/agents/security-auditor.yml +0 -12
  57. package/.opencode/agents/security-scan.yml +0 -12
  58. package/.opencode/agents/seo-consultant.yml +0 -12
  59. package/.opencode/agents/session-management.yml +0 -12
  60. package/.opencode/agents/state-manager.yml +0 -12
  61. package/.opencode/agents/storyteller.yml +0 -12
  62. package/.opencode/agents/strategist.yml +0 -12
  63. package/.opencode/agents/tech-writer.yml +0 -12
  64. package/.opencode/agents/testing-best-practices.yml +0 -12
  65. package/.opencode/agents/testing-lead.yml +0 -12
  66. package/.opencode/agents/ui-ux-design.yml +0 -12
  67. package/.opencode/codex.codex +0 -8
  68. package/.opencode/commands/auto-format.md +0 -99
  69. package/.opencode/commands/auto-summary-capture.md +0 -90
  70. package/.opencode/commands/dependency-audit.md +0 -184
  71. package/.opencode/commands/enforcer-daily-scan.md +0 -137
  72. package/.opencode/commands/framework-compliance-audit.md +0 -205
  73. package/.opencode/commands/interactive-validator.md +0 -75
  74. package/.opencode/commands/job-summary-logger.md +0 -68
  75. package/.opencode/commands/lint.md +0 -11
  76. package/.opencode/commands/mode-switch.md +0 -95
  77. package/.opencode/commands/model-health-check.md +0 -186
  78. package/.opencode/commands/performance-analysis.md +0 -144
  79. package/.opencode/commands/pre-commit-introspection.md +0 -185
  80. package/.opencode/commands/pre-commit-introspection.sh +0 -133
  81. package/.opencode/commands/security-scan.md +0 -157
  82. package/.opencode/commands/sisyphus-validation.md +0 -128
  83. package/.opencode/commands/summary-logger.md +0 -83
  84. package/.opencode/enforcer-config.json +0 -282
  85. package/.opencode/workflows/post-deployment-audit.yml +0 -123
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "0xray",
3
+ "version": "4.0.12",
4
+ "description": "0xRay suit for Grok, Hermes, OpenClaw, OpenCode: mill+inspect, Codex, 7 MCP servers. Costume 45/42 is opt-in foundry, not the default wear.",
5
+ "author": {
6
+ "name": "0xRay AI",
7
+ "url": "https://0xray.ai"
8
+ },
9
+ "homepage": "https://github.com/0xRayAI/xray",
10
+ "repository": "https://github.com/0xRayAI/xray",
11
+ "keywords": [
12
+ "0xray",
13
+ "mill",
14
+ "inspect",
15
+ "codex",
16
+ "mcp",
17
+ "suit"
18
+ ],
19
+ "mcpServers": ".mcp.json"
20
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./features.schema.json",
3
- "version": "4.0.10",
3
+ "version": "4.0.12",
4
4
  "description": "0xRay Framework - Unified Feature Configuration",
5
5
  "suit_temperament": {
6
6
  "profile": "auto",
@@ -51,7 +51,7 @@ Governance deliberation uses **code-review**, **security-audit** (via enforcer/s
51
51
  | `npx 0xray setup` | Symlinks, hook extras, Hermes skill sync |
52
52
  | `npx 0xray status` | Verify installation |
53
53
  | `npx 0xray opencode install` | OpenCode bridge (also runs on postinstall) |
54
- | `npx 0xray grok install` | Grok plugin + 7 MCPs servers + skill sync |
54
+ | `npx 0xray grok install` | Project `.grok/plugins/0xray` + 7 MCPs servers (machine plugin not last-wins) |
55
55
  | `npx 0xray hermes install` | Hermes plugin bridge |
56
56
  | `npx 0xray openclaw install` | OpenClaw config + skills |
57
57
  | `npx 0xray skill:install` | Install starter skills |
package/AGENTS.md CHANGED
@@ -47,7 +47,7 @@ Governance deliberation: **code-review**, **security-audit**, **researcher** wit
47
47
  3. `.gitignore.default` → `.gitignore` (if absent)
48
48
  4. `.xray/` config (`codex.json`, `features.json`, `config.json`) then overlay their plant
49
49
  5. `.mcp.json` (7 servers)
50
- 6. Four bridges: OpenCode, Grok, Hermes, OpenClaw
50
+ 6. Four bridges: OpenCode, Grok, Hermes, OpenClaw. Grok last-mile dest is project `.grok/plugins/0xray` (shared HOME does not last-wins clobber machine `~/.grok/plugins/0xray`)
51
51
  7. Optional git hooks
52
52
 
53
53
  **Shop plant:** `shop-extract`, `shop-witness`, `shop-pin` (groover-hangar) are first-class with mill plant. Extra shops: `foundry.json` `shopPlant`. Not `"costume": true`. `npx @0xray/foundry inspect --skip-live` is the receipt.
package/README.md CHANGED
@@ -35,7 +35,7 @@ Manual per-platform install (idempotent, same result as postinstall):
35
35
 
36
36
  ```bash
37
37
  npx 0xray opencode install
38
- npx 0xray grok install # 7 MCPs servers + dual skill sync (~/.grok/plugins + ~/.grok/skills)
38
+ npx 0xray grok install # 7 MCPs servers + project .grok/plugins/0xray (not machine last-wins)
39
39
  npx 0xray hermes install
40
40
  npx 0xray openclaw install
41
41
  npx 0xray skill:install # starter skills
@@ -185,7 +185,7 @@ Docs: [memory routing](docs-site/docs/guides/memory-routing.md) · [Repertoire](
185
185
  | Platform | Install | Postinstall behavior |
186
186
  |----------|---------|----------------------|
187
187
  | **OpenCode** | `npx 0xray opencode install` | Merges `opencode.json`, copies agent YML surfaces |
188
- | **Grok CLI / Build** | `npx 0xray grok install` | Plugin + `~/.grok/skills/` sync, 7 MCPs servers |
188
+ | **Grok CLI / Build** | `npx 0xray grok install` | Project `.grok/plugins/0xray` (shared HOME does not clobber machine plugin), 7 MCPs servers |
189
189
  | **Hermes Agent** | `npx 0xray hermes install` | `~/.hermes/plugins/xray-hermes`, consumer root marker |
190
190
  | **OpenClaw** | `npx 0xray openclaw install` | `.xray/config/openclaw.json`, skill sync |
191
191
 
package/SKILLS.md CHANGED
@@ -148,7 +148,7 @@ npx 0xray skill:install <source>
148
148
  | Source (framework) | `src/skills/<name>/SKILL.md` |
149
149
  | Published package | `node_modules/0xray/dist/skills/<name>/SKILL.md` |
150
150
  | OpenCode (after setup) | `.opencode/skills/<name>/SKILL.md` |
151
- | Grok | `~/.grok/skills/<name>/SKILL.md` + plugin dir |
151
+ | Grok | project `.grok/plugins/0xray/skills/<name>/SKILL.md` (machine `~/.grok/plugins/0xray` is not last-wins) |
152
152
  | Hermes / OpenClaw | Platform skill dirs (postinstall sync) |
153
153
 
154
154
  ## Related features (since 3.1)
package/dist/AGENTS.md CHANGED
@@ -47,7 +47,7 @@ Governance deliberation: **code-review**, **security-audit**, **researcher** wit
47
47
  3. `.gitignore.default` → `.gitignore` (if absent)
48
48
  4. `.xray/` config (`codex.json`, `features.json`, `config.json`) then overlay their plant
49
49
  5. `.mcp.json` (7 servers)
50
- 6. Four bridges: OpenCode, Grok, Hermes, OpenClaw
50
+ 6. Four bridges: OpenCode, Grok, Hermes, OpenClaw. Grok last-mile dest is project `.grok/plugins/0xray` (shared HOME does not last-wins clobber machine `~/.grok/plugins/0xray`)
51
51
  7. Optional git hooks
52
52
 
53
53
  **Shop plant:** `shop-extract`, `shop-witness`, `shop-pin` (groover-hangar) are first-class with mill plant. Extra shops: `foundry.json` `shopPlant`. Not `"costume": true`. `npx @0xray/foundry inspect --skip-live` is the receipt.
package/dist/CHANGELOG.md CHANGED
@@ -12,6 +12,9 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
12
12
  - Pack → tmp suit proof: `npm run pack:tmp-proof` (and consumer smoke) installs the packed tgz, runs `foundry mint --skip-live`, plants factory hangar shops, and inspects without costume dump. Playwright n/a (mill CLI).
13
13
 
14
14
  ### 🐛 Bug Fixes
15
+ - **Grok multi-seat wear** — last-mile dest is project `.grok/plugins/0xray`. Shared HOME (passwd `$HOME`) no longer writes machine `~/.grok/plugins/0xray`, so two seats (forge/critic/herald/blinky) cannot last-wins clobber each other's `XRAY_ROOT`. Isolated HOME may wear `$HOME/.grok/plugins/0xray` and still must not write the passwd machine plugin. Inspect reports project dest. Not a 45-skill dump.
16
+ - **Grok marketplace pack** — `package.json` `files` includes `.grok-plugin/` so `npm pack` ships the mill-default Grok manifest (`package/.grok-plugin/plugin.json`). Consumers no longer receive only stale `.plugin/plugin.json`.
17
+ - **Grok marketplace manifest** — `.grok-plugin/plugin.json` matches `4.0.10`: `author` is an object, `mcpServers` is a flat path (not `components.mcpServers`), description is mill+inspect default wear (not a 45 knowledge-skills claim). Fixes `grok plugin validate`.
15
18
  - **Consumer install of packed `0xray`** — stop shipping `@0xray/repertoire` as `file:./vendor/@0xray/repertoire`. npm `--install-links`, pnpm, and yarn resolve that path from the **consumer** root (`ENOENT .../vendor/@0xray/repertoire/package.json`), not from `node_modules/0xray`. The organ stays in the tarball at `vendor/@0xray/repertoire/`; postinstall wears it into `node_modules/@0xray/repertoire`. Fresh `npm install 0xray` / `npm install 0xray-*.tgz` no longer needs a consumer-root `vendor/` tree. Mill plant (`mill`+`inspect`) unchanged.
16
19
 
17
20
  ### 🔧 Maintenance
@@ -42,6 +45,22 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
42
45
 
43
46
  ---
44
47
 
48
+ ## [4.0.12] - 2026-09-12
49
+
50
+ Public npm is still `0xray@4.0.11`, which predates #29. Isolation cannot be proven from `main` alone — bots dogfood the registry. This patch PUT wears project-scoped Grok plugin dest so multi-seat wear does not last-wins clobber machine `~/.grok/plugins/0xray`. Shared HOME writes project `.grok/plugins/0xray` only. Isolated HOME may wear `$HOME/.grok/plugins/0xray` and still must not write the passwd machine plugin. Mill+inspect default. Nested mill stays **0.1.9**. Not 5.0.0. This PR does not publish.
51
+
52
+ - fix(grok): project-scoped plugin dest so multi-seat wear does not last-wins clobber machine ~/.grok (747816635)
53
+
54
+ ---
55
+
56
+ ## [4.0.11] - 2026-09-12
57
+
58
+ npm pack now includes `.grok-plugin/` so the mill-default Grok marketplace manifest ships in the tarball (`package/.grok-plugin/plugin.json`). Published `0xray@4.0.10` omitted it — the `files` whitelist landed in #27 after that PUT. Not 5.0.0. This PR does not publish.
59
+
60
+ - fix(pack): ship .grok-plugin in npm files whitelist (5a65851cb)
61
+
62
+ ---
63
+
45
64
  ## [4.0.10] - 2026-09-12
46
65
 
47
66
  ### 🔄 Changes
package/dist/README.md CHANGED
@@ -35,7 +35,7 @@ Manual per-platform install (idempotent, same result as postinstall):
35
35
 
36
36
  ```bash
37
37
  npx 0xray opencode install
38
- npx 0xray grok install # 7 MCPs servers + dual skill sync (~/.grok/plugins + ~/.grok/skills)
38
+ npx 0xray grok install # 7 MCPs servers + project .grok/plugins/0xray (not machine last-wins)
39
39
  npx 0xray hermes install
40
40
  npx 0xray openclaw install
41
41
  npx 0xray skill:install # starter skills
@@ -185,7 +185,7 @@ Docs: [memory routing](docs-site/docs/guides/memory-routing.md) · [Repertoire](
185
185
  | Platform | Install | Postinstall behavior |
186
186
  |----------|---------|----------------------|
187
187
  | **OpenCode** | `npx 0xray opencode install` | Merges `opencode.json`, copies agent YML surfaces |
188
- | **Grok CLI / Build** | `npx 0xray grok install` | Plugin + `~/.grok/skills/` sync, 7 MCPs servers |
188
+ | **Grok CLI / Build** | `npx 0xray grok install` | Project `.grok/plugins/0xray` (shared HOME does not clobber machine plugin), 7 MCPs servers |
189
189
  | **Hermes Agent** | `npx 0xray hermes install` | `~/.hermes/plugins/xray-hermes`, consumer root marker |
190
190
  | **OpenClaw** | `npx 0xray openclaw install` | `.xray/config/openclaw.json`, skill sync |
191
191
 
package/dist/SKILLS.md CHANGED
@@ -148,7 +148,7 @@ npx 0xray skill:install <source>
148
148
  | Source (framework) | `src/skills/<name>/SKILL.md` |
149
149
  | Published package | `node_modules/0xray/dist/skills/<name>/SKILL.md` |
150
150
  | OpenCode (after setup) | `.opencode/skills/<name>/SKILL.md` |
151
- | Grok | `~/.grok/skills/<name>/SKILL.md` + plugin dir |
151
+ | Grok | project `.grok/plugins/0xray/skills/<name>/SKILL.md` (machine `~/.grok/plugins/0xray` is not last-wins) |
152
152
  | Hermes / OpenClaw | Platform skill dirs (postinstall sync) |
153
153
 
154
154
  ## Related features (since 3.1)
@@ -3,6 +3,7 @@ import { frameworkLogger } from "../core/framework-logger.js";
3
3
  const DEFAULT_HERMES_PROVIDER = "xai-oauth";
4
4
  const DEFAULT_HERMES_MODEL = "grok-4.3";
5
5
  const DEFAULT_HERMES_TIMEOUT_MS = 120_000;
6
+ const UNIT_TEST_HERMES_TIMEOUT_MS = 2_000;
6
7
  const ROLE_PROMPTS = {
7
8
  "code-review": `You are a senior code reviewer on a governance committee for a software project. Analyze the following proposal from a code quality, maintainability, readability, and engineering best practices perspective.
8
9
 
@@ -47,12 +48,25 @@ function resolveHermesProvider() {
47
48
  function resolveHermesModel() {
48
49
  return process.env.HERMES_MODEL?.trim() || DEFAULT_HERMES_MODEL;
49
50
  }
51
+ function inUnitTestContext() {
52
+ return process.env.NODE_ENV === "test" || Boolean(process.env.VITEST);
53
+ }
54
+ /** Dead xai-oauth (invalid_grant) hangs hermes -z until DEFAULT_HERMES_TIMEOUT_MS. Unit/Vitest must not wait. */
55
+ function unitTestBlocksLiveHermes() {
56
+ if (process.env.XRAY_GOVERNANCE_ALLOW_HERMES === "true")
57
+ return false;
58
+ return inUnitTestContext();
59
+ }
50
60
  function resolveHermesTimeoutMs() {
51
61
  const raw = process.env.HERMES_TIMEOUT_MS?.trim();
52
- if (!raw)
53
- return DEFAULT_HERMES_TIMEOUT_MS;
54
- const parsed = Number(raw);
55
- return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_HERMES_TIMEOUT_MS;
62
+ if (raw) {
63
+ const parsed = Number(raw);
64
+ if (Number.isFinite(parsed) && parsed > 0)
65
+ return parsed;
66
+ }
67
+ if (inUnitTestContext())
68
+ return UNIT_TEST_HERMES_TIMEOUT_MS;
69
+ return DEFAULT_HERMES_TIMEOUT_MS;
56
70
  }
57
71
  export function resolveHermesBin() {
58
72
  const explicit = process.env.HERMES_BIN?.trim();
@@ -84,6 +98,8 @@ function getConfig() {
84
98
  const direct = getDirectLlmConfig();
85
99
  if (direct)
86
100
  return direct;
101
+ if (unitTestBlocksLiveHermes())
102
+ return null;
87
103
  if (hermesCliAvailable()) {
88
104
  return {
89
105
  mode: "hermes",
@@ -17,6 +17,9 @@
17
17
  export interface GrokInstallOptions {
18
18
  force?: boolean;
19
19
  dryRun?: boolean;
20
+ env?: NodeJS.ProcessEnv;
21
+ machineHome?: string;
22
+ targetDir?: string;
20
23
  }
21
24
  export declare function installForGrokCLI(options?: GrokInstallOptions): Promise<void>;
22
25
  /** Grok TUI reads <project>/.grok/config.toml, not the package copy. */
@@ -49,18 +49,23 @@ function registerGrokMcpServers(targetDir) {
49
49
  }
50
50
  }
51
51
  export async function installForGrokCLI(options = {}) {
52
- frameworkLogger.log('grok-integration', 'install-start', 'info', { options });
52
+ frameworkLogger.log('grok-integration', 'install-start', 'info', {
53
+ options: { force: options.force, dryRun: options.dryRun },
54
+ });
53
55
  const millSuit = requireCjs(path.join(packageRoot, 'scripts/foundry/mint-suit.cjs'));
54
- const home = process.env.HOME || process.env.USERPROFILE || '';
55
- const machine = millSuit.machineHome();
56
- const targetPluginDir = path.join(home, '.grok/plugins/0xray');
57
- if (millSuit.wouldClobberMachineGrok(targetPluginDir, process.env, machine)) {
58
- frameworkLogger.log('grok-integration', 'refuse-machine-grok-clobber', 'error', {
59
- home,
60
- machineHome: machine,
61
- targetPluginDir,
62
- });
63
- throw new Error('foundry-inspect: isolated HOME must not clobber ~/.grok/plugins/0xray');
56
+ const env = options.env || process.env;
57
+ const machine = options.machineHome || millSuit.machineHome();
58
+ const targetDir = options.targetDir || resolveConsumerTargetDir(packageRoot, process.cwd());
59
+ const dests = millSuit.resolveGrokPluginDests(targetDir, env, machine);
60
+ for (const dest of dests) {
61
+ if (millSuit.wouldClobberMachineGrok(dest, env, machine)) {
62
+ frameworkLogger.log('grok-integration', 'refuse-machine-grok-clobber', 'error', {
63
+ home: millSuit.processHome(env),
64
+ machineHome: machine,
65
+ dest,
66
+ });
67
+ throw new Error('foundry-inspect: isolated HOME must not clobber ~/.grok/plugins/0xray');
68
+ }
64
69
  }
65
70
  // Try to find the plugin source from the installed package
66
71
  const possibleSources = [
@@ -73,58 +78,57 @@ export async function installForGrokCLI(options = {}) {
73
78
  return;
74
79
  }
75
80
  if (options.dryRun) {
76
- console.log(`[Grok] Dry run: Would copy plugin from ${sourceDir} → ${targetPluginDir}`);
81
+ console.log(`[Grok] Dry run: Would copy plugin from ${sourceDir} → ${dests.join(' , ')}`);
77
82
  return;
78
83
  }
79
- const targetDir = resolveConsumerTargetDir(packageRoot, process.cwd());
84
+ const isolated = millSuit.isIsolatedHome(env, machine);
80
85
  try {
81
- const pluginExists = fs.existsSync(targetPluginDir);
82
- if (pluginExists && !options.force) {
83
- console.log('[Grok] 0xray Grok plugin is already installed.');
84
- console.log('Use --force to reinstall plugin files.');
85
- }
86
- else {
87
- fs.cpSync(sourceDir, targetPluginDir, { recursive: true, force: true });
88
- frameworkLogger.log('grok-integration', 'plugin-copied', 'info', { destination: targetPluginDir });
89
- console.log(`\x1b[32m✓ Copied Grok plugin to ${targetPluginDir}\x1b[0m`);
90
- }
91
- // Sync builtin skills to Grok plugin skills dir
92
- const grokSkillsDir = path.join(targetPluginDir, 'skills');
93
- const skillsCopied = syncBuiltinSkills(grokSkillsDir);
94
- if (skillsCopied > 0) {
95
- console.log(`\x1b[32m✓ Synced ${skillsCopied} builtin skills to Grok plugin\x1b[0m`);
96
- }
97
- frameworkLogger.log('grok-integration', 'skills-synced', 'info', { count: skillsCopied });
98
- // Grok Build / Cursor also reads ~/.grok/skills/ for agent_skills
99
- const globalSkillsDir = path.join(home, '.grok', 'skills');
100
- const globalCopied = syncBuiltinSkills(globalSkillsDir);
101
- if (globalCopied > 0) {
102
- console.log(`\x1b[32m✓ Synced ${globalCopied} builtin skills to ~/.grok/skills/\x1b[0m`);
86
+ for (const dest of dests) {
87
+ const pluginExists = fs.existsSync(dest);
88
+ if (pluginExists && !options.force) {
89
+ console.log(`[Grok] 0xray Grok plugin is already installed at ${dest}.`);
90
+ console.log('Use --force to reinstall plugin files.');
91
+ }
92
+ else {
93
+ fs.cpSync(sourceDir, dest, { recursive: true, force: true });
94
+ frameworkLogger.log('grok-integration', 'plugin-copied', 'info', { destination: dest });
95
+ console.log(`\x1b[32m✓ Copied Grok plugin to ${dest}\x1b[0m`);
96
+ }
97
+ if (millSuit.wantsCostume(targetDir)) {
98
+ const skillsCopied = syncBuiltinSkills(path.join(dest, 'skills'));
99
+ if (skillsCopied > 0) {
100
+ console.log(`\x1b[32m✓ Synced ${skillsCopied} builtin skills to Grok plugin\x1b[0m`);
101
+ }
102
+ frameworkLogger.log('grok-integration', 'skills-synced', 'info', { count: skillsCopied });
103
+ }
104
+ pinGrokPluginToInstalledDist(dest, packageRoot, targetDir);
105
+ wearGrokHookCommands(dest, packageRoot, targetDir);
103
106
  }
104
- pinGrokPluginToInstalledDist(targetPluginDir, packageRoot, targetDir);
105
- wearGrokHookCommands(targetPluginDir, packageRoot, targetDir);
106
- const projectPluginDir = path.join(targetDir, '.grok', 'plugins', '0xray');
107
- if (projectPluginDir !== targetPluginDir) {
108
- if (!fs.existsSync(projectPluginDir) || options.force) {
109
- fs.cpSync(sourceDir, projectPluginDir, { recursive: true, force: true });
107
+ if (isolated && millSuit.wantsCostume(targetDir)) {
108
+ const home = millSuit.processHome(env);
109
+ const globalCopied = syncBuiltinSkills(path.join(home, '.grok', 'skills'));
110
+ if (globalCopied > 0) {
111
+ console.log(`\x1b[32m✓ Synced ${globalCopied} builtin skills to isolated ~/.grok/skills/\x1b[0m`);
110
112
  }
111
- pinGrokPluginToInstalledDist(projectPluginDir, packageRoot, targetDir);
112
- wearGrokHookCommands(projectPluginDir, packageRoot, targetDir);
113
113
  }
114
114
  writeProjectRepertoireMcp(targetDir);
115
115
  mintAfterWear(targetDir);
116
- // Attempt auto-trust (best effort)
116
+ const primary = dests[0];
117
117
  try {
118
- execSync(`grok plugins trust "${targetPluginDir}"`, { stdio: 'ignore' });
118
+ execSync(`grok plugins trust "${primary}"`, { stdio: 'ignore' });
119
119
  console.log('\x1b[32m✓ Auto-trusted the 0xray plugin with Grok CLI\x1b[0m');
120
120
  }
121
121
  catch {
122
122
  console.log('\nPlease run this command to fully trust the plugin:');
123
- console.log(` grok plugins trust "${targetPluginDir}"`);
123
+ console.log(` grok plugins trust "${primary}"`);
124
+ }
125
+ if (isolated) {
126
+ registerGrokMcpServers(targetDir);
127
+ console.log('\x1b[32m✓ Registered 7 xray MCP servers with Grok CLI (npx)\x1b[0m');
128
+ }
129
+ else {
130
+ console.log('\x1b[32m✓ Project-scoped Grok plugin worn (machine ~/.grok/plugins/0xray not written)\x1b[0m');
124
131
  }
125
- // Register MCP servers via grok mcp add (npx -y 0xray mcp — matches install-bridges)
126
- registerGrokMcpServers(targetDir);
127
- console.log('\x1b[32m✓ Registered 7 xray MCP servers with Grok CLI (npx)\x1b[0m');
128
132
  console.log('\n✅ 0xRay is now installed as a first-class Grok CLI plugin!');
129
133
  console.log('Restart Grok or run `grok` to load the new hooks and MCP servers.');
130
134
  }
@@ -150,38 +154,40 @@ function wearGrokHookCommands(pluginDir, xrayRoot, targetDir) {
150
154
  function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
151
155
  const hookJs = path.join(xrayRoot, 'dist/integrations/grok/hooks/pre-tool-use.js');
152
156
  const cliJs = path.join(xrayRoot, 'dist/cli/index.js');
153
- if (!fs.existsSync(hookJs))
154
- return;
155
157
  const hooksPath = path.join(pluginDir, 'hooks', 'hooks.json');
156
- if (fs.existsSync(hooksPath)) {
158
+ if (fs.existsSync(hookJs) && fs.existsSync(hooksPath)) {
157
159
  const text = fs.readFileSync(hooksPath, 'utf8');
158
160
  fs.writeFileSync(hooksPath, text.split('${XRAY_AI_PATH:-node_modules/0xray}').join(xrayRoot));
159
161
  }
160
162
  const mcpPath = path.join(pluginDir, '.mcp.json');
161
- if (fs.existsSync(mcpPath) && fs.existsSync(cliJs)) {
162
- const mcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
163
- for (const server of Object.values(mcp.mcpServers ?? {})) {
164
- if (server.command === 'npx' && Array.isArray(server.args) && server.args.includes('mcp')) {
165
- const mcpIdx = server.args.indexOf('mcp');
166
- const mcpCmd = server.args[mcpIdx + 1] ?? 'governance';
167
- server.command = 'node';
168
- server.args = [cliJs, 'mcp', mcpCmd];
169
- }
163
+ if (!fs.existsSync(mcpPath))
164
+ return;
165
+ const mcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
166
+ for (const server of Object.values(mcp.mcpServers ?? {})) {
167
+ server.env = { ...server.env, XRAY_ROOT: targetDir };
168
+ if (fs.existsSync(cliJs) &&
169
+ server.command === 'npx' &&
170
+ Array.isArray(server.args) &&
171
+ server.args.includes('mcp')) {
172
+ const mcpIdx = server.args.indexOf('mcp');
173
+ const mcpCmd = server.args[mcpIdx + 1] ?? 'governance';
174
+ server.command = 'node';
175
+ server.args = [cliJs, 'mcp', mcpCmd];
170
176
  }
171
- const repertoireMcp = resolveRepertoireMcp(targetDir);
172
- if (mcp.mcpServers) {
173
- if (repertoireMcp) {
174
- mcp.mcpServers.repertoire = {
175
- command: 'node',
176
- args: [repertoireMcp],
177
- };
178
- }
179
- else {
180
- delete mcp.mcpServers.repertoire;
181
- }
177
+ }
178
+ const repertoireMcp = resolveRepertoireMcp(targetDir);
179
+ if (mcp.mcpServers) {
180
+ if (repertoireMcp) {
181
+ mcp.mcpServers.repertoire = {
182
+ command: 'node',
183
+ args: [repertoireMcp],
184
+ };
185
+ }
186
+ else {
187
+ delete mcp.mcpServers.repertoire;
182
188
  }
183
- fs.writeFileSync(mcpPath, `${JSON.stringify(mcp, null, 2)}\n`);
184
189
  }
190
+ fs.writeFileSync(mcpPath, `${JSON.stringify(mcp, null, 2)}\n`);
185
191
  }
186
192
  /** Grok TUI reads <project>/.grok/config.toml, not the package copy. */
187
193
  export function writeProjectRepertoireMcp(projectRoot) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.10",
3
+ "version": "4.0.12",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
package/llms.txt CHANGED
@@ -8,7 +8,7 @@ Default consumer wear is **mill plant** (`mill` + `inspect`), not a 45-skill / 4
8
8
 
9
9
  ## Wear (what a stranger gets)
10
10
 
11
- - `npm install 0xray` — postinstall fastens mill+inspect, writes 7 MCP `.mcp.json`, four host bridges (OpenCode, Grok, Hermes, OpenClaw), overlays their plant. Does **not** write consumer-root `SKILLS.md`.
11
+ - `npm install 0xray` — postinstall fastens mill+inspect, writes 7 MCP `.mcp.json`, four host bridges (OpenCode, Grok, Hermes, OpenClaw), overlays their plant. Grok last-mile dest is project `.grok/plugins/0xray`. Shared HOME does not last-wins clobber machine `~/.grok/plugins/0xray`. Isolated HOME must not write the machine plugin. Does **not** write consumer-root `SKILLS.md`.
12
12
  - `npx @0xray/foundry mint --skip-live` — re-fasten mill plant, then inspect.
13
13
  - `npx @0xray/foundry inspect --skip-live` — plant-vs-worn, receipt, CI. Chat is not a receipt.
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.10",
3
+ "version": "4.0.12",
4
4
  "description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
5
5
  "readme": "README.md",
6
6
  "license": "MIT",
@@ -79,7 +79,7 @@
79
79
  "test:processors": "npm test -- src/__tests__/unit/processor-activation.test.ts src/__tests__/unit/typescript-compilation-processor.test.ts",
80
80
  "test:miscellaneous": "npm test -- src/__tests__/unit/blocked-test.test.ts",
81
81
  "test:quick": "npm test -- src/__tests__/integration/boot-orchestrator.integration.test.ts src/__tests__/unit/config-loader.test.ts src/__tests__/unit/state-manager.test.ts",
82
- "test:comprehensive": "npm run typecheck && npm run test:unit && npm run test:core-framework && npm run test:security && npm run test:performance && npm run test:session-management && npm run test:code-analysis && npm run test:processors && npm run test:miscellaneous",
82
+ "test:comprehensive": "npm run typecheck && npm run test:unit && npm run test:core-framework && npm run test:security && npm run test:performance && npm run test:session-management && npm run test:code-analysis && npm run test:processors && npm run test:miscellaneous && npm test -- src/__tests__/unit/llm-governance-provider.test.ts src/__tests__/unit/mcp-server-smoke.test.ts",
83
83
  "test:integration-all": "npm test -- src/__tests__/integration/",
84
84
  "test:performance-all": "npm test -- src/__tests__/performance/ src/__tests__/unit/performance-system.test.ts src/__tests__/unit/benchmark.test.ts src/__tests__/unit/analytics.test.ts",
85
85
  "test:agents-all": "echo 'Legacy agents test entry retired'",
@@ -180,6 +180,7 @@
180
180
  "src/skills/",
181
181
  "src/mcps/",
182
182
  ".plugin/",
183
+ ".grok-plugin/",
183
184
  ".opencode/agents/",
184
185
  ".opencode/commands/",
185
186
  ".opencode/hooks/",
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- Shop plant is first-class with mill plant. `shop-extract`, `shop-witness`, `shop-pin` (and `foundry.json` `shopPlant`) are not a costume dump. `"costume": true` still required for 45/42. No mill version bump.
5
+ Shop plant is first-class with mill plant. `shop-extract`, `shop-witness`, `shop-pin` (and `foundry.json` `shopPlant`) are not a costume dump. `"costume": true` still required for 45/42. Inspect isolated-home dest is project `.grok/plugins/0xray`. Shared HOME last-mile does not write machine `~/.grok/plugins/0xray`. Isolated HOME must not write the passwd machine plugin. No mill version bump.
6
6
 
7
7
  ## [0.1.9] - 2026-09-09
8
8
 
@@ -32,7 +32,7 @@ npx @0xray/foundry hooks
32
32
 
33
33
  `FOUNDRY_ROOT` overrides cwd (the repo being milled).
34
34
 
35
- `gate` is build + test + docs-check. `docs-build` runs Docusaurus on the 0xray exo (`docs-site/`); stranger mills skip. `inspect` runs the six mill checks (diff, plant vs worn, receipt, CI, live tarball GET, isolated HOME). Mint fails on a costume dump. Isolated HOME skips machine `~/.grok`. `ci` reports GitHub Actions (no auto-push). `hooks` installs git pre/post hooks. GitHub `0xRay CI/CD` is the mill gate on `main`. `Deploy Docs` is the mill Pages put.
35
+ `gate` is build + test + docs-check. `docs-build` runs Docusaurus on the 0xray exo (`docs-site/`); stranger mills skip. `inspect` runs the six mill checks (diff, plant vs worn, receipt, CI, live tarball GET, isolated HOME). Mint fails on a costume dump. Isolated HOME skips machine `~/.grok`. Shared HOME last-mile dest is project `.grok/plugins/0xray` (no last-wins machine plugin). `ci` reports GitHub Actions (no auto-push). `hooks` installs git pre/post hooks. GitHub `0xRay CI/CD` is the mill gate on `main`. `Deploy Docs` is the mill Pages put.
36
36
 
37
37
  `docs-check` and `gate` run **full** 0xRay corpus only when the milled `package.json` name is `0xray` and `docs-site/` exists. Otherwise **light**: `package.json` + CHANGELOG.
38
38
 
@@ -57,7 +57,7 @@ After mill plant is fastened, overlay their plant (defaults; remap in `foundry.j
57
57
 
58
58
  **Shop plant:** `shop-extract`, `shop-witness`, `shop-pin` are first-class with mill plant. Inspect does not treat those worn names as a costume dump. `foundry.json` `"shopPlant"` names more shops (array), a plant dir (string), or `{ "skills": [...], "dir": "..." }`. This is not `"costume": true` and does not copy 45/42.
59
59
 
60
- **4.0.9 mill target (0xray postinstall, not this mill package):** `install-bridges.cjs` does not mill npm global prefix or npx `_npx` as a consumer. Isolated HOME skips machine `~/.grok`. Nested mill stays **0.1.9**.
60
+ **4.0.9 mill target (0xray postinstall, not this mill package):** `install-bridges.cjs` does not mill npm global prefix or npx `_npx` as a consumer. Isolated HOME skips machine `~/.grok`. Shared HOME Grok last-mile is project `.grok/plugins/0xray` (two seats do not clobber machine plugin). Nested mill stays **0.1.9**.
61
61
 
62
62
  ## Publish this mill
63
63
 
@@ -42,6 +42,18 @@ export function wouldClobberMachineGrok(
42
42
  return mint.wouldClobberMachineGrok(dest, env, machine);
43
43
  }
44
44
 
45
+ export function projectGrokPluginDir(targetDir) {
46
+ return mint.projectGrokPluginDir(targetDir);
47
+ }
48
+
49
+ export function resolveGrokPluginDests(
50
+ targetDir,
51
+ env = process.env,
52
+ machine = mint.machineHome(),
53
+ ) {
54
+ return mint.resolveGrokPluginDests(targetDir, env, machine);
55
+ }
56
+
45
57
  function readJson(file) {
46
58
  try {
47
59
  return JSON.parse(fs.readFileSync(file, "utf8"));
@@ -218,17 +230,20 @@ export function packagesToProbe(root) {
218
230
  });
219
231
  }
220
232
 
221
- function checkIsolatedHome(env = process.env, machine = mint.machineHome()) {
233
+ function checkIsolatedHome(root, env = process.env, machine = mint.machineHome()) {
222
234
  const isolated = isIsolatedHome(env, machine);
223
235
  const machinePlugin = machineGrokPluginDir(machine);
224
- const home = env.HOME || env.USERPROFILE || "";
225
- const dest = home ? path.join(home, ".grok", "plugins", "0xray") : "";
226
- const clobber = wouldClobberMachineGrok(dest, env, machine);
236
+ const dests = mint.resolveGrokPluginDests(root, env, machine);
237
+ const dest = dests[0] || null;
238
+ const clobber = dests.some((d) => wouldClobberMachineGrok(d, env, machine));
227
239
  if (clobber) {
228
240
  return {
229
241
  id: "isolated-home",
230
242
  ok: false,
231
243
  isolated,
244
+ dest,
245
+ dests,
246
+ machinePlugin,
232
247
  detail: `isolated HOME must not write ${machinePlugin}`,
233
248
  };
234
249
  }
@@ -237,7 +252,8 @@ function checkIsolatedHome(env = process.env, machine = mint.machineHome()) {
237
252
  ok: true,
238
253
  isolated,
239
254
  machinePlugin,
240
- dest: dest || null,
255
+ dest,
256
+ dests,
241
257
  };
242
258
  }
243
259
 
@@ -275,7 +291,7 @@ export async function inspectSuit(root, opts = {}) {
275
291
  }
276
292
  }
277
293
 
278
- checks.push(checkIsolatedHome(env, machineHome));
294
+ checks.push(checkIsolatedHome(root, env, machineHome));
279
295
 
280
296
  const failed = checks.filter((c) => c.ok === false);
281
297
  const receipt = checks.find((c) => c.id === "receipt") || {};
@@ -217,6 +217,10 @@ function machineGrokPluginDir(machine) {
217
217
  return path.join(machine || machineHome(), ".grok", "plugins", "0xray");
218
218
  }
219
219
 
220
+ function projectGrokPluginDir(targetDir) {
221
+ return path.join(targetDir, ".grok", "plugins", "0xray");
222
+ }
223
+
220
224
  function wouldClobberMachineGrok(dest, env, machine) {
221
225
  if (!dest) return false;
222
226
  const realMachine = machine || machineHome();
@@ -226,6 +230,24 @@ function wouldClobberMachineGrok(dest, env, machine) {
226
230
  return target === real || target.startsWith(`${real}${path.sep}`);
227
231
  }
228
232
 
233
+ /**
234
+ * Last-mile Grok plugin dests. Project floor always.
235
+ * Isolated HOME may also wear $HOME/.grok/plugins/0xray (not passwd machine).
236
+ * Shared HOME never writes machine ~/.grok/plugins/0xray (multi-seat last-wins).
237
+ */
238
+ function resolveGrokPluginDests(targetDir, env, machine) {
239
+ const realMachine = machine || machineHome();
240
+ const dests = [projectGrokPluginDir(targetDir)];
241
+ if (!isIsolatedHome(env, realMachine)) return dests;
242
+ const home = processHome(env);
243
+ if (!home) return dests;
244
+ const isolatedDest = path.join(home, ".grok", "plugins", "0xray");
245
+ if (wouldClobberMachineGrok(isolatedDest, env, realMachine)) return dests;
246
+ if (path.resolve(isolatedDest) === path.resolve(dests[0])) return dests;
247
+ dests.push(isolatedDest);
248
+ return dests;
249
+ }
250
+
229
251
  /**
230
252
  * Project skill dirs on every TUI floor. Create on fasten. Never machine home.
231
253
  * YML agents stay OpenCode (`.opencode/agents`).
@@ -578,5 +600,7 @@ module.exports = {
578
600
  processHome,
579
601
  isIsolatedHome,
580
602
  machineGrokPluginDir,
603
+ projectGrokPluginDir,
604
+ resolveGrokPluginDests,
581
605
  wouldClobberMachineGrok,
582
606
  };
@@ -16,7 +16,7 @@ Mill **does** these checks (`npx @0xray/foundry inspect`). Mint fails on a costu
16
16
  3. **Mill receipt.** `.xray/foundry-inventory.json` — mill name/version, `suit` (`fastened` / `overlay` / `costume`), `millPlant.skills` must include `mill` and `inspect`.
17
17
  4. **CI.** GitHub mill CI report (`.opencode/logs/ci-cd-monitor-report.json`), not a green check in chat. No token is a skip, not a pass invented.
18
18
  5. **Live PUT.** HTTP 200 on the npm `.tgz`. Metadata without a tarball is not shipped.
19
- 6. **Isolated HOME.** Last-mile must not clobber `~/.grok/plugins/0xray`. Mill compares `$HOME` to the passwd home (`os.userInfo().homedir`), not `os.homedir()` (which follows `$HOME`).
19
+ 6. **Isolated HOME.** Last-mile dest is project `.grok/plugins/0xray`. Isolated HOME may wear `$HOME/.grok/plugins/0xray` and must not write machine `~/.grok/plugins/0xray`. Shared HOME (passwd `$HOME`) does not last-wins clobber the machine plugin. Mill compares `$HOME` to the passwd home (`os.userInfo().homedir`), not `os.homedir()` (which follows `$HOME`).
20
20
 
21
21
  ## How to work
22
22