0xray 4.0.20 → 4.0.22

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 (74) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.opencode/skills/hermes-agent/SKILL.md +1 -1
  3. package/.opencode/skills/orchestrator/SKILL.md +23 -1
  4. package/.xray/features.json +1 -1
  5. package/AGENTS-consumer.md +15 -15
  6. package/AGENTS.md +12 -12
  7. package/README.md +40 -40
  8. package/SKILLS.md +4 -4
  9. package/dist/AGENTS.md +12 -12
  10. package/dist/CHANGELOG.md +91 -8
  11. package/dist/README.md +40 -40
  12. package/dist/SKILLS.md +4 -4
  13. package/dist/cli/commands/validate.d.ts +1 -1
  14. package/dist/cli/commands/validate.js +7 -1
  15. package/dist/governance/governance-core.d.ts +2 -1
  16. package/dist/governance/governance-core.js +20 -11
  17. package/dist/governance/governance-service.js +9 -7
  18. package/dist/inference/inference-cycle.js +22 -5
  19. package/dist/integrations/cursor/README.md +7 -6
  20. package/dist/integrations/cursor/hooks/after-file-edit.sh +3 -0
  21. package/dist/integrations/cursor/hooks/before-read-file.sh +3 -0
  22. package/dist/integrations/cursor/hooks/before-shell-execution.sh +3 -0
  23. package/dist/integrations/cursor/hooks/cursor-hook-utils.js +112 -12
  24. package/dist/integrations/cursor/hooks/cursor-usage-receipt.js +3 -0
  25. package/dist/integrations/cursor/hooks/hooks.json +13 -3
  26. package/dist/integrations/cursor/hooks/pre-compact.js +9 -0
  27. package/dist/integrations/cursor/hooks/pre-compact.sh +3 -0
  28. package/dist/integrations/cursor/hooks/pre-tool-use.js +17 -12
  29. package/dist/integrations/cursor/hooks/pre-tool-use.sh +3 -0
  30. package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +63 -0
  31. package/dist/integrations/grok/hooks/grok-hook-utils.js +3 -2
  32. package/dist/integrations/hooks/station-hook-runtime.cjs +310 -98
  33. package/dist/integrations/hooks/station-hook-runtime.mjs +10 -0
  34. package/dist/integrations/hooks/station-memory-ingest.mjs +4 -3
  35. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  36. package/dist/memory-routing/provider-loader.d.ts +3 -1
  37. package/dist/memory-routing/provider-loader.js +3 -1
  38. package/dist/nucleus/suit-temperament.d.ts +0 -5
  39. package/dist/nucleus/suit-temperament.js +13 -1
  40. package/dist/nucleus/thin-dispatch.js +11 -2
  41. package/dist/processors/implementations/agents-md-validation-processor.js +1 -1
  42. package/dist/skills/hermes-agent/SKILL.md +1 -1
  43. package/dist/skills/orchestrator/SKILL.md +23 -1
  44. package/llms.txt +1 -1
  45. package/package.json +3 -3
  46. package/scripts/foundry/CHANGELOG.md +2 -0
  47. package/scripts/foundry/README.md +4 -4
  48. package/scripts/foundry/assert-packed-dist-cli.mjs +4 -0
  49. package/scripts/foundry/cli.mjs +1 -1
  50. package/scripts/foundry/inspect.mjs +160 -5
  51. package/scripts/foundry/plant/skills/inspect/SKILL.md +2 -0
  52. package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
  53. package/scripts/foundry/validate-release-docs.mjs +48 -0
  54. package/scripts/foundry/version-manager.mjs +127 -7
  55. package/scripts/mjs/test-consumer-readiness.mjs +10 -3
  56. package/scripts/node/install-bridges.cjs +256 -10
  57. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  58. package/src/skills/hermes-agent/SKILL.md +1 -1
  59. package/src/skills/orchestrator/SKILL.md +23 -1
  60. package/vendor/@0xray/repertoire/data/stack-overlay.json +154 -1
  61. package/vendor/@0xray/repertoire/dist/RepertoireService.js +7 -6
  62. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js +10 -11
  63. package/vendor/@0xray/repertoire/dist/paths.js +66 -4
  64. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +54 -14
  65. package/xray/agents_template.md +10 -5
  66. package/xray/features.json +1 -1
  67. package/dist/mcps/shared/prompt-security-validator.d.ts +0 -44
  68. package/dist/mcps/shared/prompt-security-validator.js +0 -148
  69. package/dist/mcps/shared/security-scanner.d.ts +0 -119
  70. package/dist/mcps/shared/security-scanner.js +0 -429
  71. package/dist/scripts/activate-kernel-pipeline.d.ts +0 -7
  72. package/dist/scripts/activate-kernel-pipeline.js +0 -101
  73. package/src/mcps/shared/prompt-security-validator.ts +0 -199
  74. package/src/mcps/shared/security-scanner.ts +0 -599
package/dist/README.md CHANGED
@@ -14,31 +14,31 @@ Not a catalog of agents. The product is the **skeleton you wear**.
14
14
  - **Always on** — Codex 11 / 29 / 69, destructive shell, no new MCP/skill/handler surface
15
15
  - **Temperament** — `frontier` | `guided` | `strict` | `auto` — how loud the engine is, not whether governance exists
16
16
  - **Wear** — Grok, OpenCode, Hermes, OpenClaw, plus Cursor project hooks. One SSOT gate
17
- - **Muscle** — Repertoire: auto-enables when the module resolves; session-start writes a one-line resume so the next station is not born amnesiac
17
+ - **Muscle** — **Repertoire is preferred.** Vendored 0.2.5 ships **on**. Dest is named laws, not hangar `repo-*` or git slugs. Station is the compact-survival ticket — not a substitute for Repertoire.
18
18
 
19
19
  ```bash
20
20
  npm install 0xray
21
21
  ```
22
22
 
23
- Consumer `npm install 0xray` auto-wires four chat bridges, Cursor project hooks, and seven MCP servers. Repertoire **0.2** is the factory memory organ (vendored): seed primitives, in-process routing, extra host MCP `repertoire`. Not an eighth `xray-*` server. Opt out with `"enabled": false, "provider": "repertoire"`.
23
+ Consumer `npm install 0xray` auto-wires four chat bridges, Cursor project hooks, and seven MCP servers. **Repertoire is preferred** — vendored **0.2.5** ships on: factory + stack laws, in-process routing, extra host MCP `repertoire`. Not an eighth `xray-*` server. Dest is a judgment index. Explicit opt-out only: `"enabled": false, "provider": "repertoire"`. Do not pin 0.1.8.
24
24
 
25
25
  ## Quick Start
26
26
 
27
27
  ```bash
28
- npm install 0xray # postinstall: mill+inspect plant + 4 bridges + 7 MCPs + AGENTS.md + .mcp.json
29
- npx 0xray status # verify install
30
- npx 0xray setup # optional: symlinks, hook extras
31
- npx @0xray/foundry inspect --skip-live # mill receipt (chat is not a receipt)
28
+ npm install 0xray # postinstall: mill+inspect plant + 4 bridges + 7 MCPs + AGENTS.md + .mcp.json
29
+ npx 0xray status # verify install
30
+ npx 0xray setup # optional: symlinks, hook extras
31
+ npx @0xray/foundry inspect --skip-live # mill receipt (chat is not a receipt)
32
32
  ```
33
33
 
34
34
  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 + project .grok/plugins/0xray (not machine last-wins)
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
- npx 0xray skill:install # starter skills
41
+ npx 0xray skill:install # starter skills
42
42
  ```
43
43
 
44
44
  ## Autonomy command (default operating model)
@@ -51,7 +51,7 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
51
51
 
52
52
  | Version | Highlights |
53
53
  |---------|------------|
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`). 0xray stays 4.0.15. |
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
55
  | **4.0.9** | Mill target: postinstall does not mill npm global prefix or `_npx` as a consumer. Isolated HOME skips machine `~/.grok`. |
56
56
  | **4.0.0** | Exo + temperament. Constitution always on; ceremony by host. Repertoire organ on (vendored 0.2). Station card survives compact/host-swap. **On npm.** [vision](docs-site/docs/architecture/v4-vision.md) · [now](docs-site/docs/architecture/v4-now.md) |
57
57
  | **3.4.1** | Unified `install-bridges.cjs` on postinstall — OpenCode, Grok, Hermes, OpenClaw in one pass. All 7 MCPs servers via `npx -y 0xray mcp <cmd>` (no `dist/` paths). Canonical `release.mjs` pipeline. |
@@ -63,7 +63,7 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
63
63
 
64
64
  ### Consolidations
65
65
 
66
- - **One consumer install path** — `postinstall.cjs` → `installAllBridges()` replaces scattered per-platform setup. **4.0.9:** does not mill npm global prefix or `_npx`.
66
+ - **One consumer install path** — `postinstall.cjs` → `installAllBridges()` replaces scattered per-platform setup. Mill target is the consumer project, not npm global prefix or `_npx`.
67
67
  - **7-server MCP surface** — `.mcp.json` SSOT; Grok plugin and all bridges share `XRAY_MCP_SERVERS`.
68
68
  - **Dev vs consumer AGENTS** — `AGENTS.md` (framework) vs `AGENTS-consumer.md` (copied to consumer projects on install).
69
69
  - **Release pipeline** — `npm run release:patch|minor|major` → reconcile → gate → artifacts → tag → publish.
@@ -81,16 +81,16 @@ Docs: [guides/autonomy-command](docs-site/docs/guides/autonomy-command.md) · Sk
81
81
 
82
82
  ```
83
83
  ┌─────────────────────────────────────────────────┐
84
- │ Inference │
85
- │ Proposals · Reflection · Memory routing │
84
+ │ Inference │
85
+ │ Proposals · Reflection · Memory routing │
86
86
  ├─────────────────────────────────────────────────┤
87
- │ External Governance (Dynamo) │
88
- │ Codex enforcement · Resonance/Isotopic · SSOT │
89
- │ 3 deliberation MCPs: code-review, security, │
90
- │ researcher (within 7-server consumer surface) │
87
+ │ External Governance (Dynamo) │
88
+ │ Codex enforcement · Resonance/Isotopic · SSOT │
89
+ │ 3 deliberation MCPs: code-review, security, │
90
+ │ researcher (within 7-server consumer surface) │
91
91
  ├─────────────────────────────────────────────────┤
92
- │ Autonomous Engine (thinDispatch) │
93
- │ 7-flow · AsideContext · Confidence gate │
92
+ │ Autonomous Engine (thinDispatch) │
93
+ │ 7-flow · AsideContext · Confidence gate │
94
94
  └─────────────────────────────────────────────────┘
95
95
  ```
96
96
 
@@ -149,20 +149,20 @@ Core governance agents:
149
149
 
150
150
  See [AGENTS.md](AGENTS.md) and [SKILLS.md](SKILLS.md) for the full agent and skill catalog.
151
151
 
152
- ## Memory Routing + Repertoire (v3.3+)
152
+ ## Memory Routing + Repertoire (preferred, v3.3+)
153
153
 
154
- Pluggable `memory_routing` block in `features.json` (validated by `features.schema.json`):
154
+ **Wear Repertoire.** It is the long-term judgment organ. Station holds the ticket across compact. Dest is named invariants, not keywords. Pluggable `memory_routing` in `features.json` (validated by `features.schema.json`) ships **on**:
155
155
 
156
156
  ```json
157
157
  "memory_routing": {
158
- "enabled": true,
159
- "provider": "repertoire",
160
- "module_path": "node_modules/@0xray/repertoire/dist/provider/memory-routing-provider.js",
161
- "config": {
162
- "signalsPath": ".xray/state/repertoire/curated_signals.json",
163
- "statePath": ".xray/state/repertoire/inference-state.json",
164
- "feedbackDir": ".xray/state/repertoire/feedback"
165
- }
158
+ "enabled": true,
159
+ "provider": "repertoire",
160
+ "module_path": "node_modules/@0xray/repertoire/dist/provider/memory-routing-provider.js",
161
+ "config": {
162
+ "signalsPath": ".xray/state/repertoire/curated_signals.json",
163
+ "statePath": ".xray/state/repertoire/inference-state.json",
164
+ "feedbackDir": ".xray/state/repertoire/feedback"
165
+ }
166
166
  }
167
167
  ```
168
168
 
@@ -174,9 +174,9 @@ Pluggable `memory_routing` block in `features.json` (validated by `features.sche
174
174
  | **AsideContext** (v3.2) | `buildInheritedContext` → `inheritedContext.memoryRouting` on orchestrator `spawnAside` |
175
175
  | **Feedback** | Per-task `ingestFeedback()` closes the learning loop |
176
176
 
177
- **External hosts** (Hermes/Grok): add `repertoire-mcp` to `.mcp.json` — see Repertoire docs.
177
+ **External hosts** (Hermes/Grok): add `repertoire-mcp` to `.mcp.json` — see Repertoire docs. Preferred. Not optional flavor.
178
178
 
179
- Opt out: `"memory_routing": { "enabled": false, "provider": "repertoire" }`.
179
+ Explicit opt-out only: `"memory_routing": { "enabled": false, "provider": "repertoire" }`.
180
180
 
181
181
  Docs: [memory routing](docs-site/docs/guides/memory-routing.md) · [Repertoire](docs-site/docs/guides/repertoire.md) · [features.json](docs-site/docs/guides/features-json.md) · [all features since 3.1](docs-site/docs/guides/features-since-3.1.md)
182
182
 
@@ -188,7 +188,7 @@ Docs: [memory routing](docs-site/docs/guides/memory-routing.md) · [Repertoire](
188
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
- | **Cursor** | postinstall (`installCursorBridge`) | Fastens `.cursor/hooks.json` (preToolUse / preCompact / afterFileEdit). Leaves an existing file alone. Not a fifth chat TUI. |
191
+ | **Cursor** | postinstall (`installCursorBridge`) | Fastens `.cursor/hooks.json` + relative `.cursor/hooks/*.sh`. Rewrites leftover env-assignment one-liners. Not a fifth chat TUI. |
192
192
 
193
193
  ## Governance & Codex
194
194
 
@@ -200,27 +200,27 @@ Docs: [memory routing](docs-site/docs/guides/memory-routing.md) · [Repertoire](
200
200
 
201
201
  ## Testing
202
202
 
203
- | Suite | Status (v4.0.1) |
204
- |-------|-----------------|
205
- | Four-floor consumer e2e | OpenCode 34/0 · Grok 63/0 · Hermes 39/0/2 · OpenClaw 96/0/1 (npm 4.0.0 pack) |
203
+ | Suite | Status |
204
+ |-------|--------|
205
+ | Four-floor consumer e2e | OpenCode 34/0 · Grok 63/0 · Hermes 39/0/2 · OpenClaw 96/0/1 |
206
206
  | Consumer smoke | `npm run release:gate` — pack → clean install → 7 MCPs + 4 chat bridges + Cursor hooks + organ on |
207
207
  | Pack → tmp proof | `npm run pack:tmp-proof` — tgz install + `foundry mint --skip-live` + hangar shops + inspect (no costume dump). Playwright n/a (CLI). |
208
208
 
209
209
  ```bash
210
210
  npm test
211
- npm run pack:tmp-proof # pack → tmp fasten + hangar inspect
212
- npm run release:gate # full release gate (before upload)
213
- npm run release:npm # gate + prepare + npm publish --access public
211
+ npm run pack:tmp-proof # pack → tmp fasten + hangar inspect
212
+ npm run release:gate # full release gate (before upload)
213
+ npm run release:npm # gate + prepare + npm publish --access public
214
214
  ```
215
215
 
216
216
  ## Release
217
217
 
218
- Product **4.0.0** is on npm. Do **not** run `release:major` to ship a 4.x fix (that becomes 5.0.0). Do **not** put a `scripts.publish` lifecycle that re-runs the gate after the registry PUT.
218
+ The **4.0** line is on npm. Do **not** run `release:major` to ship a 4.x fix (that becomes 5.0.0). Do **not** put a `scripts.publish` lifecycle that re-runs the gate after the registry PUT. Patch number lives in `package.json` + CHANGELOG.
219
219
 
220
220
  ```bash
221
221
  npm run release:gate
222
- npm run release:npm # after gate is green; uses npm publish --access public
223
- npm run release:patch # version bump pipeline (not for a already-bumped 4.0.0)
222
+ npm run release:npm # after gate is green; uses npm publish --access public
223
+ npm run release:patch # version bump pipeline (not for an already-bumped package.json)
224
224
  ```
225
225
 
226
226
  Pipeline: reconcile-version → release-gate (build + test + consumer smoke) → CHANGELOG/README/AGENTS artifacts → commit → tag → npm publish.
package/dist/SKILLS.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **45 skills** in this exo catalog. Default consumer plant is **`mill` + `inspect`**, not this dump. `npm install 0xray` does **not** sync 45 skills unless `foundry.json` `"costume": true`. Factory shop plant (`shop-extract`, `shop-witness`, `shop-pin`) coexists with mill plant when a hangar wears those shops. Extra shops: `foundry.json` `shopPlant`. 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 package declares — organs (looker, vibe, mixer) live in the mill, not this catalog. Nested mill `@0xray/foundry@0.1.12`. CLI shims `foundry blip|sound` still exist on foundry 0.1.11+. Agent map: [llms.txt](llms.txt).
6
6
 
7
- xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) as the **costume / self-plant catalog**. Lead-dev operating model lives in **`orchestrator`** skill + `multi_agent_orchestration.lead_dev_mode` in features.json. Skills load on demand via the **xray-skills** MCP server (`npx -y 0xray mcp skills`) or when you plant them. This file is the exo catalog — mill does **not** write it to the consumer root.
7
+ xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) as the **costume / self-plant catalog**. Lead-dev operating model lives in **`orchestrator`** skill + `multi_agent_orchestration.lead_dev_mode` in features.json. **Repertoire is preferred** for long-term judgment (vendored 0.2.5, dest = named laws). Station is the compact ticket, not a skill and not a keyword store. Skills load on demand via the **xray-skills** MCP server (`npx -y 0xray mcp skills`) or when you plant them. This file is the exo catalog — mill does **not** write it to the consumer root.
8
8
 
9
9
  ---
10
10
 
@@ -129,7 +129,7 @@ xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) as the **costume /
129
129
  Configured in project `.mcp.json` on postinstall:
130
130
 
131
131
  ```bash
132
- npx -y 0xray mcp skills # stdio server
132
+ npx -y 0xray mcp skills # stdio server
133
133
  ```
134
134
 
135
135
  MCP tools include: `invoke-skill`, `list-skills`, `skill-code-review`, `skill-security-audit`, `skill-api-design`, `skill-testing-strategy`, and more.
@@ -156,9 +156,9 @@ npx 0xray skill:install <source>
156
156
 
157
157
  | Feature | Skill connection |
158
158
  |---------|------------------|
159
- | **Memory routing** (v3.3) | Researcher uses Repertoire signals; optional `@0xray/repertoire` MCP |
159
+ | **Memory routing** (v3.3) | **Repertoire preferred** — researcher + thinDispatch + planner use dest laws. Extra MCP `npx @0xray/repertoire mcp` (not an 8th `xray-*`). Dest is named invariants, not `repo-*` slugs. |
160
160
  | **AsideContext** (v3.2) | Orchestrator subcontexts during skill dispatch |
161
- | **7 MCP servers** (v4.0.1) | `xray-skills` is the primary skill invocation surface |
161
+ | **7 MCP servers** (v4.0) | `xray-skills` is the primary skill invocation surface |
162
162
 
163
163
  Docs: [AGENTS.md](AGENTS.md) · [README.md](README.md) · [Docusaurus guides](docs-site/docs/guides/)
164
164
 
@@ -6,7 +6,7 @@
6
6
  */
7
7
  /** Same paths as `REQUIRED_PACK_PATHS` in assert-packed-dist-cli.mjs — typed here so src/cli does not import untyped .mjs. */
8
8
  export declare const VALIDATE_PACK_PATHS: readonly string[];
9
- export declare const CONSUMER_WEAR_PATHS: readonly ["AGENTS.md", ".mcp.json", ".xray/codex.json", ".xray/features.json", ".cursor/hooks.json"];
9
+ export declare const CONSUMER_WEAR_PATHS: readonly ["AGENTS.md", ".mcp.json", ".xray/codex.json", ".xray/features.json", ".cursor/hooks.json", ".cursor/hooks/pre-tool-use.sh", ".cursor/hooks/xray-cloud-hook.sh"];
10
10
  export declare const MILL_PLANT_SKILLS: readonly ["mill", "inspect"];
11
11
  export type ValidateCheck = {
12
12
  id: string;
@@ -18,6 +18,10 @@ export const VALIDATE_PACK_PATHS = [
18
18
  "dist/integrations/cursor/hooks/after-file-edit.js",
19
19
  "dist/integrations/cursor/hooks/cursor-hook-utils.js",
20
20
  "dist/integrations/cursor/hooks/cursor-usage-receipt.js",
21
+ "dist/integrations/cursor/hooks/xray-cloud-hook.sh",
22
+ "dist/integrations/cursor/hooks/pre-tool-use.sh",
23
+ "dist/integrations/cursor/hooks/pre-compact.sh",
24
+ "dist/integrations/cursor/hooks/after-file-edit.sh",
21
25
  ];
22
26
  export const CONSUMER_WEAR_PATHS = [
23
27
  "AGENTS.md",
@@ -25,6 +29,8 @@ export const CONSUMER_WEAR_PATHS = [
25
29
  ".xray/codex.json",
26
30
  ".xray/features.json",
27
31
  ".cursor/hooks.json",
32
+ ".cursor/hooks/pre-tool-use.sh",
33
+ ".cursor/hooks/xray-cloud-hook.sh",
28
34
  ];
29
35
  export const MILL_PLANT_SKILLS = ["mill", "inspect"];
30
36
  export function resolveInstalledPackageRoot(cwd) {
@@ -101,7 +107,7 @@ export function collectValidateReport(cwd) {
101
107
  id: "consumer-wear",
102
108
  ok: missingWear.length === 0,
103
109
  detail: missingWear.length === 0
104
- ? "AGENTS.md · .mcp.json · .xray · .cursor/hooks.json"
110
+ ? "AGENTS.md · .mcp.json · .xray · .cursor/hooks.json · .cursor/hooks/*.sh"
105
111
  : `missing ${missingWear.join(", ")}`,
106
112
  });
107
113
  const millRoot = join(cwd, ".opencode", "skills");
@@ -9,7 +9,8 @@
9
9
  import type { GovernanceVote, GovernanceResult } from './governance-types.js';
10
10
  export interface DecisionMatrixInput {
11
11
  resonance: number;
12
- isotopicRatio: number;
12
+ /** Present only when a real moralFusion number was supplied. Absent is not 0.5. */
13
+ isotopicRatio?: number;
13
14
  vortexVolume?: number;
14
15
  historicalCoherence?: number;
15
16
  solarActivity?: 'quiet' | 'moderate' | 'active' | 'storm';
@@ -61,22 +61,28 @@ export function applyDecisionMatrix(input) {
61
61
  voteWeight *= 1.05;
62
62
  reasons.push(`Moral alignment confirmed (score: ${(moralScore * 100).toFixed(0)}%)`);
63
63
  }
64
- if (resonance >= 0.92 && isotopicRatio >= 0.95) {
64
+ // Cutoffs stay the literals the tests lock (0.92/0.95, 0.82/0.88, 0.75/0.80).
65
+ // PHI and TAU are not those cutoffs, so reasons do not claim them.
66
+ const ratio = typeof isotopicRatio === 'number' && Number.isFinite(isotopicRatio) ? isotopicRatio : undefined;
67
+ const highBand = resonance >= 0.92 && (ratio === undefined || ratio >= 0.95);
68
+ const solidBand = resonance >= 0.82 && (ratio === undefined || ratio >= 0.88);
69
+ const belowCutoff = resonance < 0.75 || (ratio !== undefined && ratio < 0.80);
70
+ if (highBand) {
65
71
  recommendation = 'PASS';
66
72
  confidence = 0.97;
67
73
  voteWeight = 1.4;
68
- reasons.push('High symbiotic resonance (PHI-aligned)');
74
+ reasons.push('High symbiotic resonance');
69
75
  }
70
- else if (resonance >= 0.82 && isotopicRatio >= 0.88) {
76
+ else if (solidBand) {
71
77
  recommendation = 'PASS';
72
78
  confidence = 0.89;
73
79
  voteWeight = 1.15;
74
- reasons.push('Solid alignment above TAU threshold');
80
+ reasons.push('Solid alignment above the resonance cutoff');
75
81
  }
76
- else if (resonance < 0.75 || isotopicRatio < 0.80) {
82
+ else if (belowCutoff) {
77
83
  recommendation = 'REJECT';
78
84
  confidence = 0.84;
79
- reasons.push('Signal below critical threshold (1 - TAU)');
85
+ reasons.push('Signal below the critical resonance or isotopic cutoff');
80
86
  }
81
87
  else {
82
88
  reasons.push('Moderate resonance - requires refinement');
@@ -124,14 +130,17 @@ export function mergeVotes(votes) {
124
130
  reasoningSummary: 'No votes received',
125
131
  };
126
132
  }
133
+ const weightOf = (vote) => vote.weight ?? 1;
127
134
  const approveWeight = votes
128
135
  .filter(v => v.decision === 'approve')
129
- .reduce((sum, v) => sum + (v.weight ?? 1) * v.confidence, 0);
130
- const totalWeight = votes.reduce((sum, v) => sum + (v.weight ?? 1) * v.confidence, 0);
131
- const avgConfidence = totalWeight > 0 ? totalWeight / votes.length : 0.5;
136
+ .reduce((sum, v) => sum + weightOf(v) * v.confidence, 0);
137
+ const weightedConfidenceSum = votes.reduce((sum, v) => sum + weightOf(v) * v.confidence, 0);
138
+ const weightSum = votes.reduce((sum, v) => sum + weightOf(v), 0);
139
+ const rawMean = weightSum > 0 ? weightedConfidenceSum / weightSum : 0.5;
140
+ const avgConfidence = Math.min(1, Math.max(0, rawMean));
132
141
  let finalDecision = 'needs_revision';
133
- if (totalWeight > 0) {
134
- const approveRatio = approveWeight / totalWeight;
142
+ if (weightedConfidenceSum > 0) {
143
+ const approveRatio = approveWeight / weightedConfidenceSum;
135
144
  if (approveRatio > 0.66) {
136
145
  finalDecision = 'approve';
137
146
  }
@@ -136,22 +136,24 @@ export class GovernanceService {
136
136
  let finalDecision = merged.finalDecision;
137
137
  let averageConfidence = merged.averageConfidence;
138
138
  let moralOverride = 'none';
139
- if (externalVote?.moralTension ||
140
- externalVote?.moralScore != null ||
141
- externalVote?.moralFusion != null) {
139
+ if (externalVote &&
140
+ (externalVote.moralTension ||
141
+ externalVote.moralScore != null ||
142
+ externalVote.moralFusion != null)) {
142
143
  const matrixInput = {
143
144
  resonance: averageConfidence,
144
- isotopicRatio: externalVote.moralFusion ?? 0.5,
145
145
  };
146
+ const fusion = externalVote.moralFusion;
147
+ if (typeof fusion === 'number' && Number.isFinite(fusion)) {
148
+ matrixInput.isotopicRatio = fusion;
149
+ matrixInput.moralFusion = fusion;
150
+ }
146
151
  if (externalVote.moralTension != null) {
147
152
  matrixInput.moralTension = externalVote.moralTension;
148
153
  }
149
154
  if (externalVote.moralScore != null) {
150
155
  matrixInput.moralScore = externalVote.moralScore;
151
156
  }
152
- if (externalVote.moralFusion != null) {
153
- matrixInput.moralFusion = externalVote.moralFusion;
154
- }
155
157
  const matrix = applyDecisionMatrix(matrixInput);
156
158
  moralOverride = matrix.moralOverride ?? 'none';
157
159
  if (matrix.recommendation === 'REJECT') {
@@ -10,16 +10,30 @@ import { generateProposals } from "./inference-proposal-generator.js";
10
10
  import { applyProposals as applyProposalsEx } from "./inference-applier.js";
11
11
  const CYCLE_STATE_FILE = "inference-cycle-state.json";
12
12
  const CYCLE_HISTORY_FILE = "inference-cycle-history.json";
13
+ function governanceToolText(result) {
14
+ if (typeof result !== "object" || result === null || !("content" in result)) {
15
+ return "";
16
+ }
17
+ const content = result.content;
18
+ const text = content?.[0]?.text;
19
+ return typeof text === "string" ? text : "";
20
+ }
13
21
  export class InferenceCycle {
14
22
  static instances = new Map();
15
23
  static reEntryLock = false;
16
24
  static governedProposalIds = new Set();
17
25
  static getInstance(projectRoot, options) {
18
26
  const root = path.resolve(projectRoot || process.cwd());
19
- if (!InferenceCycle.instances.has(root)) {
20
- InferenceCycle.instances.set(root, new InferenceCycle(root, undefined, options));
27
+ const existing = InferenceCycle.instances.get(root);
28
+ if (existing) {
29
+ if (options) {
30
+ existing.options = { ...existing.options, ...options };
31
+ }
32
+ return existing;
21
33
  }
22
- return InferenceCycle.instances.get(root);
34
+ const created = new InferenceCycle(root, undefined, options);
35
+ InferenceCycle.instances.set(root, created);
36
+ return created;
23
37
  }
24
38
  static resetInstance() {
25
39
  InferenceCycle.instances.clear();
@@ -121,7 +135,10 @@ export class InferenceCycle {
121
135
  const proposals = generateProposals(corpus, this.loadHistory());
122
136
  if (proposals.length === 0) {
123
137
  this.setPhase("complete");
124
- return this.buildResult(cycleId, true, threshold.reason, startTime, corpus, proposals);
138
+ this.saveCycleState(cycleId);
139
+ const emptyResult = this.buildResult(cycleId, true, threshold.reason, startTime, corpus, proposals);
140
+ this.appendHistory(emptyResult);
141
+ return emptyResult;
125
142
  }
126
143
  this.setPhase("governing");
127
144
  let votes = [];
@@ -223,7 +240,7 @@ export class InferenceCycle {
223
240
  }),
224
241
  new Promise((_, reject) => setTimeout(() => reject(new Error("Governance MCP timed out after 8s")), 8000)),
225
242
  ]);
226
- const text = result?.content?.[0]?.text || "";
243
+ const text = governanceToolText(result);
227
244
  const parsed = this.parseGovernanceMcpResponse(text, proposals);
228
245
  frameworkLogger.log("inference-cycle", "governance-mcp-primary-path", "info", {
229
246
  proposalCount: proposals.length,
@@ -2,24 +2,25 @@
2
2
 
3
3
  Thin adapter. Repo `.cursor/hooks.json` (schema **version 1**) calls 0xRay gates and the Station PreCompact writer. **sessionStart is not used** — managed Cursor cloud does not fire it.
4
4
 
5
- Repertoire MCP is optional. Station + gates work without a full memory-routing rewire.
5
+ Repertoire is preferred (vendored, dest = named laws). Station + Codex gates still run if memory-routing is explicitly off — that is fallback, not the preferred brain.
6
6
 
7
7
  ## How cloud picks up project hooks
8
8
 
9
9
  1. Commit `.cursor/hooks.json` at the **project root** (this file, not `~/.cursor/hooks.json`).
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
- 3. Hook names are camelCase: `preToolUse`, `preCompact`, `afterFileEdit`. Commands are shell strings. Stdin is JSON; stdout is JSON.
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.
13
- 5. `preCompact` is observational (cannot block). It merges Station (stock heat updates; ticket / seed / Durable / unfinished keys stay).
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
+ 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`.
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.
14
15
 
15
- Copy the consumer template from `src/integrations/cursor/hooks/hooks.json` (dist paths under `node_modules/0xray`) when wearing the published package. 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. `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
17
 
17
18
  ## Contracts
18
19
 
19
20
  | Hook | Stdin | Stdout |
20
21
  |------|--------|--------|
21
22
  | `preToolUse` | Cursor tool event (`tool_name`, `tool_input`, `cwd`, `workspace_roots`, `conversation_id`) | `{ "permission": "allow" \| "deny" }` plus `user_message` / `agent_message` on deny |
22
- | `preCompact` | `{ trigger, context_tokens, … }` | `{ "user_message": "…" }` after Station write |
23
+ | `preCompact` | `{ trigger, context_tokens, conversation_id, generation_id, … }` | `{ "user_message": "…" }` after Station write. Receipt keeps `conversation_id` + `generation_id`. That id is enough to resume. |
23
24
  | `afterFileEdit` | file event | `{}` — boots Station only |
24
25
 
25
26
  `preCompact` labels `event_class`:
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ HERE="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
3
+ exec /bin/sh "${HERE}/xray-cloud-hook.sh" afterFileEdit after-file-edit.js
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ HERE="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
3
+ exec /bin/sh "${HERE}/xray-cloud-hook.sh" beforeReadFile pre-tool-use.js
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ HERE="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
3
+ exec /bin/sh "${HERE}/xray-cloud-hook.sh" beforeShellExecution pre-tool-use.js
@@ -84,32 +84,123 @@ function addHeatRoot(out, seen, value) {
84
84
  out.push(resolved);
85
85
  }
86
86
 
87
+ export function isCursorWorkspaceWrapper(root) {
88
+ if (!root) return false;
89
+ const resolved = path.resolve(String(root));
90
+ return (
91
+ fs.existsSync(path.join(resolved, 'repos', 'xray')) ||
92
+ fs.existsSync(path.join(resolved, 'repos', 'repertoire'))
93
+ );
94
+ }
95
+
96
+ export function heatRootHasCard(root) {
97
+ if (!root) return false;
98
+ const resolved = path.resolve(String(root));
99
+ return (
100
+ fs.existsSync(path.join(resolved, '.xray', 'state', 'STATION.md')) ||
101
+ fs.existsSync(path.join(resolved, '.xray', 'features.json'))
102
+ );
103
+ }
104
+
105
+ export function isHeatRootWritable(root) {
106
+ if (!root) return false;
107
+ const resolved = path.resolve(String(root));
108
+ try {
109
+ const probe = path.join(resolved, '.xray', 'state');
110
+ fs.mkdirSync(probe, { recursive: true });
111
+ fs.accessSync(probe, fs.constants.W_OK);
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ export function shouldHeatRoot(root) {
119
+ if (!root) return false;
120
+ const resolved = path.resolve(String(root));
121
+ if (isCursorWorkspaceWrapper(resolved)) return false;
122
+ if (heatRootHasCard(resolved)) return isHeatRootWritable(resolved);
123
+ return isHeatRootWritable(resolved);
124
+ }
125
+
126
+ export function millRootFromToolPath(filePath) {
127
+ if (!filePath) return null;
128
+ const resolved = path.resolve(String(filePath));
129
+ const marker = `${path.sep}repos${path.sep}`;
130
+ const idx = resolved.indexOf(marker);
131
+ if (idx === -1) return null;
132
+ const after = resolved.slice(idx + marker.length);
133
+ const repo = after.split(path.sep)[0];
134
+ if (!repo) return null;
135
+ return resolved.slice(0, idx + marker.length + repo.length);
136
+ }
137
+
138
+ function addMillCardsUnderWrapper(out, seen, wrapper) {
139
+ if (!wrapper || !isCursorWorkspaceWrapper(wrapper)) return;
140
+ const mill = path.join(path.resolve(String(wrapper)), 'repos', 'xray');
141
+ if (heatRootHasCard(mill) && isHeatRootWritable(mill)) addHeatRoot(out, seen, mill);
142
+ }
143
+
87
144
  /** Every live context that already has a Station card — cwd plus mill when they differ. */
88
145
  export function cursorHeatRoots(event = {}) {
89
146
  const seen = new Set();
90
147
  const out = [];
91
- addHeatRoot(out, seen, event.cwd);
92
- addHeatRoot(out, seen, event.workspaceRoot);
148
+ const candidates = [event.cwd, event.workspaceRoot];
93
149
  const listed = event.workspace_roots || event.workspaceRoots;
94
150
  if (Array.isArray(listed)) {
95
151
  for (const row of listed) {
96
- if (typeof row === 'string') addHeatRoot(out, seen, row);
97
- else if (row && typeof row === 'object' && row.path) addHeatRoot(out, seen, row.path);
152
+ if (typeof row === 'string') candidates.push(row);
153
+ else if (row && typeof row === 'object' && row.path) candidates.push(row.path);
98
154
  }
99
155
  }
100
- addHeatRoot(out, seen, process.env.CURSOR_PROJECT_DIR);
101
- const mill = process.env.XRAY_AI_PATH;
156
+ candidates.push(process.env.CURSOR_PROJECT_DIR);
157
+ for (const value of candidates) {
158
+ if (shouldHeatRoot(value)) addHeatRoot(out, seen, value);
159
+ addMillCardsUnderWrapper(out, seen, value);
160
+ }
161
+ const mill = process.env.XRAY_AI_PATH || process.env.XRAY_ROOT;
102
162
  if (mill) {
103
163
  const resolved = path.resolve(String(mill));
104
- const millHasCard =
105
- fs.existsSync(path.join(resolved, '.xray', 'state', 'STATION.md')) ||
106
- fs.existsSync(path.join(resolved, '.xray', 'features.json'));
107
- if (millHasCard) addHeatRoot(out, seen, resolved);
164
+ if (shouldHeatRoot(resolved)) addHeatRoot(out, seen, resolved);
165
+ }
166
+ const ctx = cursorToolContext(event);
167
+ for (const filePath of ctx.paths) {
168
+ const fromPath = millRootFromToolPath(filePath);
169
+ if (
170
+ fromPath &&
171
+ fs.existsSync(path.join(fromPath, '.xray', 'state', 'STATION.md')) &&
172
+ isHeatRootWritable(fromPath)
173
+ ) {
174
+ addHeatRoot(out, seen, fromPath);
175
+ }
176
+ }
177
+ if (out.length === 0) {
178
+ const fallback = cursorWorkspaceRoot(event);
179
+ if (shouldHeatRoot(fallback)) addHeatRoot(out, seen, fallback);
180
+ addMillCardsUnderWrapper(out, seen, fallback);
108
181
  }
109
- if (out.length === 0) addHeatRoot(out, seen, cursorWorkspaceRoot(event));
110
182
  return out;
111
183
  }
112
184
 
185
+ function packageNameAt(root) {
186
+ try {
187
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
188
+ return typeof pkg.name === 'string' ? pkg.name : '';
189
+ } catch {
190
+ return '';
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Constitution follows the suit mill when that package is in the heat set.
196
+ * A sibling repo's leftover synthesis checkpoint must not deny the host.
197
+ */
198
+ export function cursorGateRoot(heatRoots, eventRoot) {
199
+ const mill = heatRoots.find((root) => packageNameAt(root) === '0xray');
200
+ if (mill) return mill;
201
+ return heatRoots[0] || eventRoot;
202
+ }
203
+
113
204
  export function cursorSessionId(event = {}) {
114
205
  return (
115
206
  event.conversation_id ||
@@ -121,6 +212,10 @@ export function cursorSessionId(event = {}) {
121
212
  );
122
213
  }
123
214
 
215
+ export function cursorGenerationId(event = {}) {
216
+ return event.generation_id || event.generationId || null;
217
+ }
218
+
124
219
  export function cursorToolContext(event = {}) {
125
220
  const toolName = event.tool_name || event.toolName || process.env.TOOL_NAME || 'unknown';
126
221
  const toolInput = event.tool_input ?? event.toolInput ?? {};
@@ -179,11 +274,16 @@ export function cursorBootNeedsRefresh(existing, root) {
179
274
 
180
275
  export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-use-boot', extra = {}) {
181
276
  const bootPath = sessionBootPath(root);
277
+ if (!shouldHeatRoot(root)) return bootPath;
182
278
  if (fs.existsSync(bootPath)) {
183
279
  try {
184
280
  const existing = JSON.parse(fs.readFileSync(bootPath, 'utf8'));
185
281
  if (!cursorBootNeedsRefresh(existing, root)) {
186
- heatLiveMemory(root);
282
+ try {
283
+ heatLiveMemory(root);
284
+ } catch {
285
+ /* dest EACCES must not deny the host tool */
286
+ }
187
287
  return bootPath;
188
288
  }
189
289
  } catch {
@@ -69,6 +69,9 @@ export function hostUsageFromPreCompactEvent(event = {}) {
69
69
  if (typeof event.windowCite === 'string' && event.windowCite) usage.windowCite = event.windowCite;
70
70
  if (typeof event.model === 'string' && event.model) usage.model = event.model;
71
71
  if (typeof event.bcId === 'string' && event.bcId) usage.bcId = event.bcId;
72
+ else if (typeof event.conversation_id === 'string' && event.conversation_id) {
73
+ usage.bcId = event.conversation_id;
74
+ }
72
75
  return usage;
73
76
  }
74
77