0xray 4.0.28 → 4.0.30
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/.gitignore.default +1 -0
- package/.grok-plugin/plugin.json +1 -1
- package/.xray/features.json +1 -1
- package/README.md +3 -1
- package/dist/.gitignore.default +1 -0
- package/dist/CHANGELOG.md +16 -0
- package/dist/README.md +3 -1
- package/dist/cli/index.js +52 -44
- package/dist/integrations/cursor/README.md +21 -2
- package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +61 -4
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/llms.txt +2 -0
- package/package.json +2 -2
- package/scripts/node/bridge-mcp-wiring.cjs +2 -1
- package/scripts/node/consumer-gitignore.cjs +1 -0
- package/scripts/node/consumer-install-smoke.mjs +9 -72
- package/scripts/node/install-bridges.cjs +1123 -14
- package/scripts/node/postinstall.cjs +6 -59
- package/scripts/node/setup.cjs +80 -71
- package/scripts/node/unwear-cursor-hooks.cjs +21 -0
- package/scripts/node/wear-cursor-hooks.cjs +17 -0
- package/scripts/node/wear-vendored-repertoire.cjs +35 -0
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/xray/features.json +1 -1
package/.gitignore.default
CHANGED
package/.grok-plugin/plugin.json
CHANGED
package/.xray/features.json
CHANGED
package/README.md
CHANGED
|
@@ -52,6 +52,8 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
|
|
|
52
52
|
| Version | Highlights |
|
|
53
53
|
|---------|------------|
|
|
54
54
|
| **Unreleased** | Fasten is **foundry-plant/0**: builtin plant is mill+inspect only. `foundry.json` `"plant": "@0xray/blip"` (aliases `"plant": "blip"` / `"plant": "sound"`) fastens whatever that mill declares (looker / vibe / mixer live in the mill). Nested mill `@0xray/foundry@0.1.12`. CLI shims `foundry blip\|sound` remain (0.1.11+). Shop plant first-class (`shop-extract`, `shop-witness`, `shop-pin` + `foundry.json` `shopPlant`). |
|
|
55
|
+
| **4.0.30** | Fixes 4.0.29: `npx 0xray wear` and `setup` work outside a git checkout again. They write the 4.0.28 project setup, skip only Cursor hooks, and print one warning. Inside a git checkout they restore the 4.0.28 project setup, and `wear` adds Cursor hooks plus the unwear snapshot. Companion `@0xray/grok-bot` 0.1.8. Released 2026-09-28. |
|
|
56
|
+
| **4.0.29** | `npm install` only creates the relative `node_modules/@0xray/repertoire` link to the vendored repertoire and prints `npx 0xray wear`. Run `npx 0xray wear` after install. Wear writes only the consumer project's `.cursor/hooks.json` (installed dist hooks, beside user entries); `npx 0xray unwear` restores the snapshot. Companion `@0xray/grok-bot` 0.1.8. Released 2026-09-28. |
|
|
55
57
|
| **4.0.28** | MCP servers start through `scripts/node/mcp-launch.cjs` and only see PATH, HOME, XRAY_ROOT, plus names on their keep list. Tokens such as NPM_TOKEN are not inherited. User env values stay out of argv. Postinstall keeps user edits to AGENTS.md, .mcp.json, and opencode.json. `house init` does not copy EXAMPLE.md; doctor FAILs while `house/EXAMPLE.md` exists. Inspect adds a machinePlugin check. The Cursor hook defaults XRAY_ROOT to the suit. Companion `@0xray/grok-bot` 0.1.7. Released 2026-09-27. |
|
|
56
58
|
| **4.0.27** | Host compact proof needs a generation id and a numeric usage percent, and the same preCompact receipt is mirrored to nested consumer wear roots. Memory scoring: [docs/memory-scoring.md](docs/memory-scoring.md) (orchestrator links it). Companion `@0xray/grok-bot` 0.1.6: general page vs `house/` overlay, `templates/house/`, `grok-bot house init`, doctor `GROK_BOT_HOUSE` or walk-up. Released 2026-09-26. |
|
|
57
59
|
| **4.0.9** | Mill target: postinstall does not mill npm global prefix or `_npx` as a consumer. Isolated HOME skips machine `~/.grok`. |
|
|
@@ -65,7 +67,7 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
|
|
|
65
67
|
|
|
66
68
|
### Consolidations
|
|
67
69
|
|
|
68
|
-
- **
|
|
70
|
+
- **Install, then wear, then setup where needed** — `npm install` runs `postinstall.cjs`, which writes nothing and prints one line: `npx 0xray wear`. `wear` and `unwear` set up or remove the Cursor suit. `npx 0xray setup` writes `.mcp.json` and the OpenCode, Grok, Hermes, and OpenClaw bridges (`install-bridges.cjs`); git hooks only with `--git-hooks`. Mill target is the consumer project, not npm global prefix or `_npx`.
|
|
69
71
|
- **7-server MCP surface** — `.mcp.json` SSOT; Grok plugin and all bridges share `XRAY_MCP_SERVERS`.
|
|
70
72
|
- **Dev vs consumer AGENTS** — `AGENTS.md` (framework) vs `AGENTS-consumer.md` (copied to consumer projects on install).
|
|
71
73
|
- **Release pipeline** — `npm run release:patch|minor|major` → reconcile → gate → artifacts → tag → publish.
|
package/dist/.gitignore.default
CHANGED
package/dist/CHANGELOG.md
CHANGED
|
@@ -41,6 +41,22 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
41
41
|
- Archive root `integrations/` costume SKILL dump → `docs/archive/integrations-costume/`. Live wear is `src/integrations`.
|
|
42
42
|
- Archive root `commands/` stale copy → `docs/archive/commands-copy/`. Live wear is `src/opencode/commands`.
|
|
43
43
|
|
|
44
|
+
## [4.0.30] - 2026-09-28
|
|
45
|
+
|
|
46
|
+
Next patch after npm `4.0.29`. Released 2026-09-28.
|
|
47
|
+
|
|
48
|
+
Fixes the 4.0.29 regression where `npx 0xray wear` and `npx 0xray setup` exited 1 with `cursor-wear: not a git work tree` outside a git checkout (#147). Outside a git checkout, both commands again write the 4.0.28 consumer project setup and skip only Cursor hooks, with one stderr line: `cursor-wear: hooks skipped because this folder is not a git checkout`. Inside a git checkout, `wear` and `setup` restore the 4.0.28 project setup that 4.0.29 had dropped: managed `AGENTS.md`, `.gitignore` entries, `.xray` config, the `node_modules/@0xray/repertoire` link, the mill/inspect plant, chat bridges, the `.opencode/skills` sync, and the `dist` / `scripts` links. `wear` then adds the Cursor hooks, including the six hook scripts, and the `.xray/state/cursor-hook-wear/` snapshot. Postinstall is unchanged: it links the vendored repertoire and prints `npx 0xray wear`. Companion package `@0xray/grok-bot` is unchanged at 0.1.8.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## [4.0.29] - 2026-09-28
|
|
53
|
+
|
|
54
|
+
Next patch after npm `4.0.28`. Released 2026-09-28.
|
|
55
|
+
|
|
56
|
+
`npm install` now only creates the relative `node_modules/@0xray/repertoire` link to the vendored repertoire and prints one line: `npx 0xray wear`. It no longer writes the suit. Run `npx 0xray wear` after install. Wear writes only the consumer project's `.cursor/hooks.json`, pointing at the installed package's dist hooks, and adds them beside existing user entries. It snapshots the pre-wear file under `.xray/state/cursor-hook-wear/`, and `npx 0xray unwear` restores it. `npx 0xray setup` writes `.mcp.json` and the OpenCode, Grok, Hermes, and OpenClaw bridges. Companion package `@0xray/grok-bot` is unchanged at 0.1.8.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
44
60
|
## [4.0.28] - 2026-09-26
|
|
45
61
|
|
|
46
62
|
Next patch after npm `4.0.27`. Released 2026-09-27.
|
package/dist/README.md
CHANGED
|
@@ -52,6 +52,8 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
|
|
|
52
52
|
| Version | Highlights |
|
|
53
53
|
|---------|------------|
|
|
54
54
|
| **Unreleased** | Fasten is **foundry-plant/0**: builtin plant is mill+inspect only. `foundry.json` `"plant": "@0xray/blip"` (aliases `"plant": "blip"` / `"plant": "sound"`) fastens whatever that mill declares (looker / vibe / mixer live in the mill). Nested mill `@0xray/foundry@0.1.12`. CLI shims `foundry blip\|sound` remain (0.1.11+). Shop plant first-class (`shop-extract`, `shop-witness`, `shop-pin` + `foundry.json` `shopPlant`). |
|
|
55
|
+
| **4.0.30** | Fixes 4.0.29: `npx 0xray wear` and `setup` work outside a git checkout again. They write the 4.0.28 project setup, skip only Cursor hooks, and print one warning. Inside a git checkout they restore the 4.0.28 project setup, and `wear` adds Cursor hooks plus the unwear snapshot. Companion `@0xray/grok-bot` 0.1.8. Released 2026-09-28. |
|
|
56
|
+
| **4.0.29** | `npm install` only creates the relative `node_modules/@0xray/repertoire` link to the vendored repertoire and prints `npx 0xray wear`. Run `npx 0xray wear` after install. Wear writes only the consumer project's `.cursor/hooks.json` (installed dist hooks, beside user entries); `npx 0xray unwear` restores the snapshot. Companion `@0xray/grok-bot` 0.1.8. Released 2026-09-28. |
|
|
55
57
|
| **4.0.28** | MCP servers start through `scripts/node/mcp-launch.cjs` and only see PATH, HOME, XRAY_ROOT, plus names on their keep list. Tokens such as NPM_TOKEN are not inherited. User env values stay out of argv. Postinstall keeps user edits to AGENTS.md, .mcp.json, and opencode.json. `house init` does not copy EXAMPLE.md; doctor FAILs while `house/EXAMPLE.md` exists. Inspect adds a machinePlugin check. The Cursor hook defaults XRAY_ROOT to the suit. Companion `@0xray/grok-bot` 0.1.7. Released 2026-09-27. |
|
|
56
58
|
| **4.0.27** | Host compact proof needs a generation id and a numeric usage percent, and the same preCompact receipt is mirrored to nested consumer wear roots. Memory scoring: [docs/memory-scoring.md](docs/memory-scoring.md) (orchestrator links it). Companion `@0xray/grok-bot` 0.1.6: general page vs `house/` overlay, `templates/house/`, `grok-bot house init`, doctor `GROK_BOT_HOUSE` or walk-up. Released 2026-09-26. |
|
|
57
59
|
| **4.0.9** | Mill target: postinstall does not mill npm global prefix or `_npx` as a consumer. Isolated HOME skips machine `~/.grok`. |
|
|
@@ -65,7 +67,7 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
|
|
|
65
67
|
|
|
66
68
|
### Consolidations
|
|
67
69
|
|
|
68
|
-
- **
|
|
70
|
+
- **Install, then wear, then setup where needed** — `npm install` runs `postinstall.cjs`, which writes nothing and prints one line: `npx 0xray wear`. `wear` and `unwear` set up or remove the Cursor suit. `npx 0xray setup` writes `.mcp.json` and the OpenCode, Grok, Hermes, and OpenClaw bridges (`install-bridges.cjs`); git hooks only with `--git-hooks`. Mill target is the consumer project, not npm global prefix or `_npx`.
|
|
69
71
|
- **7-server MCP surface** — `.mcp.json` SSOT; Grok plugin and all bridges share `XRAY_MCP_SERVERS`.
|
|
70
72
|
- **Dev vs consumer AGENTS** — `AGENTS.md` (framework) vs `AGENTS-consumer.md` (copied to consumer projects on install).
|
|
71
73
|
- **Release pipeline** — `npm run release:patch|minor|major` → reconcile → gate → artifacts → tag → publish.
|
package/dist/cli/index.js
CHANGED
|
@@ -45,52 +45,71 @@ program
|
|
|
45
45
|
.name("0xray")
|
|
46
46
|
.description("xray: Bulletproof AI orchestration with systematic error prevention")
|
|
47
47
|
.version(version);
|
|
48
|
-
function
|
|
49
|
-
const
|
|
50
|
-
validateScriptPath(
|
|
51
|
-
|
|
48
|
+
function runNodeScript(scriptName, args = []) {
|
|
49
|
+
const script = join(packageRoot, "scripts", "node", scriptName);
|
|
50
|
+
validateScriptPath(script, scriptName);
|
|
51
|
+
const quoted = [script, ...args].map((part) => `"${part}"`).join(" ");
|
|
52
|
+
execSync(`node ${quoted}`, { stdio: "inherit", cwd: process.cwd() });
|
|
52
53
|
}
|
|
53
54
|
program
|
|
54
55
|
.command("install")
|
|
55
56
|
.description("Install xray framework in the current project")
|
|
56
|
-
.action(
|
|
57
|
-
frameworkLogger.log('cli', 'install
|
|
57
|
+
.action(() => {
|
|
58
|
+
frameworkLogger.log('cli', 'install', 'info', { message: 'nothing written' });
|
|
59
|
+
process.stdout.write("nothing written\n");
|
|
60
|
+
});
|
|
61
|
+
program
|
|
62
|
+
.command("setup")
|
|
63
|
+
.description("Set up .mcp.json and the OpenCode, Grok, Hermes, and OpenClaw bridges")
|
|
64
|
+
.option("--git-hooks", "also install git hooks")
|
|
65
|
+
.action((opts) => {
|
|
66
|
+
const args = ["setup"];
|
|
67
|
+
if (opts.gitHooks)
|
|
68
|
+
args.push("--git-hooks");
|
|
58
69
|
try {
|
|
59
|
-
|
|
60
|
-
validateScriptPath(postinstallScript, "postinstall script");
|
|
61
|
-
execSync(`node "${postinstallScript}"`, { stdio: "inherit", cwd: process.cwd() });
|
|
62
|
-
frameworkLogger.log('cli', 'install-success', 'info', { message: 'xray framework installed' });
|
|
63
|
-
// UX banner kept for user visibility post-install (non-removable per exception)
|
|
64
|
-
console.log("✅ xray framework installed!");
|
|
65
|
-
console.log("💡 Run 'npx xray setup' for full configuration (hooks, Hermes, symlinks)");
|
|
70
|
+
runNodeScript("install-bridges.cjs", args);
|
|
66
71
|
}
|
|
67
72
|
catch (error) {
|
|
68
|
-
frameworkLogger.log(
|
|
69
|
-
|
|
73
|
+
frameworkLogger.log("cli", "setup-error", "error", {
|
|
74
|
+
error: error instanceof Error ? error.message : String(error),
|
|
75
|
+
});
|
|
70
76
|
process.exit(1);
|
|
71
77
|
}
|
|
72
78
|
});
|
|
73
79
|
program
|
|
74
|
-
.command("
|
|
75
|
-
.description("
|
|
76
|
-
.action(
|
|
80
|
+
.command("wear")
|
|
81
|
+
.description("Wear the 0xray suit in this git checkout")
|
|
82
|
+
.action(() => {
|
|
83
|
+
try {
|
|
84
|
+
runNodeScript("wear-cursor-hooks.cjs");
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
frameworkLogger.log('cli', 'wear-error', 'error', {
|
|
88
|
+
error: error instanceof Error ? error.message : String(error),
|
|
89
|
+
});
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
77
93
|
program
|
|
78
|
-
.command("
|
|
79
|
-
.description("
|
|
80
|
-
.action(
|
|
81
|
-
console.log("🚀 xray CLI: Initializing configuration...");
|
|
94
|
+
.command("unwear")
|
|
95
|
+
.description("Remove the 0xray suit and restore the pre-wear hooks file")
|
|
96
|
+
.action(() => {
|
|
82
97
|
try {
|
|
83
|
-
|
|
84
|
-
validateScriptPath(postinstallScript, "postinstall script");
|
|
85
|
-
execSync(`node "${postinstallScript}"`, { stdio: "inherit", cwd: process.cwd() });
|
|
86
|
-
runSetup();
|
|
87
|
-
console.log("✅ xray configuration initialized!");
|
|
98
|
+
runNodeScript("unwear-cursor-hooks.cjs");
|
|
88
99
|
}
|
|
89
100
|
catch (error) {
|
|
90
|
-
|
|
101
|
+
frameworkLogger.log('cli', 'unwear-error', 'error', {
|
|
102
|
+
error: error instanceof Error ? error.message : String(error),
|
|
103
|
+
});
|
|
91
104
|
process.exit(1);
|
|
92
105
|
}
|
|
93
106
|
});
|
|
107
|
+
program
|
|
108
|
+
.command("init")
|
|
109
|
+
.description("Initialize xray configuration in the current project")
|
|
110
|
+
.action(() => {
|
|
111
|
+
process.stdout.write("nothing written\n");
|
|
112
|
+
});
|
|
94
113
|
program
|
|
95
114
|
.command("status")
|
|
96
115
|
.description("Show comprehensive xray framework status")
|
|
@@ -341,22 +360,7 @@ program
|
|
|
341
360
|
console.log("===========================");
|
|
342
361
|
console.log("");
|
|
343
362
|
try {
|
|
344
|
-
|
|
345
|
-
// Run the postinstaller script (same as install command)
|
|
346
|
-
const postinstallScript = join(packageRoot, "scripts", "node", "postinstall.cjs");
|
|
347
|
-
// SECURITY: Validate script path before execution
|
|
348
|
-
validateScriptPath(postinstallScript, "postinstall script");
|
|
349
|
-
execSync(`node "${postinstallScript}"`, {
|
|
350
|
-
stdio: "inherit",
|
|
351
|
-
cwd: process.cwd(),
|
|
352
|
-
});
|
|
353
|
-
console.log("");
|
|
354
|
-
console.log("🎉 Framework configuration restored successfully!");
|
|
355
|
-
console.log("");
|
|
356
|
-
console.log("💡 Next steps:");
|
|
357
|
-
console.log(" • Restart OpenCode to load the restored configuration");
|
|
358
|
-
console.log(" • Run: npx xray health (to verify everything works)");
|
|
359
|
-
console.log(" • Try: @security-auditor scan this project");
|
|
363
|
+
process.stdout.write("nothing written\n");
|
|
360
364
|
}
|
|
361
365
|
catch (error) {
|
|
362
366
|
console.error("❌ Fix command failed:", error instanceof Error ? error.message : String(error));
|
|
@@ -451,6 +455,10 @@ program
|
|
|
451
455
|
}
|
|
452
456
|
// Check configuration - check for opencode.json or .xray/ (min compat .xray/ fallback for prior 0xRay consumer runtime per Scope Rule; plain xray primary)
|
|
453
457
|
const cwd = process.cwd();
|
|
458
|
+
const wearMeta = path.join(cwd, ".xray", "state", "cursor-hook-wear", "meta.json");
|
|
459
|
+
if (!fs.existsSync(wearMeta)) {
|
|
460
|
+
process.stdout.write("not worn\n");
|
|
461
|
+
}
|
|
454
462
|
const opencodeConfigPath = path.join(cwd, "opencode.json");
|
|
455
463
|
const xrayDir = getConfigDir(cwd);
|
|
456
464
|
const opencodeExists = fs.existsSync(opencodeConfigPath);
|
|
@@ -10,10 +10,25 @@ Repertoire is preferred (vendored, dest = named laws). Station + Codex gates sti
|
|
|
10
10
|
2. Cursor Cloud Agent clones the repo and loads **project** hooks. Desktop Agent Chat does the same when the folder is the workspace.
|
|
11
11
|
3. Hook names are camelCase: `preToolUse`, `preCompact`, `afterFileEdit`, plus Cloud `beforeShellExecution` / `beforeReadFile`. Command is a **relative** `.cursor/hooks/*.sh` — Cloud execs argv[0] without a shell. Do not put `XRAY_AI_PATH=` in `hooks.json`. Stdin is JSON; stdout is JSON.
|
|
12
12
|
4. First `preToolUse` or `afterFileEdit` writes `.xray/state/session-boot.json` + `.xray/state/STATION.md`. After a compact, **Read STATION.md**. The host does not inject it. A multi-repo Cloud wrapper (`/agent` with `repos/xray`) is not a dest heat root, even if a leftover wrapper card exists. Heat the mill under `repos/` and the Read path. Dest `EACCES` fail-opens — a wired hook must not deny every tool.
|
|
13
|
-
5. `install-bridges`
|
|
13
|
+
5. When the package has no dist hook scripts yet, `install-bridges` fastens the daemon workspace root if it can see `repos/xray` above the checkout. A consumer install that already has dist scripts writes only that consumer project directory. `ReloadAgentSkills` still runs so a mid-session wear can bind this daemon. Dogfood commands stay a relative `.cursor/hooks/*.sh`.
|
|
14
14
|
6. `preCompact` is observational (cannot block). Cloud **does** fire it ([hooks.md](https://cursor.com/docs/hooks.md) support matrix: Yes) once hooks are bound and the host actually compacts. Stdout is only `{ "user_message": "…" }` — that is a notice, not a spawn. The spawn id is stdin `conversation_id` (same `bc-`) plus Task's returned agent id. Resume that id. Do not cold-start a twin. Station merge keeps ticket / seed / Durable / unfinished keys.
|
|
15
15
|
|
|
16
|
-
Copy the consumer template from `src/integrations/cursor/hooks/hooks.json` and the sibling `.sh` runners.
|
|
16
|
+
Copy the consumer template from `src/integrations/cursor/hooks/hooks.json` and the sibling `.sh` runners. This exo repo's own `.cursor/hooks.json` stays on `.cursor/hooks/*.sh` so dogfood can run `src/integrations/cursor/hooks/*.js` before `npm run build`.
|
|
17
|
+
|
|
18
|
+
Consumer wear (`installCursorBridge` / `wearCursorHooks`, including `npm run wear` in a suited bench) writes only that consumer project's `.cursor/hooks.json`. It does not rewrite a parent checkout's committed hooks, `CURSOR_PROJECT_DIR`, or a `repos/xray` directory above the project. Those outer paths are written only when `wearCursorHooks` is called with `{ outerRoots: true }`, and each one is printed as `cursor-wear: wrote outer hooks at <path>`. The five shipped events (`preToolUse`, `preCompact`, `afterFileEdit`, `beforeShellExecution`, `beforeReadFile`) are added beside existing user entries, unless that event already runs `xray-cloud-hook.sh` (the committed `.cursor/hooks/*.sh` runners). A suited bench that already commits those runners is left byte for byte. The new command, when one is added, is a relative path to the installed package:
|
|
19
|
+
|
|
20
|
+
`node_modules/0xray/dist/integrations/cursor/hooks/<event>.sh`
|
|
21
|
+
|
|
22
|
+
Wear does not copy scripts into `.cursor/hooks/`. A user's `.cursor/hooks/<name>.sh` stays as it is, including when the name matches a shipped script, and wear adds the dist command beside it. A `hooks.json` that contains `//` comments is merged in place. A duplicate `hooks` key, or any other file that cannot be parsed safely, makes wear stop and write nothing.
|
|
23
|
+
|
|
24
|
+
Install the package first (`npm install`), then `npx 0xray wear`. Postinstall writes nothing. Wearing again writes the same bytes and takes a fresh snapshot every time. The pre-wear copy is `<project>/.xray/state/cursor-hook-wear/` (gitignored, and left out of the npm package), so removing `node_modules` does not delete it. When wear creates that folder and the path is not already ignored, it appends the path to `$GIT_DIR/info/exclude`. Wear stores the user bytes, never an already-worn `hooks.json`. Restore with `npx 0xray unwear` (or `node node_modules/0xray/scripts/node/unwear-cursor-hooks.cjs`). Unwear prints `restored` only when the file bytes match that snapshot exactly. If the file changed or cannot be parsed, unwear exits non-zero, says what differs, and leaves the snapshot in place. Wear refuses, and writes nothing, when git is missing, the directory is not a work tree, `GIT_DIR` is bare, or `hooks.json` cannot be parsed.
|
|
25
|
+
|
|
26
|
+
`xray-cloud-hook.sh` resolution order (first file that exists wins). Each run appends one `cursor-hook-invoke.log` line with `js=` set to the absolute path it executes:
|
|
27
|
+
|
|
28
|
+
1. Consumer `node_modules/0xray/dist/integrations/cursor/hooks/${JS}`, walking upward from the script directory, then from cwd. A directory whose `package.json` name is `0xray` is skipped, so a factory checkout does not prefer its own `node_modules` or a nested example over `src/`.
|
|
29
|
+
2. Sibling `${JS}` when the runner itself lives under `node_modules/0xray`.
|
|
30
|
+
3. `$XRAY_AI_PATH` `src/` then `dist/` (explicit mill).
|
|
31
|
+
4. Dogfood `src/` at cwd, `../xray`, `repos/xray`, and `$CURSOR_PROJECT_DIR/repos/xray`, then dist at cwd, then `cwd/node_modules/0xray`.
|
|
17
32
|
|
|
18
33
|
## Contracts
|
|
19
34
|
|
|
@@ -64,3 +79,7 @@ echo '{"hook_event_name":"preCompact","trigger":"auto","context_tokens":1}' \
|
|
|
64
79
|
```
|
|
65
80
|
|
|
66
81
|
Path C seed + receipt: `examples/cursor-cloud-compact/`. Suited Arm S (real usage, no FILL): `examples/killer-dual/`.
|
|
82
|
+
|
|
83
|
+
## Known limits
|
|
84
|
+
|
|
85
|
+
`findLocalExecDaemon` reads the local exec-daemon auth token from `/proc/<pid>/cmdline` (`scripts/node/install-bridges.cjs:927`–`:935`). That read needs a separate security review. Wear does not change it.
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Cloud-safe Cursor hook runner. Command in hooks.json must be a relative path.
|
|
3
3
|
# Do not put env-assignment in hooks.json — Cloud execs argv[0] without a shell.
|
|
4
|
+
#
|
|
5
|
+
# Resolution order (first existing file wins):
|
|
6
|
+
# 1. Consumer install. Walk upward from this script's directory, then from cwd.
|
|
7
|
+
# Use node_modules/0xray/dist/integrations/cursor/hooks/${JS}.
|
|
8
|
+
# Skip a directory whose package.json "name" is "0xray" (the factory repo).
|
|
9
|
+
# That keeps dogfood on src/ and prefers a consumer install over a parent src/.
|
|
10
|
+
# 2. Sibling ${JS} when this runner lives under node_modules/0xray (installed dist).
|
|
11
|
+
# 3. XRAY_AI_PATH, if set: src then dist (explicit mill, dogfood path shape).
|
|
12
|
+
# 4. Dogfood repo source: cwd/src, cwd/../xray/src, cwd/repos/xray/src,
|
|
13
|
+
# ${CURSOR_PROJECT_DIR}/repos/xray/src, then those roots' dist/, then
|
|
14
|
+
# cwd/node_modules/0xray (dist then src).
|
|
15
|
+
# Consumer wear points hooks.json at the installed dist *.sh so step 1/2 run
|
|
16
|
+
# even when Cursor's cwd is a parent factory checkout.
|
|
4
17
|
set -eu
|
|
5
18
|
|
|
6
19
|
EVENT="${1:-preToolUse}"
|
|
@@ -11,7 +24,7 @@ REL=""
|
|
|
11
24
|
pick_mill() {
|
|
12
25
|
mill="$1"
|
|
13
26
|
rel="$2"
|
|
14
|
-
if [ -f "${mill}/${rel}" ]; then
|
|
27
|
+
if [ -n "$mill" ] && [ -f "${mill}/${rel}" ]; then
|
|
15
28
|
MILL="$mill"
|
|
16
29
|
REL="$rel"
|
|
17
30
|
return 0
|
|
@@ -19,15 +32,54 @@ pick_mill() {
|
|
|
19
32
|
return 1
|
|
20
33
|
}
|
|
21
34
|
|
|
22
|
-
|
|
35
|
+
is_xray_package() {
|
|
36
|
+
pkg="$1/package.json"
|
|
37
|
+
[ -f "$pkg" ] || return 1
|
|
38
|
+
grep -q '"name"[[:space:]]*:[[:space:]]*"0xray"' "$pkg" 2>/dev/null || return 1
|
|
39
|
+
return 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pick_consumer_from() {
|
|
43
|
+
dir="$1"
|
|
44
|
+
[ -n "$dir" ] || return 1
|
|
45
|
+
while [ -n "$dir" ]; do
|
|
46
|
+
if ! is_xray_package "$dir"; then
|
|
47
|
+
if pick_mill "$dir/node_modules/0xray" "dist/integrations/cursor/hooks/${JS}"; then
|
|
48
|
+
return 0
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
parent="$(dirname -- "$dir")"
|
|
52
|
+
if [ "$parent" = "$dir" ]; then
|
|
53
|
+
break
|
|
54
|
+
fi
|
|
55
|
+
dir="$parent"
|
|
56
|
+
done
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
HERE="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
61
|
+
|
|
62
|
+
pick_consumer_from "$HERE" || pick_consumer_from "$(pwd)" || true
|
|
63
|
+
|
|
64
|
+
if [ -z "$MILL" ]; then
|
|
65
|
+
case "$HERE" in
|
|
66
|
+
*/node_modules/0xray/*)
|
|
67
|
+
pick_mill "$HERE" "${JS}" || true
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ -z "$MILL" ] && [ -n "${XRAY_AI_PATH:-}" ]; then
|
|
23
73
|
pick_mill "${XRAY_AI_PATH}" "src/integrations/cursor/hooks/${JS}" || \
|
|
24
74
|
pick_mill "${XRAY_AI_PATH}" "dist/integrations/cursor/hooks/${JS}" || true
|
|
25
75
|
fi
|
|
76
|
+
|
|
26
77
|
if [ -z "$MILL" ]; then
|
|
27
78
|
pick_mill "$(pwd)" "src/integrations/cursor/hooks/${JS}" || \
|
|
28
79
|
pick_mill "$(pwd)/../xray" "src/integrations/cursor/hooks/${JS}" || \
|
|
29
80
|
pick_mill "$(pwd)/repos/xray" "src/integrations/cursor/hooks/${JS}" || \
|
|
30
81
|
pick_mill "${CURSOR_PROJECT_DIR:-}/repos/xray" "src/integrations/cursor/hooks/${JS}" || \
|
|
82
|
+
pick_mill "$(pwd)" "dist/integrations/cursor/hooks/${JS}" || \
|
|
31
83
|
pick_mill "$(pwd)/node_modules/0xray" "dist/integrations/cursor/hooks/${JS}" || \
|
|
32
84
|
pick_mill "$(pwd)/node_modules/0xray" "src/integrations/cursor/hooks/${JS}" || true
|
|
33
85
|
fi
|
|
@@ -36,14 +88,19 @@ if [ -n "$MILL" ] && [ -d "$MILL" ]; then
|
|
|
36
88
|
MILL="$(cd "$MILL" && pwd)"
|
|
37
89
|
fi
|
|
38
90
|
|
|
91
|
+
JS_ABS=""
|
|
92
|
+
if [ -n "$MILL" ] && [ -n "$REL" ] && [ -f "${MILL}/${REL}" ]; then
|
|
93
|
+
JS_ABS="$(CDPATH= cd -- "$(dirname -- "${MILL}/${REL}")" && pwd)/$(basename -- "${REL}")"
|
|
94
|
+
fi
|
|
95
|
+
|
|
39
96
|
NODE_BIN="$(command -v node 2>/dev/null || true)"
|
|
40
97
|
|
|
41
98
|
log_invoke() {
|
|
42
99
|
dest="$1"
|
|
43
100
|
mkdir -p "${dest}/.xray/state" 2>/dev/null || true
|
|
44
101
|
{
|
|
45
|
-
printf 'ts=%s event=%s cwd=%s mill=%s node=%s\n' \
|
|
46
|
-
"$(date -Iseconds)" "$EVENT" "$(pwd)" "${MILL:-MISSING}" "${NODE_BIN:-MISSING}"
|
|
102
|
+
printf 'ts=%s event=%s cwd=%s mill=%s node=%s js=%s\n' \
|
|
103
|
+
"$(date -Iseconds)" "$EVENT" "$(pwd)" "${MILL:-MISSING}" "${NODE_BIN:-MISSING}" "${JS_ABS:-MISSING}"
|
|
47
104
|
} >> "${dest}/.xray/state/cursor-hook-invoke.log" 2>/dev/null || true
|
|
48
105
|
}
|
|
49
106
|
|
package/llms.txt
CHANGED
|
@@ -13,6 +13,8 @@ Default consumer wear is **mill plant** (`mill` + `inspect`), not a 45-skill / 4
|
|
|
13
13
|
- Host `preCompact` writes the hook line and per-session receipt on the suit mill and on nested consumer wear roots (a bench with local `node_modules/0xray`). A counted cut needs a non-empty generation id and a numeric usage percent.
|
|
14
14
|
- `@0xray/grok-bot`: the general page is `grok-bot/OP-PROC.md`. The team overlay is `house/` and is not in that npm pack. `templates/house/` ships. `grok-bot house init` copies the template and refuses to overwrite. Doctor uses `GROK_BOT_HOUSE` when set, otherwise walks up from the working directory for `house/HOUSE.md`.
|
|
15
15
|
- MCP servers start through `scripts/node/mcp-launch.cjs`. They see PATH, HOME, and XRAY_ROOT, plus the names on their keep list. Tokens such as NPM_TOKEN are not inherited. User env values stay out of argv. Postinstall keeps user edits to AGENTS.md, `.mcp.json`, and `opencode.json`. `house init` does not copy EXAMPLE.md. Doctor FAILs while `house/EXAMPLE.md` exists. Inspect has a machinePlugin check. The Cursor hook defaults XRAY_ROOT to the suit directory.
|
|
16
|
+
- `npm install` only creates the relative `node_modules/@0xray/repertoire` link to the vendored repertoire and prints `npx 0xray wear`. Run `npx 0xray wear` after install; it writes only the consumer project's `.cursor/hooks.json`. `npx 0xray unwear` restores the pre-wear snapshot.
|
|
17
|
+
- 4.0.30: `npx 0xray wear` and `setup` also work outside a git checkout. They write the 4.0.28 project setup and skip only Cursor hooks, with one warning. In a git checkout they write the 4.0.28 project setup plus Cursor hooks.
|
|
16
18
|
|
|
17
19
|
- `npx @0xray/foundry mint --skip-live` — re-fasten mill plant, then inspect.
|
|
18
20
|
- `npx @0xray/foundry inspect --skip-live` — plant-vs-worn, receipt, CI. Chat is not a receipt.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0xray",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.30",
|
|
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",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"test:miscellaneous": "npm test -- src/__tests__/unit/blocked-test.test.ts",
|
|
83
83
|
"test:grok-bot": "npm test -- src/__tests__/unit/grok-bot-seat-doctor.test.ts src/__tests__/unit/grok-bot-op-proc-map.test.ts src/__tests__/unit/lead-cadence.test.ts src/__tests__/unit/grok-bot-auto-review-policy.test.ts",
|
|
84
84
|
"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",
|
|
85
|
-
"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 src/__tests__/unit/cursor-hooks.test.ts src/__tests__/unit/host-vs-mill-score.test.ts && npm run test:grok-bot",
|
|
85
|
+
"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 src/__tests__/unit/cursor-hooks.test.ts src/__tests__/unit/host-vs-mill-score.test.ts src/__tests__/unit/wear-pack-file-list.test.ts && npm run test:grok-bot",
|
|
86
86
|
"test:integration-all": "npm test -- src/__tests__/integration/",
|
|
87
87
|
"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",
|
|
88
88
|
"test:agents-all": "echo 'Legacy agents test entry retired'",
|
|
@@ -619,8 +619,9 @@ function deployPortableProjectMcpJson(targetDir) {
|
|
|
619
619
|
...existing,
|
|
620
620
|
mcpServers: mergeMcpMap(framework, existing.mcpServers || {}, targetDir),
|
|
621
621
|
};
|
|
622
|
-
if (hadFile && jsonDeepEqual(merged, existing)) return;
|
|
622
|
+
if (hadFile && jsonDeepEqual(merged, existing)) return false;
|
|
623
623
|
fs.writeFileSync(destPath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
624
|
+
return true;
|
|
624
625
|
}
|
|
625
626
|
|
|
626
627
|
function enableHermesPluginBestEffort() {
|
|
@@ -21,16 +21,6 @@ const require = createRequire(import.meta.url);
|
|
|
21
21
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
22
|
const rootDir = path.resolve(__dirname, "../..");
|
|
23
23
|
|
|
24
|
-
const XRAY_MCP_NAMES = [
|
|
25
|
-
"xray-governance",
|
|
26
|
-
"xray-skills",
|
|
27
|
-
"xray-orchestrator",
|
|
28
|
-
"xray-enforcer",
|
|
29
|
-
"xray-researcher",
|
|
30
|
-
"xray-code-review",
|
|
31
|
-
"xray-architect-tools",
|
|
32
|
-
];
|
|
33
|
-
|
|
34
24
|
function run(cmd, cwd, { inherit = false } = {}) {
|
|
35
25
|
const result = execSync(cmd, {
|
|
36
26
|
cwd,
|
|
@@ -311,19 +301,12 @@ function main() {
|
|
|
311
301
|
}
|
|
312
302
|
|
|
313
303
|
const millSkillHanger = path.join(tmpRoot, ".opencode", "skills", "mill", "SKILL.md");
|
|
314
|
-
if (
|
|
315
|
-
throw new Error("
|
|
304
|
+
if (fs.existsSync(millSkillHanger)) {
|
|
305
|
+
throw new Error("postinstall must not fasten mill skills into the project");
|
|
316
306
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
? fs.readdirSync(wornSkills).filter((n) =>
|
|
320
|
-
fs.existsSync(path.join(wornSkills, n, "SKILL.md")),
|
|
321
|
-
)
|
|
322
|
-
: [];
|
|
323
|
-
if (wornNames.length > 3) {
|
|
324
|
-
throw new Error(`costume dump: ${wornNames.join(",")}`);
|
|
307
|
+
if (fs.existsSync(path.join(tmpRoot, ".mcp.json"))) {
|
|
308
|
+
throw new Error("postinstall must not write .mcp.json");
|
|
325
309
|
}
|
|
326
|
-
|
|
327
310
|
const installedPkg = JSON.parse(fs.readFileSync(path.join(nmRoot, "package.json"), "utf-8"));
|
|
328
311
|
if (installedPkg.version !== version) {
|
|
329
312
|
throw new Error(`Installed version ${installedPkg.version} != packed ${version}`);
|
|
@@ -342,57 +325,11 @@ function main() {
|
|
|
342
325
|
}
|
|
343
326
|
console.log(" ✅ consumer target resolution");
|
|
344
327
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const servers = Object.keys(mcp.mcpServers || {});
|
|
351
|
-
for (const name of XRAY_MCP_NAMES) {
|
|
352
|
-
if (!servers.includes(name)) {
|
|
353
|
-
throw new Error(`Missing MCP server in .mcp.json: ${name}`);
|
|
354
|
-
}
|
|
355
|
-
const entry = mcp.mcpServers[name];
|
|
356
|
-
const cli = path.join(tmpRoot, "node_modules", "0xray", "dist", "cli", "index.js");
|
|
357
|
-
const args = Array.isArray(entry.args) ? entry.args.map(String) : [];
|
|
358
|
-
const launcher = path.join(nmRoot, "scripts", "node", "mcp-launch.cjs");
|
|
359
|
-
if (entry.command !== "node" || args[0] !== launcher || args[1] !== "--keep") {
|
|
360
|
-
throw new Error(`${name} must launch via node mcp-launch.cjs --keep, got ${entry.command} ${args.join(" ")}`);
|
|
361
|
-
}
|
|
362
|
-
const dash = args.indexOf("--");
|
|
363
|
-
if (dash < 0) throw new Error(`${name} launcher args missing --`);
|
|
364
|
-
const kept = String(args[2] || "").split(",").filter(Boolean);
|
|
365
|
-
const inner = args.slice(dash + 1);
|
|
366
|
-
const pinnedNode = inner[0] === "node" && inner[1] === cli && inner[2] === "mcp";
|
|
367
|
-
const pinnedNpx =
|
|
368
|
-
inner[0] === "npx" && inner[1] === "-y" && /^0xray@\d/.test(inner[2] || "") && inner[3] === "mcp";
|
|
369
|
-
if (!pinnedNode && !pinnedNpx) {
|
|
370
|
-
throw new Error(`${name} must pin node CLI or npx 0xray@version after --, got ${inner.join(" ")}`);
|
|
371
|
-
}
|
|
372
|
-
if (inner.includes("0xray")) {
|
|
373
|
-
throw new Error(`${name} launched unpinned 0xray`);
|
|
374
|
-
}
|
|
375
|
-
for (const required of ["PATH", "HOME", "XRAY_ROOT"]) {
|
|
376
|
-
if (!kept.includes(required)) {
|
|
377
|
-
throw new Error(`${name} keep list missing ${required}`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (entry.env && (entry.env.PATH || entry.env.HOME)) {
|
|
381
|
-
throw new Error(`${name} baked PATH or HOME into the env block`);
|
|
382
|
-
}
|
|
383
|
-
for (const arg of args) {
|
|
384
|
-
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(arg)) {
|
|
385
|
-
throw new Error(`${name} put an env assignment in argv: ${arg.split("=")[0]}`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
for (const banned of ["NPM_TOKEN", "CURSOR_AUTH_TOKEN", "GITHUB_TOKEN", "GH_TOKEN", "RAILWAY_TOKEN"]) {
|
|
389
|
-
if (kept.includes(banned) || (entry.env && Object.prototype.hasOwnProperty.call(entry.env, banned))) {
|
|
390
|
-
throw new Error(`${name} inherited ${banned}`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
console.log(` ✅ .mcp.json has ${XRAY_MCP_NAMES.length} version-pinned MCP servers`);
|
|
395
|
-
|
|
328
|
+
fs.mkdirSync(path.join(tmpRoot, ".xray"), { recursive: true });
|
|
329
|
+
fs.copyFileSync(
|
|
330
|
+
path.join(nmRoot, "xray", "features.json"),
|
|
331
|
+
path.join(tmpRoot, ".xray", "features.json"),
|
|
332
|
+
);
|
|
396
333
|
assertFreshInstallDefaults(tmpRoot, version);
|
|
397
334
|
assertOrganRequire(tmpRoot);
|
|
398
335
|
|