0xray 4.0.22 → 4.0.24

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 (103) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.opencode/skills/orchestrator/SKILL.md +7 -7
  3. package/.xray/features.json +11 -1
  4. package/.xray/features.schema.json +17 -0
  5. package/AGENTS-consumer.md +1 -1
  6. package/AGENTS.md +1 -1
  7. package/README.md +2 -2
  8. package/SKILLS.md +1 -1
  9. package/dist/AGENTS.md +1 -1
  10. package/dist/CHANGELOG.md +44 -1
  11. package/dist/README.md +2 -2
  12. package/dist/SKILLS.md +1 -1
  13. package/dist/cli/index.js +131 -100
  14. package/dist/core/features-config.d.ts +4 -0
  15. package/dist/core/features-config.js +10 -0
  16. package/dist/inference/inference-accumulator.d.ts +2 -0
  17. package/dist/inference/inference-accumulator.js +110 -6
  18. package/dist/inference/inference-cycle.d.ts +12 -0
  19. package/dist/inference/inference-cycle.js +159 -16
  20. package/dist/inference/inference-proposal-generator.js +206 -5
  21. package/dist/inference/inference-run-gate.d.ts +8 -0
  22. package/dist/inference/inference-run-gate.js +10 -0
  23. package/dist/inference/session-capture.d.ts +31 -0
  24. package/dist/inference/session-capture.js +277 -1
  25. package/dist/integrations/cursor/hooks/pre-compact.js +89 -1
  26. package/dist/integrations/governance/index.d.ts +5 -0
  27. package/dist/integrations/governance/index.js +22 -2
  28. package/dist/integrations/governance/types.d.ts +11 -0
  29. package/dist/integrations/governance/types.js +16 -0
  30. package/dist/integrations/hooks/plates.cjs +165 -0
  31. package/dist/integrations/hooks/station-hook-runtime.cjs +29 -11
  32. package/dist/integrations/hooks/station-memory-ingest.mjs +109 -42
  33. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  34. package/dist/mcps/governance.server.d.ts +2 -0
  35. package/dist/mcps/governance.server.js +12 -1
  36. package/dist/mcps/researcher-confidence.d.ts +5 -1
  37. package/dist/mcps/researcher-confidence.js +37 -0
  38. package/dist/mcps/researcher.server.js +2 -2
  39. package/dist/memory-routing/index.d.ts +1 -1
  40. package/dist/memory-routing/index.js +1 -1
  41. package/dist/memory-routing/lesson-pickup.d.ts +2 -0
  42. package/dist/memory-routing/lesson-pickup.js +57 -0
  43. package/dist/memory-routing/plates.d.ts +16 -0
  44. package/dist/memory-routing/plates.js +71 -0
  45. package/dist/memory-routing/provider-loader.d.ts +5 -0
  46. package/dist/memory-routing/provider-loader.js +46 -0
  47. package/dist/memory-routing/provider-registry.d.ts +7 -2
  48. package/dist/memory-routing/provider-registry.js +26 -6
  49. package/dist/memory-routing/record-lesson.d.ts +16 -0
  50. package/dist/memory-routing/record-lesson.js +26 -0
  51. package/dist/memory-routing/types.d.ts +17 -0
  52. package/dist/memory-routing/types.js +2 -1
  53. package/dist/nucleus/thin-dispatch.d.ts +2 -0
  54. package/dist/nucleus/thin-dispatch.js +3 -2
  55. package/dist/processors/implementations/inference-improvement-processor.d.ts +5 -0
  56. package/dist/processors/implementations/inference-improvement-processor.js +101 -2
  57. package/dist/processors/implementations/storytelling-trigger-processor.js +1 -11
  58. package/dist/processors/processor-manager.d.ts +1 -1
  59. package/dist/processors/processor-manager.js +52 -20
  60. package/dist/skills/orchestrator/SKILL.md +7 -7
  61. package/llms.txt +1 -1
  62. package/package.json +1 -1
  63. package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
  64. package/scripts/foundry/reconcile-version.mjs +23 -1
  65. package/scripts/foundry/release.mjs +1 -1
  66. package/scripts/foundry/version-manager.mjs +53 -0
  67. package/scripts/node/consumer-install-smoke.mjs +3 -3
  68. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  69. package/src/mcps/governance.server.ts +12 -1
  70. package/src/mcps/researcher-confidence.ts +56 -3
  71. package/src/mcps/researcher.server.ts +4 -5
  72. package/src/skills/orchestrator/SKILL.md +7 -7
  73. package/vendor/@0xray/repertoire/README.md +3 -1
  74. package/vendor/@0xray/repertoire/data/stack-overlay.json +4 -4
  75. package/vendor/@0xray/repertoire/data/subject-overlay.json +3 -3
  76. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts +4 -0
  77. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +1 -1
  78. package/vendor/@0xray/repertoire/dist/RepertoireService.js +4 -4
  79. package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +1 -1
  80. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +1 -1
  81. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +1 -1
  82. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +1 -1
  83. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js +5 -0
  84. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +1 -1
  85. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +1 -1
  86. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js +16 -0
  87. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +1 -1
  88. package/vendor/@0xray/repertoire/dist/paths.d.ts +3 -3
  89. package/vendor/@0xray/repertoire/dist/paths.d.ts.map +1 -1
  90. package/vendor/@0xray/repertoire/dist/paths.js.map +1 -1
  91. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts +11 -0
  92. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +1 -1
  93. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js +2 -0
  94. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +1 -1
  95. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts +74 -4
  96. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +1 -1
  97. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +431 -13
  98. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +1 -1
  99. package/vendor/@0xray/repertoire/dist/types.d.ts +21 -0
  100. package/vendor/@0xray/repertoire/dist/types.d.ts.map +1 -1
  101. package/vendor/@0xray/repertoire/package.json +1 -1
  102. package/xray/features.json +11 -1
  103. package/xray/features.schema.json +17 -0
@@ -22,6 +22,74 @@ import {
22
22
  writeCursorUsageReceipt,
23
23
  } from './cursor-usage-receipt.js';
24
24
 
25
+ async function loadPlates() {
26
+ const { existsSync } = await import('node:fs');
27
+ const { dirname, join } = await import('node:path');
28
+ const { fileURLToPath, pathToFileURL } = await import('node:url');
29
+ const here = dirname(fileURLToPath(import.meta.url));
30
+ const candidates = [
31
+ join(here, '../../hooks/plates.cjs'),
32
+ join(here, '../../../integrations/hooks/plates.cjs'),
33
+ ];
34
+ const found = candidates.find((candidate) => existsSync(candidate));
35
+ if (!found) return null;
36
+ const loaded = await import(pathToFileURL(found).href);
37
+ if (typeof loaded.recallPlate === 'function') return loaded;
38
+ if (loaded.default && typeof loaded.default.recallPlate === 'function') return loaded.default;
39
+ return null;
40
+ }
41
+
42
+ async function memoryRoutingModule() {
43
+ const { existsSync } = await import('node:fs');
44
+ const { dirname, join } = await import('node:path');
45
+ const { fileURLToPath, pathToFileURL } = await import('node:url');
46
+ const here = dirname(fileURLToPath(import.meta.url));
47
+ const candidates = [
48
+ join(here, '../../../memory-routing/index.js'),
49
+ join(here, '../../../../dist/memory-routing/index.js'),
50
+ ];
51
+ const found = candidates.find((candidate) => existsSync(candidate));
52
+ if (!found) throw new Error('memory-routing module missing');
53
+ return pathToFileURL(found).href;
54
+ }
55
+
56
+ async function lessonSpeechForIntent(root, intent) {
57
+ if (!intent) return [];
58
+ try {
59
+ const { readFileSync, existsSync } = await import('node:fs');
60
+ const { join } = await import('node:path');
61
+ const { loadMemoryRoutingProvider } = await import(await memoryRoutingModule());
62
+ let routing = { enabled: false, provider: 'null' };
63
+ const featuresPath = join(root, '.xray', 'features.json');
64
+ if (existsSync(featuresPath)) {
65
+ try {
66
+ routing = JSON.parse(readFileSync(featuresPath, 'utf8')).memory_routing || routing;
67
+ } catch {
68
+ /* leftover default */
69
+ }
70
+ }
71
+ const provider = await loadMemoryRoutingProvider(routing, root);
72
+ if (!provider || provider.id === 'null' || typeof provider.buildRoutingContext !== 'function') {
73
+ return [];
74
+ }
75
+ const context = provider.buildRoutingContext(String(intent));
76
+ const matched = new Set(Array.isArray(context.matchedSignals) ? context.matchedSignals : []);
77
+ const lines = [];
78
+ for (const lesson of context.lessons || []) {
79
+ if (!matched.has(lesson.name)) continue;
80
+ for (const line of lesson.lines || []) {
81
+ const text = String(line.text || '').trim().slice(0, 400);
82
+ if (text.length === 0) continue;
83
+ lines.push(`${lesson.name}: ${text}`);
84
+ if (lines.length >= 4) return lines;
85
+ }
86
+ }
87
+ return lines;
88
+ } catch {
89
+ return [];
90
+ }
91
+ }
92
+
25
93
  function extractIntent(event) {
26
94
  return (
27
95
  event.prompt ||
@@ -47,6 +115,13 @@ async function main() {
47
115
  let receiptPath = null;
48
116
  let usagePath = null;
49
117
  let stationLine = null;
118
+ let plates = null;
119
+ let plateBody = '';
120
+ try {
121
+ plates = await loadPlates();
122
+ } catch {
123
+ plates = null;
124
+ }
50
125
  for (const root of cursorHeatRoots(event)) {
51
126
  const payload = buildSessionBootPayload(root, '0xray/cursor-compact', {
52
127
  host: 'cursor',
@@ -77,6 +152,17 @@ async function main() {
77
152
  sessionId,
78
153
  usage: hostUsageFromPreCompactEvent(event),
79
154
  });
155
+ if (plates) {
156
+ try {
157
+ const named = plates.recallPlate(payload.intent || intent || '');
158
+ if (named) {
159
+ plates.stampPlateIfMissing(root, named.id);
160
+ if (!plateBody) plateBody = `\n\n${String(named.body || '').trim()}`;
161
+ }
162
+ } catch {
163
+ /* plate stamp is observational */
164
+ }
165
+ }
80
166
  }
81
167
  appendHookActivity(eventRoot, 'cursor-pre-compact', 'station-written', 'success', {
82
168
  bootPath,
@@ -85,10 +171,12 @@ async function main() {
85
171
  event_class: eventClass,
86
172
  stationLine,
87
173
  });
174
+ const lessonLines = await lessonSpeechForIntent(eventRoot, intent);
175
+ const lessonNote = lessonLines.length > 0 ? ` Lessons: ${lessonLines.join(' | ')}` : '';
88
176
  console.log(
89
177
  JSON.stringify({
90
178
  user_message:
91
- `0xRay Station written. Read .xray/state/STATION.md — do not cold-start. event_class=${eventClass}`,
179
+ `0xRay Station written. Read .xray/state/STATION.md — do not cold-start. event_class=${eventClass}${lessonNote}${plateBody}`,
92
180
  }),
93
181
  );
94
182
  process.exit(0);
@@ -39,6 +39,11 @@ export declare class InferenceGovernanceIntegration extends BaseIntegration {
39
39
  * Check if governance is enabled and available
40
40
  */
41
41
  isAvailable(): boolean;
42
+ /**
43
+ * Start the Dynamo client when a caller required external governance
44
+ * and boot skipped it because inference_governance.enabled is false.
45
+ */
46
+ ensureDynamoClient(): Promise<void>;
42
47
  /**
43
48
  * Check a single inference proposal via the appropriate governance tool
44
49
  *
@@ -7,7 +7,7 @@
7
7
  * @since 2026-05-11
8
8
  */
9
9
  import { BaseIntegration } from '../base/index.js';
10
- import { DEFAULT_GOVERNANCE_CONFIG } from './types.js';
10
+ import { DEFAULT_GOVERNANCE_CONFIG, resolveDynamoBaseUrl } from './types.js';
11
11
  import { GovernanceClient } from './governance-client.js';
12
12
  import { frameworkLogger } from '../../core/framework-logger.js';
13
13
  import { featuresConfigLoader } from '../../core/features-config.js';
@@ -84,7 +84,27 @@ export class InferenceGovernanceIntegration extends BaseIntegration {
84
84
  * Check if governance is enabled and available
85
85
  */
86
86
  isAvailable() {
87
- return this.configData.enabled && this.client !== null && this.status === 'initialized';
87
+ return this.client !== null && this.status === 'initialized';
88
+ }
89
+ /**
90
+ * Start the Dynamo client when a caller required external governance
91
+ * and boot skipped it because inference_governance.enabled is false.
92
+ */
93
+ async ensureDynamoClient() {
94
+ if (this.status !== 'initialized') {
95
+ await this.initialize();
96
+ }
97
+ if (this.client !== null) {
98
+ return;
99
+ }
100
+ const endpoint = resolveDynamoBaseUrl(this.configData.endpointUrl);
101
+ this.client = new GovernanceClient({
102
+ baseUrl: endpoint,
103
+ timeoutMs: this.configData.requestTimeoutMs,
104
+ });
105
+ await this.log('info', 'Dynamo client armed for required external governance', {
106
+ endpoint,
107
+ });
88
108
  }
89
109
  /**
90
110
  * Check a single inference proposal via the appropriate governance tool
@@ -56,6 +56,17 @@ export interface GovernanceIntegrationConfig {
56
56
  voteWeightMultiplier: number;
57
57
  };
58
58
  }
59
+ /**
60
+ * Public Dynamo Solar SSOT. Used only when a caller requires external
61
+ * governance and neither features nor GOVERNANCE_ENDPOINT names a host.
62
+ * The client posts to /call_connected_tool on this origin.
63
+ */
64
+ export declare const PUBLIC_DYNAMO_SSOT = "https://mcp-production-80e2.up.railway.app";
65
+ /**
66
+ * Origin for POST /call_connected_tool.
67
+ * Preference: configured endpoint, then GOVERNANCE_ENDPOINT, then the public SSOT.
68
+ */
69
+ export declare function resolveDynamoBaseUrl(configured: string): string;
59
70
  /**
60
71
  * Default governance configuration
61
72
  */
@@ -6,6 +6,22 @@
6
6
  *
7
7
  * @since 2026-05-11
8
8
  */
9
+ /**
10
+ * Public Dynamo Solar SSOT. Used only when a caller requires external
11
+ * governance and neither features nor GOVERNANCE_ENDPOINT names a host.
12
+ * The client posts to /call_connected_tool on this origin.
13
+ */
14
+ export const PUBLIC_DYNAMO_SSOT = 'https://mcp-production-80e2.up.railway.app';
15
+ /**
16
+ * Origin for POST /call_connected_tool.
17
+ * Preference: configured endpoint, then GOVERNANCE_ENDPOINT, then the public SSOT.
18
+ */
19
+ export function resolveDynamoBaseUrl(configured) {
20
+ const fromConfig = configured.trim();
21
+ const fromEnv = (process.env.GOVERNANCE_ENDPOINT || '').trim();
22
+ const raw = fromConfig || fromEnv || PUBLIC_DYNAMO_SSOT;
23
+ return raw.replace(/\/governance\/?$/, '').replace(/\/$/, '');
24
+ }
9
25
  /**
10
26
  * Default governance configuration
11
27
  */
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Runtime plate reader. One implementation.
3
+ * src/memory-routing/plates.ts is the typed API and delegates here so
4
+ * Station (CJS) and the Cursor preCompact hook (plain node) cannot drift.
5
+ */
6
+ const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs");
7
+ const { dirname, join } = require("path");
8
+
9
+ const PLATE_IDS = Object.freeze([
10
+ "routing",
11
+ "governance",
12
+ "boot",
13
+ "orchestration",
14
+ "processor",
15
+ "reporting",
16
+ "memory-recall",
17
+ ]);
18
+
19
+ /** Id token, then extra phrases. Equal top scores recall nothing. */
20
+ const CUES = {
21
+ routing: [
22
+ [/\brouting\b/i, 5],
23
+ [/thindispatch/i, 4],
24
+ [/task-skill-router|taskskillrouter/i, 4],
25
+ [/scoreandroute/i, 3],
26
+ [/\broute(?:s|d)?\b/i, 3],
27
+ ],
28
+ governance: [
29
+ [/\bgovernance\b/i, 5],
30
+ [/rule-enforcer|ruleenforcer/i, 4],
31
+ [/dynamo solar/i, 3],
32
+ ],
33
+ boot: [
34
+ [/\bboot\b/i, 5],
35
+ [/boot-orchestrator|bootorchestrator/i, 4],
36
+ [/executebootsequence/i, 4],
37
+ ],
38
+ orchestration: [
39
+ [/\borchestrat\w*/i, 5],
40
+ [/aside-?context/i, 4],
41
+ [/spawnaside|spawn aside/i, 3],
42
+ [/analyze-complexity/i, 2],
43
+ [/govern-and-apply/i, 2],
44
+ ],
45
+ processor: [
46
+ [/\bprocessors?\b/i, 5],
47
+ [/pre[\s-]+processors?/i, 6],
48
+ [/post[\s-]+processors?/i, 4],
49
+ [/executepreprocessors|execute-pre-processors/i, 6],
50
+ [/processor-?manager/i, 4],
51
+ ],
52
+ reporting: [
53
+ [/\breporting\b/i, 5],
54
+ [/framework-reporting|framework report/i, 4],
55
+ [/activity report/i, 4],
56
+ [/\breports?\b/i, 2],
57
+ ],
58
+ "memory-recall": [
59
+ [/\bmemory-recall\b/i, 5],
60
+ [/memory\s+recall/i, 5],
61
+ [/recall(?:s|ed|ing)?\s+(?:a\s+|one\s+|the\s+)?plate/i, 5],
62
+ [/stamp(?:ed|ing)?\s+plates?/i, 4],
63
+ ],
64
+ };
65
+
66
+ function platesDir() {
67
+ let dir = __dirname;
68
+ for (let depth = 0; depth < 8; depth += 1) {
69
+ const candidate = join(dir, "docs-site", "docs", "plates");
70
+ if (existsSync(join(candidate, "index.md"))) return candidate;
71
+ const parent = dirname(dir);
72
+ if (parent === dir) break;
73
+ dir = parent;
74
+ }
75
+ return null;
76
+ }
77
+
78
+ function assertPlateId(id) {
79
+ if (!PLATE_IDS.includes(id)) {
80
+ throw new Error(`unknown plate: ${id}`);
81
+ }
82
+ }
83
+
84
+ function plateSourcePath(id) {
85
+ assertPlateId(id);
86
+ const dir = platesDir();
87
+ if (!dir) throw new Error("plate docs missing");
88
+ const sourcePath = join(dir, `${id}.md`);
89
+ if (!existsSync(sourcePath)) throw new Error(`plate file missing: ${id}`);
90
+ return sourcePath;
91
+ }
92
+
93
+ function stripFrontmatter(markdown) {
94
+ return String(markdown).replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "").trim();
95
+ }
96
+
97
+ function loadPlate(id) {
98
+ const sourcePath = plateSourcePath(id);
99
+ const raw = readFileSync(sourcePath, "utf8");
100
+ return {
101
+ id,
102
+ body: stripFrontmatter(raw),
103
+ sourcePath,
104
+ };
105
+ }
106
+
107
+ function scorePlate(id, text) {
108
+ const cues = CUES[id] || [];
109
+ let score = 0;
110
+ for (const [pattern, weight] of cues) {
111
+ if (pattern.test(text)) score += weight;
112
+ }
113
+ return score;
114
+ }
115
+
116
+ function recallPlate(intent) {
117
+ const text = String(intent || "").trim();
118
+ if (!text || !platesDir()) return null;
119
+ let bestId = null;
120
+ let best = 0;
121
+ let second = 0;
122
+ for (const id of PLATE_IDS) {
123
+ const score = scorePlate(id, text);
124
+ if (score > best) {
125
+ second = best;
126
+ best = score;
127
+ bestId = id;
128
+ } else if (score > second) {
129
+ second = score;
130
+ }
131
+ }
132
+ if (!bestId || best === 0 || best === second) return null;
133
+ return loadPlate(bestId);
134
+ }
135
+
136
+ function wornPlatePath(projectRoot, id) {
137
+ assertPlateId(id);
138
+ return join(projectRoot, ".xray", "state", "plates", `${id}.md`);
139
+ }
140
+
141
+ function stampPlateIfMissing(projectRoot, id) {
142
+ const path = wornPlatePath(projectRoot, id);
143
+ if (existsSync(path)) {
144
+ return { id, path, written: false };
145
+ }
146
+ const sourcePath = plateSourcePath(id);
147
+ mkdirSync(dirname(path), { recursive: true });
148
+ writeFileSync(path, readFileSync(sourcePath, "utf8"));
149
+ return { id, path, written: true };
150
+ }
151
+
152
+ function plateStockLine(intent) {
153
+ const plate = recallPlate(intent);
154
+ if (!plate) return null;
155
+ return `Plate: ${plate.id} — .xray/state/plates/${plate.id}.md`;
156
+ }
157
+
158
+ module.exports = {
159
+ PLATE_IDS,
160
+ loadPlate,
161
+ recallPlate,
162
+ stampPlateIfMissing,
163
+ plateStockLine,
164
+ wornPlatePath,
165
+ };
@@ -20,6 +20,7 @@ const {
20
20
  const { join } = require("path");
21
21
 
22
22
  const HOOKS_DIR = __dirname;
23
+ const { plateStockLine } = require("./plates.cjs");
23
24
 
24
25
  const INTENT_MAX = 240;
25
26
 
@@ -673,7 +674,11 @@ function heatLiveMemory(root) {
673
674
  }
674
675
  }
675
676
 
676
- /** Every floor that heats. Dedup per HEAD. Patterns come from git, not a leftover catalog. */
677
+ /**
678
+ * Station note on HEAD move. Dedup per HEAD.
679
+ * Commit subjects are match text for the card, not a graded lesson.
680
+ * Do not mint session-<date>-<short HEAD> under docs/inference.
681
+ */
677
682
  function maybeCaptureSessionOnHeadMove(root) {
678
683
  const cfg = readSessionCaptureConfig(root);
679
684
  if (!cfg.enabled) return null;
@@ -690,34 +695,28 @@ function maybeCaptureSessionOnHeadMove(root) {
690
695
  ? gitCommitSubjects(root, [`${stamp.head}..HEAD`])
691
696
  : gitCommitSubjects(root, ["-n", String(cfg.lookback)]);
692
697
  if (commits.length < cfg.minCommits) return null;
693
- const sessionId = `session-${new Date().toISOString().slice(0, 10)}-${git.head}`;
694
698
  const approaches = commits.map((row) => row.message).filter(Boolean);
695
699
  const span = {
696
700
  from: stamp && stamp.head ? stamp.head : commits[commits.length - 1] ? commits[commits.length - 1].hash : git.head,
697
701
  to: git.head,
698
702
  };
699
703
  const patterns = patternsFromGit(root, commits, span);
700
- const session = {
701
- sessionId,
704
+ const stationNote = {
702
705
  timestamp: new Date().toISOString(),
703
706
  span,
704
- problems: [],
705
707
  approaches,
706
- wrongTurns: [],
707
- solutions: [],
708
708
  patterns,
709
709
  matched_primitives: preferLawHits(patterns.map((row) => row && row.name)),
710
710
  metrics: { commits: commits.length },
711
711
  };
712
712
  const outDir = join(root, "docs", "inference");
713
713
  mkdirSync(outDir, { recursive: true });
714
- const filePath = join(outDir, `session-${sessionId.replace(/^session-/, "")}.json`);
715
- writeFileSync(filePath, `${JSON.stringify(session, null, 2)}\n`);
716
- writeFileSync(join(outDir, "latest-session.json"), `${JSON.stringify(session, null, 2)}\n`);
714
+ const filePath = join(outDir, "latest-session.json");
715
+ writeFileSync(filePath, `${JSON.stringify(stationNote, null, 2)}\n`);
717
716
  mkdirSync(join(root, ".xray", "state"), { recursive: true });
718
717
  writeFileSync(
719
718
  sessionCaptureStampPath(root),
720
- `${JSON.stringify({ head: git.head, sessionId, path: filePath, updatedAt: session.timestamp }, null, 2)}\n`,
719
+ `${JSON.stringify({ head: git.head, path: filePath, updatedAt: stationNote.timestamp }, null, 2)}\n`,
721
720
  );
722
721
  return filePath;
723
722
  }
@@ -1045,6 +1044,20 @@ const STOCK_STATION_PREFIXES = [
1045
1044
  "git:",
1046
1045
  "repertoire:",
1047
1046
  "working:",
1047
+ "plate:",
1048
+ ];
1049
+
1050
+ /** Stock design map. Exact lines so a later heat does not preserve a second copy. */
1051
+ const DESIGN_MAP_LINES = [
1052
+ "Subsystem map:",
1053
+ "+----------------------+------------------------------------------+",
1054
+ "| Subsystem | After compact |",
1055
+ "+----------------------+------------------------------------------+",
1056
+ "| Inference | proposals, reflection, Repertoire |",
1057
+ "| External Governance | Dynamo vote, Codex |",
1058
+ "| Autonomous Engine | thinDispatch, AsideContext |",
1059
+ "| Lessons | hot lines on the signal; this card |",
1060
+ "+----------------------+------------------------------------------+",
1048
1061
  ];
1049
1062
 
1050
1063
  const STOCK_STATION_FOOTERS = [
@@ -1099,6 +1112,7 @@ function isStockStationLine(line) {
1099
1112
  const trimmed = String(line || "").trim();
1100
1113
  if (!trimmed) return true;
1101
1114
  if (/^#\s+station\s*$/i.test(trimmed)) return true;
1115
+ if (DESIGN_MAP_LINES.includes(trimmed)) return true;
1102
1116
  const lower = trimmed.toLowerCase();
1103
1117
  if (STOCK_STATION_FOOTERS.includes(lower)) return true;
1104
1118
  return STOCK_STATION_PREFIXES.some((prefix) => lower.startsWith(prefix));
@@ -1171,6 +1185,10 @@ function formatStationMarkdown(fields) {
1171
1185
  if (fields.workingLine) {
1172
1186
  lines.push(fields.workingLine);
1173
1187
  }
1188
+ const plateLine = plateStockLine(fields.intent);
1189
+ if (plateLine) lines.push(plateLine);
1190
+ lines.push("");
1191
+ lines.push(...DESIGN_MAP_LINES);
1174
1192
  lines.push("");
1175
1193
  lines.push("Continue this card. Compaction and host change are the same cut. Do not cold-start.");
1176
1194
  lines.push("Grok does not inject this file — Read it. OpenCode injects. Do not thicken the Grok exo.");
@@ -3,10 +3,24 @@
3
3
  * Station organ write — compact ingest + wake grow.
4
4
  * Grow uses the worn RepertoireService. Groover field stays off.
5
5
  */
6
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
7
- import { join } from 'node:path';
6
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { join, resolve } from 'node:path';
8
8
  import { pathToFileURL } from 'node:url';
9
9
 
10
+ /** Tail cap for diary files the kernel list does not open. Heat only. */
11
+ const UNREAD_DIARY_CAP = 80_000;
12
+
13
+ /**
14
+ * Suit logs with a writer and no brain reader.
15
+ * Graded sessions and the five kernel-diary files stay on their existing readers.
16
+ * latest-session.json stays a station note, not a corpus session.
17
+ */
18
+ const UNREAD_DIARY_FILES = [
19
+ ['logs', 'framework', 'activity-report.json'],
20
+ ['logs', 'framework', 'plugin-tool-events.log'],
21
+ ['.xray', 'inference', 'postprocessor-light-latest.json'],
22
+ ];
23
+
10
24
  const root = process.argv[2];
11
25
  const mode = process.argv[3] || 'station';
12
26
 
@@ -95,6 +109,50 @@ function readCleanupDiary(projectRoot) {
95
109
  return { text: parts.join('\n'), sources };
96
110
  }
97
111
 
112
+ function pushMatching(dir, pattern, into) {
113
+ if (!existsSync(dir)) return;
114
+ let names = [];
115
+ try {
116
+ names = readdirSync(dir);
117
+ } catch {
118
+ return;
119
+ }
120
+ const matched = names.filter((name) => pattern.test(name)).sort().reverse();
121
+ for (const name of matched) into.push(join(dir, name));
122
+ }
123
+
124
+ /**
125
+ * Diary, activity, and workflow files the kernel candidate list does not open.
126
+ * Text is heat for last_seen. It is not a lesson and not a graded session.
127
+ */
128
+ export function collectUnreadSuitDiary(projectRoot) {
129
+ const files = UNREAD_DIARY_FILES.map((parts) => join(projectRoot, ...parts));
130
+ pushMatching(join(projectRoot, '.xray', 'inference'), /^workflow-\d+\.json$/, files);
131
+ pushMatching(join(projectRoot, 'logs', 'monitoring'), /^memory-monitor-.+\.log$/, files);
132
+ pushMatching(join(projectRoot, '.opencode', 'logs'), /^xray-plugin-.+\.log$/, files);
133
+ const sources = [];
134
+ const chunks = [];
135
+ let used = 0;
136
+ for (const file of files) {
137
+ const resolved = resolve(file);
138
+ if (!existsSync(resolved) || sources.includes(resolved)) continue;
139
+ let raw = '';
140
+ try {
141
+ raw = readFileSync(resolved, 'utf8');
142
+ } catch {
143
+ continue;
144
+ }
145
+ if (!raw.trim()) continue;
146
+ const remain = UNREAD_DIARY_CAP - used;
147
+ if (remain <= 0) break;
148
+ const slice = raw.length > remain ? raw.slice(-remain) : raw;
149
+ chunks.push(slice);
150
+ sources.push(resolved);
151
+ used += slice.length;
152
+ }
153
+ return { text: chunks.join('\n'), sources };
154
+ }
155
+
98
156
  async function growDest(projectRoot) {
99
157
  const routing = readRouting(projectRoot);
100
158
  if (isExplicitOptOut(routing)) {
@@ -123,9 +181,10 @@ async function growDest(projectRoot) {
123
181
  ? pathsMod.collectKernelDiaryText(projectRoot)
124
182
  : { text: '', sources: [] };
125
183
  const cleanup = readCleanupDiary(projectRoot);
184
+ const unread = collectUnreadSuitDiary(projectRoot);
126
185
  const diary = service.heatKernelDiary({
127
- text: [kernel.text, cleanup.text].filter(Boolean).join('\n'),
128
- sources: [...(kernel.sources || []), ...cleanup.sources],
186
+ text: [kernel.text, cleanup.text, unread.text].filter(Boolean).join('\n'),
187
+ sources: [...(kernel.sources || []), ...cleanup.sources, ...unread.sources],
129
188
  });
130
189
  const after = service.signalsManager.load().signals.length;
131
190
  const heatedNames = Array.isArray(diary.heated) ? diary.heated : [];
@@ -147,49 +206,57 @@ async function growDest(projectRoot) {
147
206
  process.stdout.write(`${JSON.stringify({ skipped: 'no-organ' })}\n`);
148
207
  }
149
208
 
150
- if (!root) {
151
- process.exit(0);
209
+ function invokedAsScript() {
210
+ const entry = process.argv[1];
211
+ if (!entry) return false;
212
+ return import.meta.url === pathToFileURL(entry).href;
152
213
  }
153
214
 
154
- if (mode === '--grow') {
155
- await growDest(root);
156
- process.exit(0);
157
- }
215
+ if (invokedAsScript()) {
216
+ if (!root) {
217
+ process.exit(0);
218
+ }
158
219
 
159
- const sessionId = mode;
160
- const hookEvent = process.argv[4] || 'post_compact';
161
- let signals = [];
162
- try {
163
- signals = JSON.parse(process.argv[5] || '[]');
164
- } catch {
165
- signals = [];
166
- }
167
- if (!Array.isArray(signals) || signals.length === 0) {
168
- process.exit(0);
169
- }
220
+ if (mode === '--grow') {
221
+ await growDest(root);
222
+ process.exit(0);
223
+ }
170
224
 
171
- let routing = { enabled: false, provider: 'null' };
172
- const featuresPath = join(root, '.xray', 'features.json');
173
- if (existsSync(featuresPath)) {
225
+ const sessionId = mode;
226
+ const hookEvent = process.argv[4] || 'post_compact';
227
+ let signals = [];
174
228
  try {
175
- routing = JSON.parse(readFileSync(featuresPath, 'utf8')).memory_routing || routing;
229
+ signals = JSON.parse(process.argv[5] || '[]');
176
230
  } catch {
177
- /* leftover */
231
+ signals = [];
232
+ }
233
+ if (!Array.isArray(signals) || signals.length === 0) {
234
+ process.exit(0);
178
235
  }
179
- }
180
236
 
181
- const { loadMemoryRoutingProvider } = await import('../../memory-routing/index.js');
182
- const provider = await loadMemoryRoutingProvider(routing, root);
183
- if (!provider || typeof provider.ingestFeedback !== 'function') process.exit(0);
184
-
185
- provider.ingestFeedback({
186
- timestamp: new Date().toISOString(),
187
- sessionId,
188
- taskId: `station-${hookEvent}`,
189
- assignedAgent: 'station',
190
- memorySignals: signals,
191
- complexity: 0,
192
- success: true,
193
- durationMs: 0,
194
- });
195
- process.exit(0);
237
+ let routing = { enabled: false, provider: 'null' };
238
+ const featuresPath = join(root, '.xray', 'features.json');
239
+ if (existsSync(featuresPath)) {
240
+ try {
241
+ routing = JSON.parse(readFileSync(featuresPath, 'utf8')).memory_routing || routing;
242
+ } catch {
243
+ /* leftover */
244
+ }
245
+ }
246
+
247
+ const { loadMemoryRoutingProvider } = await import('../../memory-routing/index.js');
248
+ const provider = await loadMemoryRoutingProvider(routing, root);
249
+ if (!provider || typeof provider.ingestFeedback !== 'function') process.exit(0);
250
+
251
+ provider.ingestFeedback({
252
+ timestamp: new Date().toISOString(),
253
+ sessionId,
254
+ taskId: `station-${hookEvent}`,
255
+ assignedAgent: 'station',
256
+ memorySignals: signals,
257
+ complexity: 0,
258
+ success: true,
259
+ durationMs: 0,
260
+ });
261
+ process.exit(0);
262
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.22",
3
+ "version": "4.0.24",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -20,6 +20,8 @@ declare class GovernanceServer extends XrayKnowledgeSkillBase {
20
20
  protected setupToolHandlers(): void;
21
21
  /** Align tool default with features.json inference_governance.enabled (P0.2). */
22
22
  private resolveRequireExternal;
23
+ /** Boot stays flag-gated. A required external call starts the client on demand. */
24
+ private armRequiredDynamo;
23
25
  private handleGovernProposals;
24
26
  private handleGovernReflection;
25
27
  private handleGetActiveCodex;