0xray 4.0.28 → 4.0.29

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.
@@ -21,6 +21,7 @@ credentials.json
21
21
  *.key
22
22
 
23
23
  # 0xray suit artifacts (postinstall / bridge install)
24
+ .xray/state/cursor-hook-wear/
24
25
  .grok/
25
26
  .mcp.json
26
27
  opencode.json
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.28",
3
+ "version": "4.0.29",
4
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
5
  "author": {
6
6
  "name": "0xRay AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./features.schema.json",
3
- "version": "4.0.28",
3
+ "version": "4.0.29",
4
4
  "description": "0xRay Framework - Unified Feature Configuration",
5
5
  "suit_temperament": {
6
6
  "profile": "auto",
package/README.md CHANGED
@@ -52,6 +52,7 @@ 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.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
56
  | **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
57
  | **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
58
  | **4.0.9** | Mill target: postinstall does not mill npm global prefix or `_npx` as a consumer. Isolated HOME skips machine `~/.grok`. |
@@ -65,7 +66,7 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
65
66
 
66
67
  ### Consolidations
67
68
 
68
- - **One consumer install path** — `postinstall.cjs` → `installAllBridges()` replaces scattered per-platform setup. Mill target is the consumer project, not npm global prefix or `_npx`.
69
+ - **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
70
  - **7-server MCP surface** — `.mcp.json` SSOT; Grok plugin and all bridges share `XRAY_MCP_SERVERS`.
70
71
  - **Dev vs consumer AGENTS** — `AGENTS.md` (framework) vs `AGENTS-consumer.md` (copied to consumer projects on install).
71
72
  - **Release pipeline** — `npm run release:patch|minor|major` → reconcile → gate → artifacts → tag → publish.
@@ -21,6 +21,7 @@ credentials.json
21
21
  *.key
22
22
 
23
23
  # 0xray suit artifacts (postinstall / bridge install)
24
+ .xray/state/cursor-hook-wear/
24
25
  .grok/
25
26
  .mcp.json
26
27
  opencode.json
package/dist/CHANGELOG.md CHANGED
@@ -41,6 +41,14 @@ 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.29] - 2026-09-28
45
+
46
+ Next patch after npm `4.0.28`. Released 2026-09-28.
47
+
48
+ `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.
49
+
50
+ ---
51
+
44
52
  ## [4.0.28] - 2026-09-26
45
53
 
46
54
  Next patch after npm `4.0.27`. Released 2026-09-27.
package/dist/README.md CHANGED
@@ -52,6 +52,7 @@ 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.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
56
  | **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
57
  | **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
58
  | **4.0.9** | Mill target: postinstall does not mill npm global prefix or `_npx` as a consumer. Isolated HOME skips machine `~/.grok`. |
@@ -65,7 +66,7 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
65
66
 
66
67
  ### Consolidations
67
68
 
68
- - **One consumer install path** — `postinstall.cjs` → `installAllBridges()` replaces scattered per-platform setup. Mill target is the consumer project, not npm global prefix or `_npx`.
69
+ - **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
70
  - **7-server MCP surface** — `.mcp.json` SSOT; Grok plugin and all bridges share `XRAY_MCP_SERVERS`.
70
71
  - **Dev vs consumer AGENTS** — `AGENTS.md` (framework) vs `AGENTS-consumer.md` (copied to consumer projects on install).
71
72
  - **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 runSetup() {
49
- const setupScript = join(packageRoot, "scripts", "node", "setup.cjs");
50
- validateScriptPath(setupScript, "setup script");
51
- execSync(`node "${setupScript}"`, { stdio: "inherit", cwd: process.cwd() });
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(async () => {
57
- frameworkLogger.log('cli', 'install-start', 'info', { message: 'Installing xray framework...' });
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
- const postinstallScript = join(packageRoot, "scripts", "node", "postinstall.cjs");
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('cli', 'install-error', 'error', { error: error instanceof Error ? error.message : String(error) });
69
- console.error("❌ Installation failed:", error instanceof Error ? error.message : String(error));
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("setup")
75
- .description("Full framework setup: hooks, Hermes integration, symlinks, MCP paths")
76
- .action(runSetup);
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("init")
79
- .description("Initialize xray configuration in the current project")
80
- .action(async () => {
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
- const postinstallScript = join(packageRoot, "scripts", "node", "postinstall.cjs");
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
- console.error("❌ Initialization failed:", error instanceof Error ? error.message : String(error));
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
- console.log("Running postinstall setup to restore configuration...");
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` also fastens the daemon workspace root when it can see `repos/xray` above the checkout, then pokes local `ReloadAgentSkills` so a mid-session wear binds this daemon. Command stays a relative `.cursor/hooks/*.sh`.
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. `xray-cloud-hook.sh` finds mill JS at `XRAY_AI_PATH`, `../xray`, or `node_modules/0xray` (dist then src). This exo repo wears `src/integrations/cursor/hooks/*.js` so a cloud can run before `npm run build`.
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
- if [ -n "${XRAY_AI_PATH:-}" ]; then
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.28",
3
+ "version": "4.0.29",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
package/llms.txt CHANGED
@@ -13,6 +13,7 @@ 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.
16
17
 
17
18
  - `npx @0xray/foundry mint --skip-live` — re-fasten mill plant, then inspect.
18
19
  - `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.28",
3
+ "version": "4.0.29",
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",
@@ -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() {
@@ -9,6 +9,7 @@ const MARKER_END = "# --- end 0xray suit ---";
9
9
 
10
10
  /** Suit artifacts dropped by postinstall / install-bridges — keep out of product repos */
11
11
  const SUIT_IGNORE_LINES = [
12
+ ".xray/state/cursor-hook-wear/",
12
13
  ".grok/",
13
14
  ".mcp.json",
14
15
  "opencode.json",
@@ -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 (!fs.existsSync(millSkillHanger)) {
315
- throw new Error("mill plant missing mill skill");
304
+ if (fs.existsSync(millSkillHanger)) {
305
+ throw new Error("postinstall must not fasten mill skills into the project");
316
306
  }
317
- const wornSkills = path.join(tmpRoot, ".opencode", "skills");
318
- const wornNames = fs.existsSync(wornSkills)
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
- const mcpPath = path.join(tmpRoot, ".mcp.json");
346
- if (!fs.existsSync(mcpPath)) {
347
- throw new Error(".mcp.json not created by postinstall");
348
- }
349
- const mcp = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
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