0xray 4.0.24 → 4.0.26

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.24",
3
+ "version": "4.0.26",
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.24",
3
+ "version": "4.0.26",
4
4
  "description": "0xRay Framework - Unified Feature Configuration",
5
5
  "suit_temperament": {
6
6
  "profile": "auto",
package/dist/CHANGELOG.md CHANGED
@@ -41,8 +41,47 @@ 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.26] - 2026-09-25
45
+
46
+ ### 🔄 Changes
47
+
48
+ ### 🐛 Bug Fixes
49
+ - fix: store a lesson when the task matches no signal (e3c75b43d)
50
+ - fix: prove a Cursor compact cut with a session log and per-session receipt (bb3a1c68f)
51
+ - fix: mill check accepts a published version; mcp:status runs (a8bd79e23)
52
+
53
+ ### 🔎 Other Changes
54
+ - Merge pull request #116 from 0xRayAI/cursor/mill-ci-mcp-status-fbe0 (ba94508d2)
55
+ - Merge pull request #120 from 0xRayAI/cursor/lesson-recall-fbe0 (8f4a5a62a)
56
+ - Merge pull request #119 from 0xRayAI/cursor/compact-receipt-proof-fbe0 (a911fd17b)
57
+
58
+ ---
59
+
60
+ ## [4.0.25] - 2026-09-24
61
+
62
+ `0xray@4.0.25` ships the plate schematics in the package. `docs-site/docs/plates/` is on the npm `files` list. A worn suit resolves `memory-recall` and `processor` from `dist/integrations/hooks/plates.cjs`.
63
+
64
+ ### 🔄 Changes
65
+
66
+ ### 🐛 Bug Fixes
67
+ - fix: ship plate schematics in the npm package (b1bdd7415)
68
+
69
+ ### 📚 Documentation
70
+ - docs: announce 4.0.24 — hot-swap, memory organ, plates (b04b80a15)
71
+
72
+ ---
73
+
44
74
  ## [4.0.24] - 2026-09-24
45
75
 
76
+ `0xray@4.0.24` is on npm. `npm install 0xray`.
77
+
78
+ Exo, not a catalog. A factory stamps a skeleton (Inference, External Governance, Autonomous Engine) and fastens a mill. Code mill, music mill (`@0xray/blip` sound), agents across TUIs and LLMs wear it and operate.
79
+
80
+ - **Hot-swappable compaction.** Compaction and host change are the same cut. The station card (`.xray/state/STATION.md`) is the handoff. The next turn continues the card.
81
+ - **Memory organ.** Finished speech and reflections become lessons on signals. Kernel and activity logs are heat (`last_seen`) only. Unnamed speech mints a signal. Caps: 20 hot lesson lines, 20 retained ids (insertion order), 24 learned proposed signals. Factory laws stay.
82
+ - **Plates.** One INPUT/PROCESSING/OUTPUT schematic per pipeline in `docs-site/docs/plates/`. Station keeps one pointer line. Recall injects that one schematic. A lesson is an episode. A plate is the machine.
83
+ - **Repertoire 0.2.8** is the organ package, vendored in 4.0.24.
84
+
46
85
  ### 🔄 Changes
47
86
 
48
87
  ### 🐛 Bug Fixes
@@ -1,3 +1,15 @@
1
+ export type McpInstallerAction = {
2
+ kind: "list" | "status" | "help";
3
+ } | {
4
+ kind: "install" | "remove";
5
+ name?: string;
6
+ } | {
7
+ kind: "unknown";
8
+ command: string;
9
+ };
10
+ /** Colon and hyphen forms documented on `npx 0xray` map to the bare dispatcher verbs. */
11
+ export declare function normalizeMcpInstallerToken(command: string | undefined): string | undefined;
12
+ export declare function classifyMcpInstallerArgs(args: readonly string[]): McpInstallerAction;
1
13
  export declare function listMCPsCommand(): void;
2
14
  export declare function showMCPStatusCommand(): void;
3
15
  export declare function installMCPCommand(name: string): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from "fs";
2
- import { join, dirname } from "path";
2
+ import { join, dirname, resolve } from "path";
3
3
  import { execSync } from "child_process";
4
+ import { fileURLToPath } from "url";
4
5
  import { getConfigDir } from "../../core/config-paths.js";
5
6
  function getMCPConfigPath() {
6
7
  return join(process.cwd(), ".xray", "mcp-registry.json");
@@ -186,9 +187,44 @@ function removeMCP(name) {
186
187
  saveInstalledMCPs(installed);
187
188
  console.log(`\n✅ Removed ${name}`);
188
189
  }
190
+ const MCP_INSTALLER_ALIASES = {
191
+ "mcp:list": "list",
192
+ "mcp-list": "list",
193
+ "mcp:status": "status",
194
+ "mcp-status": "status",
195
+ "mcp:install": "install",
196
+ "mcp-install": "install",
197
+ "mcp:remove": "remove",
198
+ "mcp-remove": "remove",
199
+ };
200
+ /** Colon and hyphen forms documented on `npx 0xray` map to the bare dispatcher verbs. */
201
+ export function normalizeMcpInstallerToken(command) {
202
+ if (command === undefined)
203
+ return undefined;
204
+ return MCP_INSTALLER_ALIASES[command] ?? command;
205
+ }
206
+ export function classifyMcpInstallerArgs(args) {
207
+ const raw = args[0];
208
+ const command = normalizeMcpInstallerToken(raw);
209
+ if (command === undefined || command === "status")
210
+ return { kind: "status" };
211
+ if (command === "list")
212
+ return { kind: "list" };
213
+ if (command === "help" || command === "--help" || command === "-h")
214
+ return { kind: "help" };
215
+ if (command === "install" || command === "remove") {
216
+ const name = args[1];
217
+ return name === undefined ? { kind: command } : { kind: command, name };
218
+ }
219
+ return { kind: "unknown", command: raw ?? command };
220
+ }
221
+ const isDirectRun = process.argv[1] !== undefined &&
222
+ resolve(process.argv[1]) === fileURLToPath(import.meta.url);
189
223
  async function main() {
224
+ if (!isDirectRun)
225
+ return;
190
226
  const args = process.argv.slice(2);
191
- const command = args[0];
227
+ const command = normalizeMcpInstallerToken(args[0]);
192
228
  switch (command) {
193
229
  case "list":
194
230
  listMCPs();
@@ -22,6 +22,9 @@ export const VALIDATE_PACK_PATHS = [
22
22
  "dist/integrations/cursor/hooks/pre-tool-use.sh",
23
23
  "dist/integrations/cursor/hooks/pre-compact.sh",
24
24
  "dist/integrations/cursor/hooks/after-file-edit.sh",
25
+ "dist/integrations/hooks/plates.cjs",
26
+ "docs-site/docs/plates/memory-recall.md",
27
+ "docs-site/docs/plates/processor.md",
25
28
  ];
26
29
  export const CONSUMER_WEAR_PATHS = [
27
30
  "AGENTS.md",
@@ -40,7 +40,9 @@ Do **not** prove window pressure with UTF-8 chars÷4. `src/integrations/cursor/h
40
40
  | `cursor-cloud` `run-info` / `get-events` / dashboard (model, bc-id, window) | yes — token MISS is honest |
41
41
  | chars÷4, fill-only, `fillBytes` | **forbidden** (`ok: false`) |
42
42
 
43
- Host `preCompact` fire is counted from `.xray/state/cursor-hook-invoke.log` (`event=preCompact`). Do not hand-invoke `pre-compact.js` to mint that Y.
43
+ Host `preCompact` fire is counted from `.xray/state/cursor-hook-invoke.log` (`event=preCompact`). Do not hand-invoke `pre-compact.js` to mint that Y. That invoke log has no session id.
44
+
45
+ A cut counts only when `.xray/state/cursor-receipts/<sessionId>.json` exists **and** `.xray/state/cursor-hook.log` contains a line with that same session id (timestamp, `context_tokens`, `context_usage_percent`, `context_window_size`, `generation_id`). `probeLogExists` is true only when that append succeeded. `.xray/state/cursor-usage-receipt.json` and `cursor-precompact.json` keep only the newest event. That single latest json is not enough — the next arm overwrites it.
44
46
 
45
47
  Arm S landscape + receipt: `examples/killer-dual/`.
46
48
 
@@ -53,6 +53,11 @@ export {
53
53
  FORBIDDEN_USAGE_METHODS,
54
54
  invokeProbeLogPath,
55
55
  cursorUsageReceiptPath,
56
+ cursorHookLogPath,
57
+ sanitizeSessionReceiptId,
58
+ cursorSessionReceiptPath,
59
+ appendCursorCompactProofLog,
60
+ writeCursorSessionReceipt,
56
61
  parseInvokeProbeLog,
57
62
  hostUsageFromPreCompactEvent,
58
63
  classifyUsageCite,
@@ -28,6 +28,70 @@ export function cursorUsageReceiptPath(root) {
28
28
  return path.join(root, '.xray', 'state', 'cursor-usage-receipt.json');
29
29
  }
30
30
 
31
+ /** Proof log. A cut is greppable here; cursor-hook-invoke.log has no session id. */
32
+ export function cursorHookLogPath(root) {
33
+ return path.join(root, '.xray', 'state', 'cursor-hook.log');
34
+ }
35
+
36
+ function logToken(value) {
37
+ if (value == null) return '';
38
+ return String(value).replace(/[\r\n]+/g, '');
39
+ }
40
+
41
+ /**
42
+ * Safe filename segment for `.xray/state/cursor-receipts/<id>.json`.
43
+ * Empty when the id cannot be kept inside that directory.
44
+ */
45
+ export function sanitizeSessionReceiptId(sessionId) {
46
+ const raw = typeof sessionId === 'string' ? sessionId.trim() : '';
47
+ if (!raw) return '';
48
+ const safe = raw
49
+ .replace(/\0/g, '')
50
+ .replace(/[/\\]/g, '_')
51
+ .replace(/\.\./g, '_')
52
+ .replace(/[^A-Za-z0-9._-]+/g, '_')
53
+ .replace(/^[._]+/, '')
54
+ .replace(/[._]+$/g, '');
55
+ if (!safe || safe === '.' || safe === '..') return '';
56
+ return safe.slice(0, 180);
57
+ }
58
+
59
+ export function cursorSessionReceiptPath(root, sessionId) {
60
+ const safe = sanitizeSessionReceiptId(sessionId);
61
+ if (!safe) return null;
62
+ return path.join(root, '.xray', 'state', 'cursor-receipts', `${safe}.json`);
63
+ }
64
+
65
+ /**
66
+ * Append one preCompact proof line. True only when the append landed and
67
+ * the file contains that session id (grep of *.log can find the cut).
68
+ */
69
+ export function appendCursorCompactProofLog(root, fields = {}) {
70
+ const sessionId = logToken(fields.sessionId);
71
+ const timestamp = logToken(fields.timestamp) || new Date().toISOString();
72
+ const line = [
73
+ `ts=${timestamp}`,
74
+ `session_id=${sessionId}`,
75
+ `context_tokens=${logToken(fields.context_tokens)}`,
76
+ `context_usage_percent=${logToken(fields.context_usage_percent)}`,
77
+ `context_window_size=${logToken(fields.context_window_size)}`,
78
+ `generation_id=${logToken(fields.generation_id)}`,
79
+ ].join(' ');
80
+ const dest = cursorHookLogPath(root);
81
+ try {
82
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
83
+ fs.appendFileSync(dest, `${line}\n`);
84
+ } catch {
85
+ return false;
86
+ }
87
+ if (!sessionId) return false;
88
+ try {
89
+ return fs.readFileSync(dest, 'utf8').includes(`session_id=${sessionId}`);
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+
31
95
  export function parseInvokeProbeLog(root) {
32
96
  const dest = invokeProbeLogPath(root);
33
97
  const counts = { preToolUse: 0, preCompact: 0, afterFileEdit: 0, unknown: 0 };
@@ -228,7 +292,7 @@ export function buildCursorUsageReceipt(root, input = {}) {
228
292
  preToolUseCount: probe.counts.preToolUse,
229
293
  afterFileEditCount: probe.counts.afterFileEdit,
230
294
  eventClass,
231
- probeLogExists: probe.exists,
295
+ probeLogExists: input.probeLogExists === true,
232
296
  },
233
297
  usage,
234
298
  repertoire,
@@ -299,3 +363,32 @@ export function writeCursorUsageReceipt(root, input = {}) {
299
363
  return null;
300
364
  }
301
365
  }
366
+
367
+ /**
368
+ * Per-session copy of the latest precompact + usage receipts.
369
+ * The next arm overwrites cursor-precompact.json and cursor-usage-receipt.json.
370
+ */
371
+ export function writeCursorSessionReceipt(root, sessionId, paths = {}) {
372
+ const dest = cursorSessionReceiptPath(root, sessionId);
373
+ if (!dest) return null;
374
+ if (!paths.precompactPath && !paths.usagePath) return null;
375
+ try {
376
+ const payload = {
377
+ sessionId: typeof sessionId === 'string' ? sessionId : null,
378
+ precompact: null,
379
+ usage: null,
380
+ };
381
+ if (paths.precompactPath && fs.existsSync(paths.precompactPath)) {
382
+ payload.precompact = JSON.parse(fs.readFileSync(paths.precompactPath, 'utf8'));
383
+ }
384
+ if (paths.usagePath && fs.existsSync(paths.usagePath)) {
385
+ payload.usage = JSON.parse(fs.readFileSync(paths.usagePath, 'utf8'));
386
+ }
387
+ if (!payload.precompact && !payload.usage) return null;
388
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
389
+ fs.writeFileSync(dest, JSON.stringify(payload, null, 2));
390
+ return dest;
391
+ } catch {
392
+ return null;
393
+ }
394
+ }
@@ -18,7 +18,9 @@ import {
18
18
  writeSessionBoot,
19
19
  } from './cursor-hook-utils.js';
20
20
  import {
21
+ appendCursorCompactProofLog,
21
22
  hostUsageFromPreCompactEvent,
23
+ writeCursorSessionReceipt,
22
24
  writeCursorUsageReceipt,
23
25
  } from './cursor-usage-receipt.js';
24
26
 
@@ -132,6 +134,17 @@ async function main() {
132
134
  });
133
135
  bootPath = writeSessionBoot(root, payload);
134
136
  stationLine = payload.stationLine;
137
+ const stampedAt = new Date().toISOString();
138
+ const proofLogged = appendCursorCompactProofLog(root, {
139
+ sessionId,
140
+ timestamp: stampedAt,
141
+ context_tokens: typeof event.context_tokens === 'number' ? event.context_tokens : null,
142
+ context_usage_percent:
143
+ typeof event.context_usage_percent === 'number' ? event.context_usage_percent : null,
144
+ context_window_size:
145
+ typeof event.context_window_size === 'number' ? event.context_window_size : null,
146
+ generation_id: generationId,
147
+ });
135
148
  receiptPath = writeCursorPrecompactReceipt(root, {
136
149
  event_class: eventClass,
137
150
  hookEvent: 'pre_compact',
@@ -145,13 +158,18 @@ async function main() {
145
158
  messages_to_compact:
146
159
  typeof event.messages_to_compact === 'number' ? event.messages_to_compact : null,
147
160
  bootPath,
148
- timestamp: new Date().toISOString(),
161
+ timestamp: stampedAt,
149
162
  });
150
163
  usagePath = writeCursorUsageReceipt(root, {
151
164
  eventClass,
152
165
  sessionId,
166
+ probeLogExists: proofLogged,
153
167
  usage: hostUsageFromPreCompactEvent(event),
154
168
  });
169
+ writeCursorSessionReceipt(root, sessionId, {
170
+ precompactPath: receiptPath,
171
+ usagePath,
172
+ });
155
173
  if (plates) {
156
174
  try {
157
175
  const named = plates.recallPlate(payload.intent || intent || '');
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.24",
3
+ "version": "4.0.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -4,13 +4,18 @@ export interface LessonInput {
4
4
  taskId: string;
5
5
  assignedAgent: string;
6
6
  sessionId: string;
7
- /** Signals the finished task already named. When set, text matching is not used. */
7
+ /**
8
+ * Signals the finished task already named. When set, text matching is not used.
9
+ * An empty list with a lesson still stores that lesson under a name taken from the task.
10
+ */
8
11
  signals?: string[];
9
12
  /** Approaches, solutions, or the wrong turn from the sessions that named the law. */
10
13
  lesson?: string;
11
14
  }
12
15
  /**
13
16
  * A finished task names the signals its text actually matched and says
14
- * whether the outcome was good. That is the one-tenth step. No match, no write.
17
+ * whether the outcome was good. That is the one-tenth step.
18
+ * A lesson with no matched signal is stored under a hyphenated name from the
19
+ * task, so the same task text recalls it. No lesson and no match, no write.
15
20
  */
16
21
  export declare function recordLesson(input: LessonInput): string[];
@@ -1,14 +1,48 @@
1
1
  import { ensureMemoryRoutingProviderSync } from "./provider-registry.js";
2
+ /**
3
+ * Hyphenated phrase whose spaced form is still inside the task.
4
+ * A later routing read of that same task hits the name.
5
+ */
6
+ function signalNameForUnmatchedLesson(task) {
7
+ const words = task
8
+ .toLowerCase()
9
+ .split(/[^a-z0-9]+/)
10
+ .filter((word) => word.length >= 3);
11
+ const normalized = task.toLowerCase();
12
+ const kept = [];
13
+ for (const word of words) {
14
+ const next = [...kept, word];
15
+ const phrase = next.join(" ");
16
+ const hyphen = next.join("-");
17
+ if (!normalized.includes(phrase) || hyphen.length > 120)
18
+ break;
19
+ kept.push(word);
20
+ }
21
+ if (kept.length === 0)
22
+ return null;
23
+ return kept.join("-");
24
+ }
2
25
  /**
3
26
  * A finished task names the signals its text actually matched and says
4
- * whether the outcome was good. That is the one-tenth step. No match, no write.
27
+ * whether the outcome was good. That is the one-tenth step.
28
+ * A lesson with no matched signal is stored under a hyphenated name from the
29
+ * task, so the same task text recalls it. No lesson and no match, no write.
5
30
  */
6
31
  export function recordLesson(input) {
7
32
  const provider = ensureMemoryRoutingProviderSync();
8
33
  if (provider.id === "null" || !provider.ingestFeedback)
9
34
  return [];
10
- const named = input.signals ?? provider.buildRoutingContext(input.operation).matchedSignals;
11
- const names = [...new Set(named.filter((name) => name.length > 0))];
35
+ const lesson = (input.lesson ?? "").trim();
36
+ const supplied = input.signals;
37
+ const named = supplied !== undefined
38
+ ? supplied
39
+ : provider.buildRoutingContext(input.operation).matchedSignals;
40
+ let names = [...new Set(named.filter((name) => name.length > 0))];
41
+ if (names.length === 0 && lesson.length > 0) {
42
+ const coined = signalNameForUnmatchedLesson(input.operation);
43
+ if (coined)
44
+ names = [coined];
45
+ }
12
46
  if (names.length === 0)
13
47
  return [];
14
48
  provider.ingestFeedback({
@@ -0,0 +1,52 @@
1
+ ---
2
+ title: Boot
3
+ sidebar_label: Boot
4
+ ---
5
+
6
+ # Boot
7
+
8
+ `BootOrchestrator.executeBootSequence` brings the framework up. Processor registration here is the legacy boot list. The live tool gate is the suit pipeline, drawn on the processor plate.
9
+
10
+ ```
11
+ ┌────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ │
14
+ │ ┌─────────────────────┐ ┌────────────────────────┐ │
15
+ │ │ process start │ │ SIGINT / SIGTERM │ │
16
+ │ └──────────┬──────────┘ └───────────┬────────────┘ │
17
+ │ └──────────────┬──────────────┘ │
18
+ └─────────────────────────────┼──────────────────────────────┘
19
+ v
20
+ ┌─────────────────────────────┼──────────────────────────────┐
21
+ │ PROCESSING LAYER v │
22
+ │ ┌────────────────────────────────────────────────────┐ │
23
+ │ │ load .xray config │ │
24
+ │ └──────────────────────────┬─────────────────────────┘ │
25
+ │ v │
26
+ │ ┌────────────────────────────────────────────────────┐ │
27
+ │ │ orchestrator + delegation session │ │
28
+ │ └──────────────────────────┬─────────────────────────┘ │
29
+ │ v │
30
+ │ ┌────────────────────────────────────────────────────┐ │
31
+ │ │ session management · activity log │ │
32
+ │ └──────────────────────────┬─────────────────────────┘ │
33
+ │ v │
34
+ │ ┌────────────────────────────────────────────────────┐ │
35
+ │ │ activateProcessors (PROCESSOR_DEFS) │ │
36
+ │ │ legacy boot list, then agents and plugins │ │
37
+ │ └──────────────────────────┬─────────────────────────┘ │
38
+ │ v │
39
+ │ ┌────────────────────────────────────────────────────┐ │
40
+ │ │ enforcement · codex compliance │ │
41
+ │ │ Dynamo governance integration │ │
42
+ │ └──────────────────────────┬─────────────────────────┘ │
43
+ └──────────────────────────────┼────────────────────────────┘
44
+ v
45
+ ┌──────────────────────────────┼────────────────────────────┐
46
+ │ OUTPUT LAYER v │
47
+ │ ┌────────────────────────────────────────────────────┐ │
48
+ │ │ BootResult │ │
49
+ │ │ success · agents loaded · errors │ │
50
+ │ └────────────────────────────────────────────────────┘ │
51
+ └────────────────────────────────────────────────────────────┘
52
+ ```
@@ -0,0 +1,50 @@
1
+ ---
2
+ title: Governance
3
+ sidebar_label: Governance
4
+ ---
5
+
6
+ # Governance
7
+
8
+ A proposal is deliberated inside the suit, filtered by Dynamo Solar, then merged. The PreToolUse codex gate stands beside the vote.
9
+
10
+ ```
11
+ ┌────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ │
14
+ │ ┌────────────────────────────────────────────────────┐ │
15
+ │ │ proposal │ │
16
+ │ │ title · description · type │ │
17
+ │ └──────────────────────────┬─────────────────────────┘ │
18
+ └──────────────────────────────┼────────────────────────────┘
19
+ v
20
+ ┌──────────────────────────────┼────────────────────────────┐
21
+ │ PROCESSING LAYER v │
22
+ │ ┌────────────────────────────────────────────────────┐ │
23
+ │ │ internal deliberation │ │
24
+ │ │ code-review · security-audit · researcher │ │
25
+ │ └──────────────────────────┬─────────────────────────┘ │
26
+ │ v │
27
+ │ ┌────────────────────────────────────────────────────┐ │
28
+ │ │ external filter │ │
29
+ │ │ Dynamo Solar SSOT │ │
30
+ │ └──────────────────────────┬─────────────────────────┘ │
31
+ │ v │
32
+ │ ┌────────────────────────────────────────────────────┐ │
33
+ │ │ merge │ │
34
+ │ │ governance-core weighted vote · decision matrix │ │
35
+ │ └──────────────────────────┬─────────────────────────┘ │
36
+ │ │ │
37
+ │ ┌──────────────────────────┼─────────────────────────┐ │
38
+ │ │ PreToolUse gate │ │ │
39
+ │ │ Codex 11 · 29 · 69 │ beside the vote │ │
40
+ │ └──────────────────────────┼─────────────────────────┘ │
41
+ └──────────────────────────────┼────────────────────────────┘
42
+ v
43
+ ┌──────────────────────────────┼────────────────────────────┐
44
+ │ OUTPUT LAYER v │
45
+ │ ┌──────────────────────┐ ┌──────────────────────┐ │
46
+ │ │ vote │ │ codex decision │ │
47
+ │ │ approve / reject │ │ allow / deny │ │
48
+ │ └──────────────────────┘ └──────────────────────┘ │
49
+ └────────────────────────────────────────────────────────────┘
50
+ ```
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: Plates
3
+ sidebar_label: Overview
4
+ ---
5
+
6
+ # Plates
7
+
8
+ A plate is one pipeline, drawn as a stamp. The drawing is the thing a person reads: an input layer, a processing layer, an output layer, and the arrows between them.
9
+
10
+ These files are the canonical stamps. The Docusaurus sidebar publishes them, and the npm package ships this directory. The worn reader walks up from `dist/integrations/hooks/plates.cjs` to `docs-site/docs/plates/`. One plate, one file.
11
+
12
+ The station card stays the short subsystem table. That table is the index still on disk after compaction. When the current intent names one pipeline, the card adds a single stock line, `Plate: <id> — .xray/state/plates/<id>.md`. Recall returns that one plate, and a compact can carry its schematic. Lessons stay episodes on signals. A plate is the machine. A lesson is what happened.
13
+
14
+ The markdown file is the source of truth for that stamp. When the pipeline changes, edit the plate. A worn project may hold a copy at `.xray/state/plates/<id>.md`. The copy is written only when that file is missing, so a local edit stays.
15
+
16
+ [Routing](./routing.md) · [Governance](./governance.md) · [Boot](./boot.md) · [Orchestration](./orchestration.md) · [Processor](./processor.md) · [Reporting](./reporting.md) · [Memory recall](./memory-recall.md)
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: Memory recall
3
+ sidebar_label: Memory recall
4
+ ---
5
+
6
+ # Memory recall
7
+
8
+ Speech becomes a lesson. Intent names one plate. Compact points at the station card. The card names the stamp. The stamp file is the schematic.
9
+
10
+ ```
11
+ ┌──────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ │
14
+ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
15
+ │ │ speech │ │ intent │ │ compact cut │ │
16
+ │ └──────┬───────┘ └──────┬───────┘ └───────┬────────┘ │
17
+ └──────────┼──────────────────┼───────────────────┼───────────┘
18
+ v v v
19
+ ┌──────────┼──────────────────┼───────────────────┼───────────┐
20
+ │ PROCESSING LAYER │ │ │
21
+ │ v v v │
22
+ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
23
+ │ │ speech grades│ │ intent │ │ compact says │ │
24
+ │ │ to a lesson │ │ recalls one │ │ Read STATION.md│ │
25
+ │ │ on a signal │ │ plate │ │ │ │
26
+ │ └──────┬───────┘ └──────┬───────┘ └───────┬────────┘ │
27
+ │ │ │ │ │
28
+ │ │ v │ │
29
+ │ │ ┌────────────────────────────┐ │ │
30
+ │ │ │ card keeps the short │ │ │
31
+ │ │ │ subsystem table │ │ │
32
+ │ │ │ card names Plate: <id> │ │ │
33
+ │ │ └─────────────┬──────────────┘ │ │
34
+ └──────────┼───────────────────┼──────────────────┼──────────┘
35
+ v v v
36
+ ┌──────────┴───────────────────┴──────────────────┴──────────┐
37
+ │ OUTPUT LAYER │
38
+ │ │
39
+ │ ┌────────────────────────────┐ ┌─────────────────────┐ │
40
+ │ │ lesson on the signal │ │ stamp file │ │
41
+ │ │ an episode of what happened│ │ the schematic │ │
42
+ │ └────────────────────────────┘ │ docs-site/docs/ │ │
43
+ │ │ plates/<id>.md │ │
44
+ │ │ worn copy only when │ │
45
+ │ │ .xray/state/plates/ │ │
46
+ │ │ <id>.md is missing │ │
47
+ │ └─────────────────────┘ │
48
+ └──────────────────────────────────────────────────────────────┘
49
+ ```
@@ -0,0 +1,55 @@
1
+ ---
2
+ title: Orchestration
3
+ sidebar_label: Orchestration
4
+ ---
5
+
6
+ # Orchestration
7
+
8
+ Multi-step work is planned, run, and closed. Routing picks an agent. This plate is the coordination around that pick.
9
+
10
+ ```
11
+ ┌────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ │
14
+ │ ┌────────────────────────────────────────────────────┐ │
15
+ │ │ tasks │ │
16
+ │ │ descriptions · dependencies · type │ │
17
+ │ └──────────────────────────┬─────────────────────────┘ │
18
+ └──────────────────────────────┼────────────────────────────┘
19
+ v
20
+ ┌──────────────────────────────┼────────────────────────────┐
21
+ │ PROCESSING LAYER v │
22
+ │ ┌────────────────────────────────────────────────────┐ │
23
+ │ │ analyze-complexity │ │
24
+ │ │ ExecutionPlanner validates and plans │ │
25
+ │ └──────────────────────────┬─────────────────────────┘ │
26
+ │ v │
27
+ │ ┌────────────────────────────────────────────────────┐ │
28
+ │ │ orchestrate-task │ │
29
+ │ │ lead stays on the main thread │ │
30
+ │ └──────────────────────────┬─────────────────────────┘ │
31
+ │ v │
32
+ │ ┌────────────────────────────────────────────────────┐ │
33
+ │ │ spawnAside │ │
34
+ │ │ on orchestrate-task, analyze-complexity, │ │
35
+ │ │ and govern-and-apply │ │
36
+ │ │ observations from the three flows │ │
37
+ │ └──────────────────────────┬─────────────────────────┘ │
38
+ │ v │
39
+ │ ┌────────────────────────────────────────────────────┐ │
40
+ │ │ confidence gate │ │
41
+ │ │ getTaskConfidence when Repertoire is worn │ │
42
+ │ └──────────────────────────┬─────────────────────────┘ │
43
+ │ v │
44
+ │ ┌────────────────────────────────────────────────────┐ │
45
+ │ │ closeAside │ │
46
+ │ └──────────────────────────┬─────────────────────────┘ │
47
+ └──────────────────────────────┼────────────────────────────┘
48
+ v
49
+ ┌──────────────────────────────┼────────────────────────────┐
50
+ │ OUTPUT LAYER v │
51
+ │ ┌─────────────────────┐ ┌─────────────────────────┐ │
52
+ │ │ execution result │ │ aside observations │ │
53
+ │ └─────────────────────┘ └─────────────────────────┘ │
54
+ └────────────────────────────────────────────────────────────┘
55
+ ```
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: Processor
3
+ sidebar_label: Processor
4
+ ---
5
+
6
+ # Processor
7
+
8
+ Three pipelines run in parallel. A is the live suit. B is the legacy processor manager. C is mill, git, and CI. Nothing in A is an input to B or C.
9
+
10
+ ```
11
+ ┌────────────────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ │
14
+ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────────┐ │
15
+ │ │ tool / session │ │ prompt text │ │ commit / release │ │
16
+ │ └────────┬─────────┘ └────────┬─────────┘ └─────────┬──────────┘ │
17
+ └────────────┼─────────────────────┼──────────────────────┼─────────────┘
18
+ v v v
19
+ ┌────────────┼─────────────────────┼──────────────────────┼─────────────┐
20
+ │ PROCESSING LAYER three parallel pipelines │ │
21
+ │ │ │ │ │
22
+ │ ┌─────────┴──────────┐ ┌────────┴─────────┐ ┌──────────┴──────────┐ │
23
+ │ │ A suit (live OS) │ │ B legacy manager │ │ C mill / git / CI │ │
24
+ │ │ │ │ │ │ │ │
25
+ │ │ SessionStart │ │ prompt-security │ │ mint │ │
26
+ │ │ v │ │ v │ │ v │ │
27
+ │ │ PreToolUse │ │ executePre │ │ inspect │ │
28
+ │ │ v │ │ v │ │ v │ │
29
+ │ │ tool runs │ │ work │ │ pre-commit │ │
30
+ │ │ v │ │ v │ │ v │ │
31
+ │ │ PostToolUse │ │ executePost │ │ pre-push │ │
32
+ │ │ v │ │ │ │ v │ │
33
+ │ │ compact heat │ │ │ │ foundry release │ │
34
+ │ │ │ │ │ │ v │ │
35
+ │ │ │ │ │ │ CI quality → docs │ │
36
+ │ └─────────┬──────────┘ └────────┬─────────┘ └──────────┬──────────┘ │
37
+ └────────────┼─────────────────────┼──────────────────────┼────────────┘
38
+ v v v
39
+ ┌────────────┴─────────────────────┴──────────────────────┴────────────┐
40
+ │ OUTPUT LAYER │
41
+ │ │
42
+ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────────┐ │
43
+ │ │ allow / deny │ │ processor result │ │ gate / tag │ │
44
+ │ └──────────────────┘ └──────────────────┘ └────────────────────┘ │
45
+ └────────────────────────────────────────────────────────────────────────┘
46
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: Reporting
3
+ sidebar_label: Reporting
4
+ ---
5
+
6
+ # Reporting
7
+
8
+ Logs become a report. Session start can also drop a schedule marker. The marker is a side output, not a second chain through the formatter.
9
+
10
+ ```
11
+ ┌────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ │
14
+ │ ┌──────────────────┐ ┌──────────────┐ ┌─────────────┐ │
15
+ │ │ activity log │ │ schedule │ │ CLI request │ │
16
+ │ └────────┬─────────┘ └──────┬───────┘ └──────┬──────┘ │
17
+ │ └───────────────────┴────────┬────────┘ │
18
+ └─────────────────────────────────────────┼──────────────────┘
19
+ v
20
+ ┌─────────────────────────────────────────┼──────────────────┐
21
+ │ PROCESSING LAYER v │
22
+ │ ┌────────────────────────────────────────────────────┐ │
23
+ │ │ FrameworkReportingSystem │ │
24
+ │ │ collect logs → metrics → formatReport │ │
25
+ │ │ cache a fresh report, then write the file │ │
26
+ │ └──────────────────────────┬─────────────────────────┘ │
27
+ │ │ │
28
+ │ ┌──────────────────────────┼─────────────────────────┐ │
29
+ │ │ SessionStart marker │ │ │
30
+ │ │ autonomous-reporting │ side output │ │
31
+ │ │ scheduled flag │ │ │
32
+ │ └──────────────────────────┼─────────────────────────┘ │
33
+ └──────────────────────────────┼────────────────────────────┘
34
+ v
35
+ ┌──────────────────────────────┼────────────────────────────┐
36
+ │ OUTPUT LAYER v │
37
+ │ ┌──────────────────────────┐ ┌─────────────────────┐ │
38
+ │ │ report file │ │ realtime health │ │
39
+ │ │ markdown or json │ │ score and alerts │ │
40
+ │ └──────────────────────────┘ └─────────────────────┘ │
41
+ └────────────────────────────────────────────────────────────┘
42
+ ```
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: Routing
3
+ sidebar_label: Routing
4
+ ---
5
+
6
+ # Routing
7
+
8
+ Task text becomes an agent. thinDispatch scores the work. Repertoire may adjust the score when the organ is worn.
9
+
10
+ ```
11
+ ┌──────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ │
14
+ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
15
+ │ │ task text │ │ @agent │ │ scoreAndRoute │ │
16
+ │ └──────┬───────┘ └──────┬───────┘ └───────┬────────┘ │
17
+ │ └──────────────────┴─────────┬─────────┘ │
18
+ └────────────────────────────────────────┼─────────────────────┘
19
+ v
20
+ ┌────────────────────────────────────────┼─────────────────────┐
21
+ │ PROCESSING LAYER │ │
22
+ │ v │
23
+ │ ┌──────────────────────────────────────────────────────┐ │
24
+ │ │ TaskSkillRouter │ │
25
+ │ │ keyword facade into the route │ │
26
+ │ └──────────────────────────┬───────────────────────────┘ │
27
+ │ v │
28
+ │ ┌──────────────────────────────────────────────────────┐ │
29
+ │ │ thinDispatch │ │
30
+ │ │ ≤15 single agent │ │
31
+ │ │ ≤25 single agent with tools │ │
32
+ │ │ ≤50 multi-agent │ │
33
+ │ │ >50 orchestrator-led │ │
34
+ │ └──────────────────────────┬───────────────────────────┘ │
35
+ │ v │
36
+ │ ┌──────────────────────────────────────────────────────┐ │
37
+ │ │ Repertoire resolveThinDispatch │ │
38
+ │ │ worn organ may change agent and score │ │
39
+ │ │ null provider leaves the score alone │ │
40
+ │ └──────────────────────────┬───────────────────────────┘ │
41
+ └──────────────────────────────┼──────────────────────────────┘
42
+ v
43
+ ┌──────────────────────────────┼──────────────────────────────┐
44
+ │ OUTPUT LAYER v │
45
+ │ ┌────────────┐ ┌────────────────┐ ┌────────────────┐ │
46
+ │ │ agent │ │ strategy │ │ adjusted score │ │
47
+ │ └────────────┘ └────────────────┘ └────────────────┘ │
48
+ └──────────────────────────────────────────────────────────────┘
49
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.24",
3
+ "version": "4.0.26",
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",
@@ -179,6 +179,7 @@
179
179
  "src/opencode/workflows/",
180
180
  "src/integrations/grok/plugin/0xray/",
181
181
  "src/integrations/openclaw/plugin/",
182
+ "docs-site/docs/plates/",
182
183
  "src/skills/",
183
184
  "src/mcps/",
184
185
  ".plugin/",
@@ -28,6 +28,9 @@ export const REQUIRED_PACK_PATHS = [
28
28
  "dist/integrations/cursor/hooks/pre-tool-use.sh",
29
29
  "dist/integrations/cursor/hooks/pre-compact.sh",
30
30
  "dist/integrations/cursor/hooks/after-file-edit.sh",
31
+ "dist/integrations/hooks/plates.cjs",
32
+ "docs-site/docs/plates/memory-recall.md",
33
+ "docs-site/docs/plates/processor.md",
31
34
  ];
32
35
 
33
36
  export function packedFilePaths(files) {
@@ -11,7 +11,9 @@
11
11
  * npx @0xray/foundry reconcile patch --apply
12
12
  *
13
13
  * --apply writes package.json and the package-lock.json version fields.
14
- * --check fails when the lock does not match package.json.
14
+ * --check passes when package.json is equal to the registry (already published)
15
+ * or greater (the pre-publish bump). It fails when package.json is behind npm,
16
+ * or when the lock does not match package.json.
15
17
  */
16
18
 
17
19
  import { execSync } from "child_process";
@@ -91,6 +93,21 @@ function fail(msg) {
91
93
  process.exit(1);
92
94
  }
93
95
 
96
+ /**
97
+ * Registry gate for --check.
98
+ * Equal to npm is a shipped release. Greater is the pre-publish bump.
99
+ * Only a package.json behind the registry fails.
100
+ * @param {string} local
101
+ * @param {string} npmVersion
102
+ * @returns {string | null}
103
+ */
104
+ export function publishedVersionCheckError(local, npmVersion) {
105
+ if (compare(local, npmVersion) < 0) {
106
+ return `package.json (${local}) must be >= npm (${npmVersion}). Run: npx @0xray/foundry reconcile patch --apply`;
107
+ }
108
+ return null;
109
+ }
110
+
94
111
  function main() {
95
112
  const local = readLocalVersion();
96
113
  const npm = readPublishedVersion();
@@ -138,9 +155,12 @@ function main() {
138
155
  return;
139
156
  }
140
157
 
141
- // Strict checks for release gate / pre-tag
142
- if (compare(local, npm) <= 0) {
143
- fail(`package.json (${local}) must be > npm (${npm}). Run: npx @0xray/foundry reconcile patch --apply`);
158
+ // Strict checks for release gate / pre-tag.
159
+ // `local` is the version read before --apply. Apply still writes
160
+ // bumpVersion(max(npm, tag)) and is not this gate.
161
+ const behindNpm = publishedVersionCheckError(local, npm);
162
+ if (behindNpm) {
163
+ fail(behindNpm);
144
164
  }
145
165
 
146
166
  const pkgVersion = readLocalVersion();
@@ -67,6 +67,9 @@ const REQUIRED_TARBALL_DIST = [
67
67
  "package/dist/cli/index.js",
68
68
  "package/dist/mcps/orchestrator.server.js",
69
69
  "package/dist/integrations/grok/hooks/session-start.js",
70
+ "package/dist/integrations/hooks/plates.cjs",
71
+ "package/docs-site/docs/plates/memory-recall.md",
72
+ "package/docs-site/docs/plates/processor.md",
70
73
  ];
71
74
 
72
75
  function assertPackedDistCliInTarball(tarballPath) {
@@ -78,7 +81,7 @@ function assertPackedDistCliInTarball(tarballPath) {
78
81
  `packed tarball missing ${missing.join(", ")} (0xray@4.0.13 shipped 0 dist files)`,
79
82
  );
80
83
  }
81
- process.stdout.write(" ✅ packed tarball includes dist/cli + MCP + Grok hooks\n");
84
+ process.stdout.write(" ✅ packed tarball includes dist/cli + MCP + Grok hooks + plate stamps\n");
82
85
  }
83
86
 
84
87
  function assertOrganRequire(tmpRoot) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.24",
3
+ "version": "4.0.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./features.schema.json",
3
- "version": "4.0.24",
3
+ "version": "4.0.26",
4
4
  "description": "0xRay Framework - Unified Feature Configuration",
5
5
  "suit_temperament": {
6
6
  "profile": "auto",