0xray 4.0.34 → 4.0.36

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 (160) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.xray/codex.json +12 -4
  3. package/.xray/features.json +1 -1
  4. package/AGENTS-consumer.md +6 -6
  5. package/AGENTS.md +5 -5
  6. package/README.md +2 -2
  7. package/dist/AGENTS.md +5 -5
  8. package/dist/CHANGELOG.md +26 -0
  9. package/dist/README.md +2 -2
  10. package/dist/cli/index.js +2 -5
  11. package/dist/governance/governance-service.js +1 -1
  12. package/dist/integrations/base/README.md +1 -9
  13. package/dist/integrations/base/index.d.ts +0 -1
  14. package/dist/integrations/base/index.js +0 -2
  15. package/dist/integrations/governance/index.js +31 -8
  16. package/dist/integrations/grok/hooks/grok-hook-utils.js +1 -1
  17. package/dist/integrations/grok/hooks/session-start.js +12 -0
  18. package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +6 -6
  19. package/dist/integrations/hooks/goggles-mcp.mjs +10 -3
  20. package/dist/integrations/hooks/goggles-pipeline.mjs +110 -23
  21. package/dist/integrations/hooks/host-truth.mjs +74 -0
  22. package/dist/integrations/hooks/station-hook-runtime.cjs +3 -0
  23. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  24. package/dist/mcps/knowledge-skills/storyteller.server.js +0 -1
  25. package/dist/nucleus/delegation-gate.d.ts +2 -2
  26. package/dist/nucleus/delegation-gate.js +13 -2
  27. package/dist/nucleus/work-freshness.mjs +189 -0
  28. package/dist/processors/implementations/agents-md-validation-processor.js +1 -1
  29. package/docs-site/docs/plates/boot.md +17 -2
  30. package/docs-site/docs/plates/governance.md +3 -2
  31. package/docs-site/docs/plates/index.md +3 -1
  32. package/docs-site/docs/plates/memory-recall.md +26 -35
  33. package/docs-site/docs/plates/orchestration.md +39 -35
  34. package/docs-site/docs/plates/processor.md +34 -36
  35. package/docs-site/docs/plates/reporting.md +3 -8
  36. package/docs-site/docs/plates/routing.md +22 -11
  37. package/package.json +3 -2
  38. package/scripts/foundry/release.mjs +48 -5
  39. package/scripts/mjs/run-grok-hook.mjs +50 -0
  40. package/scripts/mjs/test-consumer-readiness.mjs +1 -0
  41. package/scripts/mjs/test-mcp-functionality.mjs +14 -9
  42. package/scripts/node/basic-security-audit.cjs +0 -1
  43. package/scripts/node/bridge-mcp-wiring.cjs +66 -3
  44. package/scripts/node/install-bridges.cjs +3 -3
  45. package/scripts/node/postinstall.cjs +1 -1
  46. package/scripts/node/prepare-consumer.cjs +11 -7
  47. package/scripts/node/setup-dev.cjs +18 -26
  48. package/scripts/node/validate-mcp-connectivity.js +2 -1
  49. package/src/integrations/grok/plugin/0xray/hooks/hooks.json +6 -6
  50. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  51. package/src/mcps/knowledge-skills/storyteller.server.ts +0 -1
  52. package/xray/agents_template.md +5 -5
  53. package/xray/codex.json +12 -4
  54. package/xray/features.json +1 -1
  55. package/dist/agents/known-names.d.ts +0 -26
  56. package/dist/agents/known-names.js +0 -72
  57. package/dist/analytics/consent-manager.d.ts +0 -92
  58. package/dist/analytics/consent-manager.js +0 -257
  59. package/dist/benchmark/performance-benchmark.d.ts +0 -103
  60. package/dist/benchmark/performance-benchmark.js +0 -373
  61. package/dist/cli/commands/analytics-disable.d.ts +0 -9
  62. package/dist/cli/commands/analytics-disable.js +0 -72
  63. package/dist/cli/commands/analytics-enable-action.d.ts +0 -8
  64. package/dist/cli/commands/analytics-enable-action.js +0 -82
  65. package/dist/cli/commands/analytics-preview.d.ts +0 -9
  66. package/dist/cli/commands/analytics-preview.js +0 -106
  67. package/dist/cli/commands/analytics-status.d.ts +0 -9
  68. package/dist/cli/commands/analytics-status.js +0 -68
  69. package/dist/cli/commands/enforce.d.ts +0 -12
  70. package/dist/cli/commands/enforce.js +0 -78
  71. package/dist/cli/commands/govern.d.ts +0 -29
  72. package/dist/cli/commands/govern.js +0 -149
  73. package/dist/cli/commands/security-audit.d.ts +0 -23
  74. package/dist/cli/commands/security-audit.js +0 -219
  75. package/dist/cli/server.d.ts +0 -1
  76. package/dist/cli/server.js +0 -147
  77. package/dist/config/routing-mappings.d.ts +0 -26
  78. package/dist/config/routing-mappings.js +0 -151
  79. package/dist/core/bridge.mjs +0 -922
  80. package/dist/core/trace-context.d.ts +0 -12
  81. package/dist/core/trace-context.js +0 -33
  82. package/dist/enforcement/index.d.ts +0 -22
  83. package/dist/enforcement/index.js +0 -40
  84. package/dist/enforcement/loaders/index.d.ts +0 -36
  85. package/dist/enforcement/loaders/index.js +0 -39
  86. package/dist/enforcement/test-auto-healing.d.ts +0 -83
  87. package/dist/enforcement/test-auto-healing.js +0 -325
  88. package/dist/inference/index.d.ts +0 -10
  89. package/dist/inference/index.js +0 -5
  90. package/dist/infrastructure/iac-validator.d.ts +0 -150
  91. package/dist/infrastructure/iac-validator.js +0 -465
  92. package/dist/infrastructure/schemas/cloud-schemas.d.ts +0 -482
  93. package/dist/infrastructure/schemas/cloud-schemas.js +0 -488
  94. package/dist/integrations/base/ExampleIntegration.d.ts +0 -46
  95. package/dist/integrations/base/ExampleIntegration.js +0 -180
  96. package/dist/integrations/enforcement-gate.d.ts +0 -44
  97. package/dist/integrations/enforcement-gate.js +0 -348
  98. package/dist/mcps/connection/connection-manager.d.ts +0 -39
  99. package/dist/mcps/connection/connection-manager.js +0 -79
  100. package/dist/mcps/index.d.ts +0 -17
  101. package/dist/mcps/index.js +0 -65
  102. package/dist/mcps/orchestrator/index.d.ts +0 -6
  103. package/dist/mcps/orchestrator/index.js +0 -5
  104. package/dist/metrics/index.d.ts +0 -6
  105. package/dist/metrics/index.js +0 -6
  106. package/dist/orchestrator/self-direction-activation.d.ts +0 -108
  107. package/dist/orchestrator/self-direction-activation.js +0 -244
  108. package/dist/orchestrator/universal-registry-bridge.d.ts +0 -84
  109. package/dist/orchestrator/universal-registry-bridge.js +0 -246
  110. package/dist/performance/performance-budget-enforcer.d.ts +0 -157
  111. package/dist/performance/performance-budget-enforcer.js +0 -433
  112. package/dist/performance/performance-regression-tester.d.ts +0 -107
  113. package/dist/performance/performance-regression-tester.js +0 -341
  114. package/dist/postprocessor/analysis/CodeChangeAnalyzer.d.ts +0 -11
  115. package/dist/postprocessor/analysis/CodeChangeAnalyzer.js +0 -50
  116. package/dist/postprocessor/config/ProcessorConfigLoader.d.ts +0 -44
  117. package/dist/postprocessor/config/ProcessorConfigLoader.js +0 -16
  118. package/dist/postprocessor/integration.d.ts +0 -156
  119. package/dist/postprocessor/integration.js +0 -194
  120. package/dist/postprocessor/metamorphosis/index.d.ts +0 -2
  121. package/dist/postprocessor/metamorphosis/index.js +0 -1
  122. package/dist/postprocessor/redeploy/RetryHandler.d.ts +0 -49
  123. package/dist/postprocessor/redeploy/RetryHandler.js +0 -118
  124. package/dist/processors/implementations/session-capture-processor.d.ts +0 -14
  125. package/dist/processors/implementations/session-capture-processor.js +0 -37
  126. package/dist/public/XrayService.d.ts +0 -17
  127. package/dist/public/XrayService.js +0 -40
  128. package/dist/public/index.d.ts +0 -4
  129. package/dist/public/index.js +0 -2
  130. package/dist/security/comprehensive-security-audit.d.ts +0 -157
  131. package/dist/security/comprehensive-security-audit.js +0 -1004
  132. package/dist/security/index.d.ts +0 -13
  133. package/dist/security/index.js +0 -13
  134. package/dist/security/security-agent-coordinator.d.ts +0 -71
  135. package/dist/security/security-agent-coordinator.js +0 -203
  136. package/dist/security/security-hardening-system.d.ts +0 -238
  137. package/dist/security/security-hardening-system.js +0 -726
  138. package/dist/security/security-orchestration-layer.d.ts +0 -118
  139. package/dist/security/security-orchestration-layer.js +0 -495
  140. package/dist/testing/memory-regression-suite.d.ts +0 -105
  141. package/dist/testing/memory-regression-suite.js +0 -258
  142. package/dist/tools/assess-complexity-tool.d.ts +0 -28
  143. package/dist/tools/assess-complexity-tool.js +0 -44
  144. package/dist/tools/query-routing-tool.d.ts +0 -17
  145. package/dist/tools/query-routing-tool.js +0 -59
  146. package/dist/utils/batch-operations.d.ts +0 -25
  147. package/dist/utils/batch-operations.js +0 -292
  148. package/dist/utils/task-graph.d.ts +0 -29
  149. package/dist/utils/task-graph.js +0 -106
  150. package/dist/utils/test-template-generator.d.ts +0 -25
  151. package/dist/utils/test-template-generator.js +0 -178
  152. package/dist/utils/token-manager.d.ts +0 -48
  153. package/dist/utils/token-manager.js +0 -163
  154. package/dist/validation/agent-config-validator.d.ts +0 -110
  155. package/dist/validation/agent-config-validator.js +0 -280
  156. package/dist/validation/orchestration-flow-validator.d.ts +0 -87
  157. package/dist/validation/orchestration-flow-validator.js +0 -606
  158. package/src/mcps/connection/connection-manager.ts +0 -91
  159. package/src/mcps/index.ts +0 -80
  160. package/src/mcps/orchestrator/index.ts +0 -29
@@ -3,6 +3,8 @@
3
3
  * A named plane returns its view. Field pipes fill what they can see.
4
4
  * The view keeps every field for the next look.
5
5
  * A held plane stays quiet. A leave is denied.
6
+ * On this host a search is grep, glob, or a shell that runs one.
7
+ * The lens file stays open. One later read may go on, then the next read stops.
6
8
  */
7
9
  import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from 'node:fs';
8
10
  import { createRequire } from 'node:module';
@@ -72,9 +74,12 @@ export function organStop(held, action) {
72
74
  const text = String(action?.text || '');
73
75
  const paths = (action?.paths || []).map(String).filter(Boolean);
74
76
  const tool = String(action?.tool || '');
75
- if (/docs-site\/docs\/plates\/[a-z0-9-]+\.md/i.test([text, ...paths].join('\n'))) {
77
+ const spoken = [text, ...paths].join('\n');
78
+ if (/docs-site\/docs\/plates\/[a-z0-9-]+\.md/i.test(spoken)) {
76
79
  return organDeny(`The reading is ${plane}. That drawing is not the plane.`);
77
80
  }
81
+ const otherFile = otherPlaneFile(plane, spoken);
82
+ if (otherFile) return organDeny(`The reading is ${plane}. The action is ${otherFile}.`);
78
83
  const stripped = withoutPaths(text);
79
84
  const others = acceptedPlanes().filter((name) => name !== plane && planeWord(name, stripped));
80
85
  const namesThis = planeWord(plane, stripped);
@@ -255,10 +260,10 @@ function cardView(plane, scope, flavor, root) {
255
260
  teardown: plane.teardown || '',
256
261
  worn: plane.worn || '',
257
262
  };
263
+ view.exam = examinePlane(plane, root).text;
258
264
  if (flavor === 'triage') {
259
265
  const filled = new Set(filledOf(plane));
260
266
  view.empty = FIELD_ORDER.filter((name) => !filled.has(name));
261
- view.exam = examinePlane(plane, root).text;
262
267
  }
263
268
  return view;
264
269
  }
@@ -283,6 +288,8 @@ function formatCardText(view) {
283
288
  if (view.empty.length) lines.push(`Empty: ${view.empty.join(', ')}`);
284
289
  lines.push(view.exam);
285
290
  text = `${text}\n${lines.join('\n')}`;
291
+ } else if (typeof view.exam === 'string' && view.exam.startsWith('Drift:')) {
292
+ text = `${text}\n${view.exam}`;
286
293
  }
287
294
  return text;
288
295
  }
@@ -304,6 +311,8 @@ export function formatCardPane(view) {
304
311
  if (view.flavor === 'triage') {
305
312
  if (Array.isArray(view.empty) && view.empty.length) rows.push(['Empty', view.empty.join(', ')]);
306
313
  if (view.exam) rows.push(['', view.exam]);
314
+ } else if (typeof view.exam === 'string' && view.exam.startsWith('Drift:')) {
315
+ rows.push(['', view.exam]);
307
316
  }
308
317
  const body = rows.map(([label, value]) => (label ? `${label.padEnd(8)} ${value}` : value).trimEnd());
309
318
  const title = `${view.flavor} · ${view.plane}`;
@@ -684,12 +693,86 @@ export function suitHint(text) {
684
693
  }
685
694
 
686
695
  const DEEP_SEARCH = /search_codebase|find_implementation|get_documentation/i;
696
+ const HOLDS_ON_NAME = new Set(['dichotomy', 'syncopate', 'synthesis']);
697
+ const SHELL_SEARCH = /(?:^|&&|\|\||[;&|`(\n])\s*(?:(?:\/[\w.+/-]+\/)|(?:npx\s+))?(?:rg|grep|ag|ack|fd|find)\b|\bgit\s+grep\b/;
698
+
699
+ function shellSearches(text) {
700
+ return SHELL_SEARCH.test(String(text || ''));
701
+ }
702
+
703
+ function mappedPlaneFiles(name) {
704
+ try {
705
+ const data = JSON.parse(readFileSync(planesFile(), 'utf8'));
706
+ const entry = data[name];
707
+ if (!entry || typeof entry !== 'object') return [];
708
+ const candidates = [entry.file, entry.worn, entry.skills];
709
+ if (Array.isArray(entry.files)) candidates.push(...entry.files);
710
+ return candidates.filter((file) => typeof file === 'string' && file.includes('.') && file.includes('/'));
711
+ } catch {
712
+ return [];
713
+ }
714
+ }
715
+
716
+ function otherPlaneFile(held, spoken) {
717
+ const text = String(spoken || '');
718
+ for (const name of acceptedPlanes()) {
719
+ if (name === held) continue;
720
+ if (mappedPlaneFiles(name).some((file) => text.includes(file))) return name;
721
+ }
722
+ return '';
723
+ }
724
+
725
+ function mapFiles() {
726
+ try {
727
+ const data = JSON.parse(readFileSync(planesFile(), 'utf8'));
728
+ const files = [];
729
+ for (const name of [...CARD_PLANES, ...WORN_PLANES]) {
730
+ const entry = data[name];
731
+ if (!entry || typeof entry !== 'object') continue;
732
+ const candidates = [entry.file, entry.worn, entry.skills];
733
+ if (Array.isArray(entry.files)) candidates.push(...entry.files);
734
+ for (const file of candidates) {
735
+ if (typeof file === 'string' && file.includes('.') && file.includes('/')) files.push(file);
736
+ }
737
+ }
738
+ return files;
739
+ } catch {
740
+ return [];
741
+ }
742
+ }
743
+
744
+ function opensLensFile(text) {
745
+ const spoken = String(text || '');
746
+ return mapFiles().some((file) => spoken.includes(file));
747
+ }
687
748
 
688
749
  function researchCall(toolName, text) {
689
750
  const tool = String(toolName || '');
690
751
  const spoken = String(text || '');
691
752
  if (/researcher|explorer|deep[- ]?research/i.test(tool) || DEEP_SEARCH.test(tool)) return true;
692
- return /bash|shell/i.test(tool) && (/researcher|explorer|deep[- ]?research/i.test(spoken) || DEEP_SEARCH.test(spoken));
753
+ if (/bash|shell/i.test(tool) && (/researcher|explorer|deep[- ]?research/i.test(spoken) || DEEP_SEARCH.test(spoken) || shellSearches(spoken))) return true;
754
+ if (/^(grep|glob)$/i.test(tool)) return true;
755
+ return /^read_file$/i.test(tool) && !opensLensFile(spoken);
756
+ }
757
+
758
+ function passPath(root) {
759
+ return join(root, '.xray', 'state', 'goggles-lens-pass.json');
760
+ }
761
+
762
+ function loadReadPass(root) {
763
+ try {
764
+ const data = JSON.parse(readFileSync(passPath(root), 'utf8'));
765
+ if (!data || data.read !== true) return null;
766
+ return data.spent === true ? 'spent' : 'armed';
767
+ } catch {
768
+ return null;
769
+ }
770
+ }
771
+
772
+ function saveReadPass(root, spent) {
773
+ const file = passPath(root);
774
+ mkdirSync(dirname(file), { recursive: true });
775
+ writeFileSync(file, `${JSON.stringify({ read: true, spent: Boolean(spent) })}\n`);
693
776
  }
694
777
 
695
778
  /** One plane: the files the suit continues with. A look with no plane stops. Two planes stay quiet. */
@@ -724,11 +807,22 @@ function stampPlane(root, id) {
724
807
  }
725
808
  }
726
809
 
727
- /** A search stays stopped. The next move is the file on the lens, not the same search again. */
810
+ /** A search stays stopped. The lens file stays open. One later read may go on, then the next read stops. */
728
811
  export function lensBeforeResearch(root, toolName, text) {
729
812
  const spoken = String(text || '');
730
813
  if (!researchCall(toolName, spoken)) return null;
731
814
  const names = CARD_PLANES.filter((name) => intentWords(spoken).includes(name));
815
+ if (/^read_file$/i.test(String(toolName || ''))) {
816
+ if (loadReadPass(root) === 'armed') {
817
+ saveReadPass(root, true);
818
+ return null;
819
+ }
820
+ if (loadReadPass(root) !== 'spent') {
821
+ if (names.length === 1) stampPlane(root, names[0]);
822
+ saveReadPass(root, false);
823
+ }
824
+ return { gate: 'lens', decision: 'deny', reason: lensPage(root, names) };
825
+ }
732
826
  if (names.length === 1) stampPlane(root, names[0]);
733
827
  return { gate: 'lens', decision: 'deny', reason: lensPage(root, names) };
734
828
  }
@@ -772,22 +866,17 @@ function seenFields(plane, root) {
772
866
  };
773
867
  }
774
868
 
775
- function kept(stored, seen, key) {
776
- if (key === 'files') {
777
- if (Array.isArray(seen.files) && seen.files.length) return seen.files.map(String);
778
- return Array.isArray(stored.files) ? stored.files.map(String) : [];
779
- }
780
- if (seen[key]) return seen[key];
781
- return stored[key] || '';
869
+ function freshField(seen, key) {
870
+ if (key === 'files') return Array.isArray(seen.files) ? seen.files.map(String) : [];
871
+ return seen[key] || '';
782
872
  }
783
873
 
784
874
  export function growPlane(id, platesDir, root) {
785
875
  const seen = seenFields(assemblePlane(id, platesDir), sourceRoot(platesDir));
786
876
  const views = loadViews(root);
787
- const stored = views[id] && typeof views[id] === 'object' ? views[id] : {};
788
877
  const grown = { id };
789
- for (const key of VIEW_KEYS) grown[key] = kept(stored, seen, key);
790
- if (id === 'ground') grown.from = stored.from || '';
878
+ for (const key of VIEW_KEYS) grown[key] = freshField(seen, key);
879
+ if (id === 'ground') grown.from = '';
791
880
  views[id] = {};
792
881
  for (const key of VIEW_KEYS) views[id][key] = grown[key];
793
882
  saveViews(root, views);
@@ -876,17 +965,13 @@ function isGogglesWork(text) {
876
965
 
877
966
 
878
967
  export function cardStop(root, toolName, text, paths = []) {
879
- const spoken = String(text || '');
880
- if (spoken.length <= 200 && isGogglesWork(spoken) && !(paths || []).some((item) => /goggles/i.test(String(item)))) {
881
- clearReading(root);
882
- clearCardLine(root);
883
- return null;
884
- }
885
968
  const held = loadReading(root);
886
969
  if (!held) return null;
887
970
  const drift = actualityLine();
888
971
  if (drift) return organDeny(drift);
889
- return organStop(held, { tool: toolName, text, paths });
972
+ const stopped = organStop(held, { tool: toolName, text, paths });
973
+ if (stopped) return stopped;
974
+ return { gate: 'goggles', decision: 'allow' };
890
975
  }
891
976
 
892
977
  function stationFile(root) {
@@ -928,14 +1013,16 @@ export function handCard(root, intent) {
928
1013
  clearCardLine(root);
929
1014
  return null;
930
1015
  }
931
- if (!lookVerb(words)) return null;
1016
+ const asked = lookVerb(words);
1017
+ const planes = acceptedPlanes().filter((name) => words.includes(name));
1018
+ if (!asked && planes.length === 0) return null;
1019
+ if (!asked && planes.length === 1 && !HOLDS_ON_NAME.has(planes[0])) return null;
932
1020
  clearCardLine(root);
933
1021
  const looked = readGoggles(words);
934
1022
  if (looked.text.startsWith('Actuality. Drift')) {
935
1023
  saveReading(root, { drift: looked.text });
936
1024
  return null;
937
1025
  }
938
- const planes = acceptedPlanes().filter((name) => words.includes(name));
939
1026
  if (planes.length !== 1) {
940
1027
  clearReading(root);
941
1028
  return null;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * What each chat can actually do.
3
+ * Friend line first. Hook names are labels, not the sentence.
4
+ *
5
+ * stopsTool — the host can refuse a tool before it runs.
6
+ * noticesDeath — a crush/compact hook runs.
7
+ * note — what comes back into the chat: none, one-line, or the whole card.
8
+ */
9
+
10
+ export const HOST_TRUTH = [
11
+ {
12
+ id: 'cursor',
13
+ name: 'Cursor',
14
+ stopsTool: true,
15
+ noticesDeath: true,
16
+ note: 'one-line',
17
+ hooks: 'preToolUse, preCompact',
18
+ line: 'Cursor can stop a tool. When the chat dies, it can leave one line in the next message. The rest of the card is a file. Read it.',
19
+ },
20
+ {
21
+ id: 'grok',
22
+ name: 'Grok',
23
+ stopsTool: true,
24
+ noticesDeath: true,
25
+ note: 'none',
26
+ hooks: 'PreToolUse, PreCompact',
27
+ line: 'Grok can stop a tool. When the chat dies, the hook still writes the file, and the host throws the note away. Read the file.',
28
+ },
29
+ {
30
+ id: 'grok-bot',
31
+ name: 'Grok Bot',
32
+ stopsTool: false,
33
+ noticesDeath: false,
34
+ note: 'none',
35
+ hooks: 'none',
36
+ line: 'Grok Bot cannot stop a tool and cannot leave a note. The house is how a team shares rules there.',
37
+ },
38
+ {
39
+ id: 'openclaw',
40
+ name: 'OpenClaw',
41
+ stopsTool: true,
42
+ noticesDeath: false,
43
+ note: 'none',
44
+ hooks: 'PreToolUse',
45
+ line: 'OpenClaw can stop a tool. It has no hook for when the chat dies, and it does not leave a note.',
46
+ },
47
+ {
48
+ id: 'hermes',
49
+ name: 'Hermes',
50
+ stopsTool: true,
51
+ noticesDeath: false,
52
+ note: 'none',
53
+ hooks: 'pre_tool_call',
54
+ line: 'Hermes can stop a tool. It does not register a hook for when the chat dies, and it does not leave a note.',
55
+ },
56
+ {
57
+ id: 'opencode',
58
+ name: 'OpenCode',
59
+ stopsTool: true,
60
+ noticesDeath: false,
61
+ note: 'whole-card',
62
+ hooks: 'tool.execute.before',
63
+ line: 'OpenCode can stop a tool. It does not register a hook for when the chat dies. When the card file exists, the whole card is already in the prompt.',
64
+ },
65
+ ];
66
+
67
+ export function hostTruthById(id) {
68
+ return HOST_TRUTH.find((row) => row.id === id) ?? null;
69
+ }
70
+
71
+ /** The card a person reads. One paragraph per chat. */
72
+ export function hostTruthCard() {
73
+ return HOST_TRUTH.map((row) => row.line).join('\n\n');
74
+ }
@@ -1252,6 +1252,9 @@ function formatStationMarkdown(fields) {
1252
1252
  } else {
1253
1253
  lines.push("Git: n/a");
1254
1254
  }
1255
+ if (fields.freshnessLine) {
1256
+ lines.push(fields.freshnessLine);
1257
+ }
1255
1258
  lines.push(fields.repertoireResume || "Repertoire: module unresolved");
1256
1259
  if (fields.workingLine) {
1257
1260
  lines.push(fields.workingLine);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.34",
3
+ "version": "4.0.36",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -13,7 +13,6 @@ const STORY_TYPES = ['reflection', 'saga', 'journey', 'narrative'];
13
13
  function resolveSkillMd() {
14
14
  const here = dirname(fileURLToPath(import.meta.url));
15
15
  const candidates = [
16
- join(here, '../../../skills/storyteller/SKILL.md'),
17
16
  join(here, '../../skills/storyteller/SKILL.md'),
18
17
  join(process.cwd(), 'node_modules/0xray/dist/skills/storyteller/SKILL.md'),
19
18
  join(process.cwd(), 'node_modules/xray/dist/skills/storyteller/SKILL.md'),
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Multi-host delegation gate SSOT — pending-delegations + spawn todo enforcement.
3
3
  * Grok / Hermes / OpenCode adapters call evaluatePreToolGate via delegation-gate-runtime.
4
- * Constitution (11/29/69 + destructive shell) lives here. Grok may still extra-block Codex 2/7.
4
+ * Constitution (11/29/69/70 + destructive shell) lives here. Grok may still extra-block Codex 2/7.
5
5
  */
6
6
  import { type PendingDelegation } from './pending-delegations.js';
7
7
  export { getActivePendingDelegations } from './pending-delegations.js';
@@ -91,7 +91,7 @@ export declare function evaluateSpawnPlanGate(toolName: string, toolInput: ToolG
91
91
  export declare function evaluateSynthesisGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
92
92
  /** Block writes / unrelated work while pending delegations exist. */
93
93
  export declare function evaluatePendingDelegationGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
94
- /** Codex 11 / 29 / 69 + destructive shell — always on, all hosts that call this SSOT. */
94
+ /** Codex 11 / 29 / 69 / 70 + destructive shell — always on, all hosts that call this SSOT. */
95
95
  export declare function evaluateConstitutionGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
96
96
  export declare function evaluatePreToolGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
97
97
  export declare function evaluatePostToolSpawn(toolName: string, toolInput: ToolGateInput, projectRoot: string, options?: PostToolSpawnOptions): PostToolSpawnResult;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Multi-host delegation gate SSOT — pending-delegations + spawn todo enforcement.
3
3
  * Grok / Hermes / OpenCode adapters call evaluatePreToolGate via delegation-gate-runtime.
4
- * Constitution (11/29/69 + destructive shell) lives here. Grok may still extra-block Codex 2/7.
4
+ * Constitution (11/29/69/70 + destructive shell) lives here. Grok may still extra-block Codex 2/7.
5
5
  */
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
@@ -11,6 +11,7 @@ import { allPlanTodos, areSynthesisConsultTodosComplete, findRecentStalePlanArch
11
11
  import { getSynthesisDueReason, isSynthesisCheckpointDue, recordExecutionSlice, } from './synthesis.js';
12
12
  import { isSynthesisConsultTodoId, tryRecordSynthesisConsultReceipt, } from './synthesis-consult-receipt.js';
13
13
  import { resolveSpawnPlan, hasValidSpawnPlanContext } from './spawn-plan-resolution.js';
14
+ import { probeFreshness } from './work-freshness.mjs';
14
15
  import { isUserAsidesEnabled, isUserAsideTodoId, loadActiveAsidePointer, loadActiveUserAside, } from './user-aside.js';
15
16
  import { validateAsideWorktreeCwd, } from './aside-worktree.js';
16
17
  import { ceremonyForProfile, resolveSuitProfile, spawnPlanModeForProfile, writeSuitSessionBoot, maybeHeatHostStation, } from './suit-temperament.js';
@@ -416,11 +417,21 @@ function collectWritePaths(toolInput) {
416
417
  }
417
418
  return paths;
418
419
  }
419
- /** Codex 11 / 29 / 69 + destructive shell — always on, all hosts that call this SSOT. */
420
+ /** Codex 11 / 29 / 69 / 70 + destructive shell — always on, all hosts that call this SSOT. */
420
421
  export function evaluateConstitutionGate(toolName, toolInput, ctx) {
421
422
  const content = collectWriteContent(toolInput);
422
423
  const paths = collectWritePaths(toolInput);
423
424
  const writing = isWriteTool(toolName, ctx.host ?? 'generic');
425
+ if (writing) {
426
+ const freshness = probeFreshness(ctx.projectRoot);
427
+ if (freshness.stale) {
428
+ return {
429
+ allow: false,
430
+ reason: freshness.reason,
431
+ gate: 'codex-70',
432
+ };
433
+ }
434
+ }
424
435
  if (writing && ctx.features.no_new_surface !== false) {
425
436
  for (const p of paths) {
426
437
  const normalized = p.replace(/\\/g, '/');
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Codex 70. An edit waits until this copy is on the latest main
3
+ * and the package is not older than the published one.
4
+ * This repo is that package when package.json name is 0xray.
5
+ * The check is saved at session start. The edit gate reads the saved result.
6
+ * A temp directory inside a repo is not the workspace, so unit tests stay quiet.
7
+ */
8
+
9
+ import { execFileSync } from 'node:child_process';
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
11
+ import { join, resolve } from 'node:path';
12
+
13
+ export function compareVersions(left, right) {
14
+ const a = String(left).split('.').map((part) => parseInt(part, 10) || 0);
15
+ const b = String(right).split('.').map((part) => parseInt(part, 10) || 0);
16
+ const length = Math.max(a.length, b.length);
17
+ for (let i = 0; i < length; i += 1) {
18
+ const da = a[i] || 0;
19
+ const db = b[i] || 0;
20
+ if (da !== db) return da < db ? -1 : 1;
21
+ }
22
+ return 0;
23
+ }
24
+
25
+ export function decideFreshness(input) {
26
+ const behind = Number(input.behind) || 0;
27
+ if (behind > 0) {
28
+ const commits = behind === 1 ? '1 commit' : `${behind} commits`;
29
+ return {
30
+ stale: true,
31
+ reason: `Codex 70: this copy is ${commits} behind main. Update before you edit. Compare set-aside work to main before you throw it away.`,
32
+ };
33
+ }
34
+ const repoVersion = input.repoName === '0xray' ? input.repoVersion || null : null;
35
+ const worn = input.wornSuit || repoVersion;
36
+ const published = input.publishedSuit || null;
37
+ const target = published || repoVersion;
38
+ if (target && worn && compareVersions(worn, target) < 0) {
39
+ return {
40
+ stale: true,
41
+ reason: `Codex 70: package ${worn} is older than ${target}. Install the current one before you edit.`,
42
+ };
43
+ }
44
+ return { stale: false, reason: '' };
45
+ }
46
+
47
+ export function describeFreshness(input) {
48
+ const behind = Number(input.behind) || 0;
49
+ const parts = [behind > 0 ? `${behind} behind main` : 'up to date'];
50
+ if (input.wornSuit) parts.push(`package ${input.wornSuit}`);
51
+ if (input.publishedSuit) parts.push(`published ${input.publishedSuit}`);
52
+ const stashCount = Number(input.stashCount) || 0;
53
+ if (stashCount > 0) parts.push(`${stashCount} set aside, compare before drop`);
54
+ return `Fresh: ${parts.join('. ')}.`;
55
+ }
56
+
57
+ function run(cmd, args, cwd, timeout) {
58
+ return execFileSync(cmd, args, {
59
+ cwd,
60
+ encoding: 'utf8',
61
+ timeout,
62
+ stdio: ['ignore', 'pipe', 'ignore'],
63
+ }).trim();
64
+ }
65
+
66
+ function gitTop(cwd) {
67
+ try {
68
+ return run('git', ['rev-parse', '--show-toplevel'], cwd, 3000);
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ function readJson(file) {
75
+ try {
76
+ return JSON.parse(readFileSync(file, 'utf8'));
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ export function wornSuitVersion(projectRoot, opts = {}) {
83
+ if (projectRoot) {
84
+ const local = readJson(join(projectRoot, 'node_modules', '0xray', 'package.json'));
85
+ if (local && local.version) return String(local.version);
86
+ }
87
+ const candidates = [
88
+ '/opt/homebrew/lib/node_modules/0xray/package.json',
89
+ '/usr/local/lib/node_modules/0xray/package.json',
90
+ ];
91
+ for (const file of candidates) {
92
+ const pkg = readJson(file);
93
+ if (pkg && pkg.version) return String(pkg.version);
94
+ }
95
+ if (!opts.liveGlobal) return null;
96
+ try {
97
+ const root = run('npm', ['root', '-g'], projectRoot || process.cwd(), 8000);
98
+ const pkg = readJson(join(root, '0xray', 'package.json'));
99
+ return pkg && pkg.version ? String(pkg.version) : null;
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+
105
+ export function readWorkSnapshot(projectRoot) {
106
+ const top = gitTop(projectRoot);
107
+ let behind = 0;
108
+ let stashCount = 0;
109
+ if (top && resolve(top) === resolve(projectRoot)) {
110
+ try {
111
+ behind = Number(run('git', ['rev-list', '--count', 'HEAD..origin/main'], projectRoot, 4000)) || 0;
112
+ } catch {
113
+ behind = 0;
114
+ }
115
+ try {
116
+ const list = run('git', ['stash', 'list'], projectRoot, 4000);
117
+ stashCount = list ? list.split('\n').filter(Boolean).length : 0;
118
+ } catch {
119
+ stashCount = 0;
120
+ }
121
+ }
122
+ const repo = readJson(join(projectRoot, 'package.json'));
123
+ const saved = readJson(join(projectRoot, '.xray', 'state', 'freshness.json'));
124
+ return {
125
+ behind,
126
+ stashCount,
127
+ repoName: repo && repo.name ? String(repo.name) : null,
128
+ repoVersion: repo && repo.version ? String(repo.version) : null,
129
+ wornSuit: wornSuitVersion(projectRoot),
130
+ publishedSuit: saved && saved.publishedSuit ? String(saved.publishedSuit) : null,
131
+ };
132
+ }
133
+
134
+ function freshnessFile(projectRoot) {
135
+ return join(projectRoot, '.xray', 'state', 'freshness.json');
136
+ }
137
+
138
+ export function readSavedFreshness(projectRoot) {
139
+ const saved = readJson(freshnessFile(projectRoot));
140
+ return saved ? describeFreshness(saved) : null;
141
+ }
142
+
143
+ export function probeFreshness(projectRoot) {
144
+ const saved = readJson(freshnessFile(projectRoot));
145
+ if (saved && saved.behind != null) return decideFreshness(saved);
146
+ return decideFreshness(readWorkSnapshot(projectRoot));
147
+ }
148
+
149
+ export function refreshFreshness(projectRoot) {
150
+ const top = gitTop(projectRoot);
151
+ if (top && resolve(top) === resolve(projectRoot)) {
152
+ try {
153
+ run('git', ['fetch', 'origin', '--prune'], projectRoot, 8000);
154
+ } catch {
155
+ /* offline fetch must not block the boot */
156
+ }
157
+ }
158
+ let publishedSuit = null;
159
+ try {
160
+ publishedSuit = run('npm', ['view', '0xray', 'version'], projectRoot, 8000);
161
+ } catch {
162
+ publishedSuit = null;
163
+ }
164
+ const snapshot = readWorkSnapshot(projectRoot);
165
+ if (publishedSuit) snapshot.publishedSuit = publishedSuit;
166
+ if (snapshot.repoName === '0xray' && snapshot.repoVersion) {
167
+ snapshot.wornSuit = snapshot.repoVersion;
168
+ } else {
169
+ const globalWorn = wornSuitVersion(projectRoot, { liveGlobal: true });
170
+ if (globalWorn) snapshot.wornSuit = globalWorn;
171
+ }
172
+ try {
173
+ const dir = join(projectRoot, '.xray', 'state');
174
+ mkdirSync(dir, { recursive: true });
175
+ writeFileSync(freshnessFile(projectRoot), `${JSON.stringify({
176
+ publishedSuit: snapshot.publishedSuit,
177
+ wornSuit: snapshot.wornSuit,
178
+ behind: snapshot.behind,
179
+ stashCount: snapshot.stashCount,
180
+ checkedAt: new Date().toISOString(),
181
+ }, null, 2)}\n`);
182
+ } catch {
183
+ /* the gate still reads git and the worn package */
184
+ }
185
+ return {
186
+ ...snapshot,
187
+ freshnessLine: describeFreshness(snapshot),
188
+ };
189
+ }
@@ -224,7 +224,7 @@ export class AgentsMdValidationProcessor {
224
224
  "",
225
225
  "## Codex",
226
226
  "",
227
- `0xRay enforces Universal Development Codex (69 terms) for systematic error prevention.`,
227
+ `0xRay enforces Universal Development Codex (70 terms) for systematic error prevention.`,
228
228
  "",
229
229
  ].join("\n"),
230
230
  "## CLI Commands": [
@@ -20,6 +20,11 @@ sidebar_label: Boot
20
20
  ┌─────────────────────────────┼──────────────────────────────┐
21
21
  │ PROCESSING LAYER v │
22
22
  │ ┌────────────────────────────────────────────────────┐ │
23
+ │ │ memory monitoring │ │
24
+ │ │ setupMemoryMonitoring │ │
25
+ │ └──────────────────────────┬─────────────────────────┘ │
26
+ │ v │
27
+ │ ┌────────────────────────────────────────────────────┐ │
23
28
  │ │ load .xray config │ │
24
29
  │ └──────────────────────────┬─────────────────────────┘ │
25
30
  │ v │
@@ -33,12 +38,22 @@ sidebar_label: Boot
33
38
  │ v │
34
39
  │ ┌────────────────────────────────────────────────────┐ │
35
40
  │ │ activateProcessors (PROCESSOR_DEFS) │ │
36
- │ │ legacy boot list, then agents and plugins │ │
41
+ │ └──────────────────────────┬─────────────────────────┘ │
42
+ │ v │
43
+ │ ┌────────────────────────────────────────────────────┐ │
44
+ │ │ processor health │ │
45
+ │ │ validateProcessorHealth │ │
46
+ │ └──────────────────────────┬─────────────────────────┘ │
47
+ │ v │
48
+ │ ┌────────────────────────────────────────────────────┐ │
49
+ │ │ agents and plugins │ │
50
+ │ │ loadRemainingAgents · initializePluginSystem │ │
37
51
  │ └──────────────────────────┬─────────────────────────┘ │
38
52
  │ v │
39
53
  │ ┌────────────────────────────────────────────────────┐ │
40
54
  │ │ enforcement · codex compliance │ │
41
- │ │ Dynamo governance integration │ │
55
+ │ │ finalizeSecurityIntegration │ │
56
+ │ │ governance init when that flag is not false │ │
42
57
  │ └──────────────────────────┬─────────────────────────┘ │
43
58
  └──────────────────────────────┼────────────────────────────┘
44
59
  v
@@ -5,7 +5,7 @@ sidebar_label: Governance
5
5
 
6
6
  # Governance
7
7
 
8
- A proposal is deliberated inside the suit, filtered by Dynamo Solar, then merged. The PreToolUse codex gate stands beside the vote.
8
+ A proposal is deliberated inside the suit, filtered by Dynamo Solar, then merged. The vote is approve, reject, or needs_revision. The PreToolUse codex gate stands beside the vote.
9
9
 
10
10
  ```
11
11
  ┌────────────────────────────────────────────────────────────┐
@@ -44,7 +44,8 @@ A proposal is deliberated inside the suit, filtered by Dynamo Solar, then merged
44
44
  │ OUTPUT LAYER v │
45
45
  │ ┌──────────────────────┐ ┌──────────────────────┐ │
46
46
  │ │ vote │ │ codex decision │ │
47
- │ │ approve / reject │ │ allow / deny │ │
47
+ │ │ approve / reject / │ │ allow / deny │ │
48
+ │ │ needs_revision │ │ │ │
48
49
  │ └──────────────────────┘ └──────────────────────┘ │
49
50
  └────────────────────────────────────────────────────────────┘
50
51
  ```
@@ -23,6 +23,8 @@ Plates have a type. A domain model plate shows how named parts fit. A state flow
23
23
 
24
24
  [House](./house.md)
25
25
 
26
- ## Not yet typed
26
+ ## Pipeline
27
27
 
28
28
  [Routing](./routing.md) · [Governance](./governance.md) · [Boot](./boot.md) · [Orchestration](./orchestration.md) · [Processor](./processor.md) · [Reporting](./reporting.md) · [Memory recall](./memory-recall.md)
29
+
30
+ These seven are the lens planes. Each drawing is an input layer, a processing layer, and an output layer. Ground has no plate file.