0xray 3.5.1 → 3.5.2

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 (54) hide show
  1. package/.gitignore.default +8 -0
  2. package/.opencode/hooks/post-commit +7 -27
  3. package/.xray/features.json +6 -0
  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 +1 -1
  8. package/SKILLS.md +1 -1
  9. package/dist/.gitignore.default +8 -0
  10. package/dist/AGENTS.md +1 -1
  11. package/dist/CHANGELOG.md +16 -0
  12. package/dist/README.md +1 -1
  13. package/dist/SKILLS.md +1 -1
  14. package/dist/core/bridge.mjs +10 -32
  15. package/dist/core/features-config.d.ts +16 -0
  16. package/dist/core/features-config.js +24 -0
  17. package/dist/integrations/grok/hooks/grok-hook-utils.js +2 -2
  18. package/dist/integrations/grok/hooks/post-tool-use.js +1 -1
  19. package/dist/integrations/grok/hooks/pre-tool-use.js +23 -32
  20. package/dist/integrations/grok/hooks/session-start.js +20 -1
  21. package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +2 -1
  22. package/dist/integrations/hermes-agent/bridge.mjs +22 -50
  23. package/dist/integrations/hooks/synthesis-hook-runtime.mjs +50 -0
  24. package/dist/mcps/orchestrator/handlers/complexity-handler.d.ts +3 -0
  25. package/dist/mcps/orchestrator/handlers/complexity-handler.js +25 -10
  26. package/dist/mcps/orchestrator/server.js +124 -4
  27. package/dist/memory-routing/types.d.ts +7 -0
  28. package/dist/nucleus/autonomy-kernel.d.ts +3 -0
  29. package/dist/nucleus/autonomy-kernel.js +40 -0
  30. package/dist/nucleus/delegation-gate.d.ts +6 -4
  31. package/dist/nucleus/delegation-gate.js +77 -5
  32. package/dist/nucleus/lead-dev-plan-persistence.d.ts +3 -0
  33. package/dist/nucleus/lead-dev-plan-persistence.js +44 -1
  34. package/dist/nucleus/synthesis-completion.d.ts +6 -0
  35. package/dist/nucleus/synthesis-completion.js +29 -0
  36. package/dist/nucleus/synthesis-context.d.ts +31 -0
  37. package/dist/nucleus/synthesis-context.js +86 -0
  38. package/dist/nucleus/synthesis.d.ts +54 -0
  39. package/dist/nucleus/synthesis.js +196 -0
  40. package/dist/plugin/xray-codex-injection.js +17 -0
  41. package/package.json +7 -3
  42. package/scripts/helpers/find-project-root.mjs +92 -0
  43. package/scripts/hooks/install-hooks.cjs +136 -36
  44. package/scripts/hooks/run-hook.js +46 -9
  45. package/scripts/mjs/delegation-gate-fixture.mjs +37 -0
  46. package/scripts/node/auto-reflection-generator.mjs +7 -0
  47. package/scripts/node/consumer-gitignore.cjs +101 -0
  48. package/scripts/node/load-reflection-config.mjs +77 -0
  49. package/scripts/node/postinstall.cjs +9 -6
  50. package/src/integrations/grok/plugin/0xray/hooks/hooks.json +2 -1
  51. package/src/mcps/orchestrator/handlers/complexity-handler.ts +33 -8
  52. package/src/mcps/orchestrator/server.ts +181 -5
  53. package/xray/features.json +6 -0
  54. package/xray/features.schema.json +17 -0
@@ -20,6 +20,13 @@ credentials.json
20
20
  *.pem
21
21
  *.key
22
22
 
23
+ # 0xray suit artifacts (postinstall / bridge install)
24
+ .grok/
25
+ .mcp.json
26
+ opencode.json
27
+ AGENTS.md
28
+ SKILLS.md
29
+
23
30
  # Runtime state (xray inference workflows, session state)
24
31
  .xray/
25
32
  .strray/
@@ -41,6 +48,7 @@ xray/inference/
41
48
 
42
49
  # Logs
43
50
  logs/
51
+ logs/framework/
44
52
  *.log
45
53
 
46
54
  # OS files
@@ -20,39 +20,19 @@ BRANCH=$(git rev-parse --abbrev-ref HEAD)
20
20
  fi
21
21
  ) &
22
22
 
23
- # Load auto_reflection config from features.json
24
- CONFIG_FILE="$PROJECT_ROOT/.xray/features.json"
23
+ # Load reflection config — synthesis.reflection SSOT, fallback auto_reflection
25
24
  MODE="minimal"
26
25
  COMMIT_THRESHOLD=25
27
26
  DAYS_THRESHOLD=14
28
27
  AUTO_GENERATE=true
29
28
  PROMPT_USER=true
30
29
 
31
- if [ -f "$CONFIG_FILE" ]; then
32
- MODE=$(grep -o '"mode": *"[^"]*"' "$CONFIG_FILE" | cut -d'"' -f4)
33
- MODE=${MODE:-minimal}
34
-
35
- # Get thresholds based on mode
36
- case "$MODE" in
37
- full)
38
- COMMIT_THRESHOLD=10
39
- DAYS_THRESHOLD=5
40
- AUTO_GENERATE=true
41
- PROMPT_USER=true
42
- ;;
43
- minimal)
44
- COMMIT_THRESHOLD=25
45
- DAYS_THRESHOLD=14
46
- AUTO_GENERATE=true
47
- PROMPT_USER=true
48
- ;;
49
- off)
50
- COMMIT_THRESHOLD=999
51
- DAYS_THRESHOLD=365
52
- AUTO_GENERATE=false
53
- PROMPT_USER=false
54
- ;;
55
- esac
30
+ if command -v node >/dev/null 2>&1; then
31
+ LOAD_SCRIPT="$PROJECT_ROOT/scripts/node/load-reflection-config.mjs"
32
+ if [ -f "$LOAD_SCRIPT" ]; then
33
+ export PROJECT_ROOT="$PROJECT_ROOT"
34
+ eval "$(node "$LOAD_SCRIPT")"
35
+ fi
56
36
  fi
57
37
 
58
38
  # Check if this was a significant commit - prompt for reflection
@@ -141,6 +141,12 @@
141
141
  "module_path": "",
142
142
  "config": {}
143
143
  },
144
+ "synthesis": {
145
+ "enabled": false,
146
+ "every_n_gates": 12,
147
+ "every_n_turns": 0,
148
+ "every_n_todos_completed": 0
149
+ },
144
150
  "inference_governance": {
145
151
  "enabled": false,
146
152
  "request_timeout_ms": 10000,
@@ -27,6 +27,23 @@
27
27
  "then": {
28
28
  "required": ["module_path"]
29
29
  }
30
+ },
31
+ "synthesis": {
32
+ "type": "object",
33
+ "properties": {
34
+ "enabled": { "type": "boolean" },
35
+ "every_n_gates": { "type": "integer", "minimum": 0 },
36
+ "every_n_turns": { "type": "integer", "minimum": 0 },
37
+ "every_n_todos_completed": { "type": "integer", "minimum": 0 },
38
+ "reflection": {
39
+ "type": "object",
40
+ "properties": {
41
+ "mode": { "type": "string", "enum": ["full", "minimal", "off"] },
42
+ "triggers": { "type": "object" },
43
+ "thresholds": { "type": "object" }
44
+ }
45
+ }
46
+ }
30
47
  }
31
48
  },
32
49
  "additionalProperties": true
@@ -4,7 +4,7 @@
4
4
 
5
5
  Quick reference for the 0xRay AI orchestration framework (shipped to your project on `npm install 0xray`).
6
6
 
7
- **v3.5.1** — 7 MCP servers · 45 skills · 68 codex terms · 4 platform bridges
7
+ **v3.5.2** — 7 MCP servers · 45 skills · 68 codex terms · 4 platform bridges
8
8
 
9
9
  ## Postinstall (automatic)
10
10
 
package/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xray Agents
2
2
 
3
- Quick reference for the xray AI orchestration framework (**v3.5.1**).
3
+ Quick reference for the xray AI orchestration framework (**v3.5.2**).
4
4
 
5
5
  **42 agents** · **45 skills** · **7 consumer MCP servers** · **68 codex terms** · **3,226 tests**
6
6
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xray — MCP-Centric AI Governance OS
2
2
 
3
- **v3.5.1** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
3
+ **v3.5.2** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
4
4
 
5
5
  ## Your AI Power Suit
6
6
 
package/SKILLS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 0xray Skills
2
2
 
3
- **v3.5.1** — **45 skills** · invoked via `xray-skills` MCP · synced on postinstall
3
+ **v3.5.2** — **45 skills** · invoked via `xray-skills` MCP · synced on postinstall
4
4
 
5
5
  xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) across the full software lifecycle. Lead-dev operating model lives in **`orchestrator`** skill + `multi_agent_orchestration.lead_dev_mode` in features.json. Skills load on demand via the **xray-skills** MCP server (`npx -y 0xray mcp skills`), agent `@mentions`, or platform skill directories after postinstall.
6
6
 
@@ -20,6 +20,13 @@ credentials.json
20
20
  *.pem
21
21
  *.key
22
22
 
23
+ # 0xray suit artifacts (postinstall / bridge install)
24
+ .grok/
25
+ .mcp.json
26
+ opencode.json
27
+ AGENTS.md
28
+ SKILLS.md
29
+
23
30
  # Runtime state (xray inference workflows, session state)
24
31
  .xray/
25
32
  .strray/
@@ -41,6 +48,7 @@ xray/inference/
41
48
 
42
49
  # Logs
43
50
  logs/
51
+ logs/framework/
44
52
  *.log
45
53
 
46
54
  # OS files
package/dist/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xray Agents
2
2
 
3
- Quick reference for the xray AI orchestration framework (**v3.5.1**).
3
+ Quick reference for the xray AI orchestration framework (**v3.5.2**).
4
4
 
5
5
  **42 agents** · **45 skills** · **7 consumer MCP servers** · **68 codex terms** · **3,226 tests**
6
6
 
package/dist/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Conventional Commits](https://www.conventionalcommits.org/).
6
6
 
7
+ ## [3.5.2] - 2026-06-19
8
+
9
+ ### 🐛 Bug Fixes
10
+ - **Grok synthesis gate** — `pre-tool-use.js` wires `evaluatePreToolGate` SSOT (synthesis deny, gate slices, `govern-and-apply` block); fail-closed on hook errors
11
+ - **Consumer bridge root** — `findProjectRoot` git-root + devDependency detection; `findGitRoot` applies `normalizePath`
12
+ - **Consumer hooks** — stale xray pre-commit reinstall; post-commit hook installed; pre-commit fails closed when runner missing; non-xray hooks backed up before overwrite
13
+ - **Consumer `.gitignore`** — idempotent suit block merge on postinstall; preserves orphan tail on incomplete marker
14
+ - **`bridge.mjs`** — `handleGetConfig` fixes undeclared `envDir` ReferenceError
15
+ - **Post-commit reflection** — `load-reflection-config.mjs` mode whitelist + `--json` output (no shell `eval`); macOS/BSD `stat` support
16
+
17
+ ### ✅ Tests
18
+ - `verify-grok-synthesis-gate.mjs` (4/4 live Grok hook); chained in `verify:grok-delegation-gate` and `verify:delegation-gate`
19
+ - `verify-consumer-gitignore` incomplete-marker fixture; `verify:find-project-root` 3/3
20
+
21
+ ---
22
+
7
23
  ## [3.5.1] - 2026-06-19
8
24
 
9
25
  ### ✨ Features
package/dist/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xray — MCP-Centric AI Governance OS
2
2
 
3
- **v3.5.1** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
3
+ **v3.5.2** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
4
4
 
5
5
  ## Your AI Power Suit
6
6
 
package/dist/SKILLS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 0xray Skills
2
2
 
3
- **v3.5.1** — **45 skills** · invoked via `xray-skills` MCP · synced on postinstall
3
+ **v3.5.2** — **45 skills** · invoked via `xray-skills` MCP · synced on postinstall
4
4
 
5
5
  xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) across the full software lifecycle. Lead-dev operating model lives in **`orchestrator`** skill + `multi_agent_orchestration.lead_dev_mode` in features.json. Skills load on demand via the **xray-skills** MCP server (`npx -y 0xray mcp skills`), agent `@mentions`, or platform skill directories after postinstall.
6
6
 
@@ -51,6 +51,7 @@ import { join, dirname, resolve, relative } from "path";
51
51
  import { fileURLToPath } from "url";
52
52
  import { homedir } from "os";
53
53
  import { createServer } from "http";
54
+ import { findProjectRoot } from "../../scripts/helpers/find-project-root.mjs";
54
55
 
55
56
  const __dirname = dirname(fileURLToPath(import.meta.url));
56
57
 
@@ -63,35 +64,11 @@ let CodexFormatterModule = null;
63
64
  let frameworkReady = false;
64
65
  let frameworkLoadAttempted = false;
65
66
 
66
- // ============================================================================
67
- // Project Root Detection
68
- // ============================================================================
69
-
70
- function findProjectRoot() {
71
- const envHome = process.env.XRAY_HOME;
72
- if (envHome && existsSync(join(envHome, "package.json"))) return envHome;
73
-
74
- const candidates = [
75
- process.cwd(),
76
- join(homedir(), "dev", "xray"),
77
- join(dirname(__dirname), "..", ".."), // core/ -> project root
78
- join(dirname(__dirname), "..", "..", ".."), // integrations/ -> project root
79
- ];
80
-
81
- for (const dir of candidates) {
82
- try {
83
- const pkgPath = join(dir, "package.json");
84
- if (existsSync(pkgPath)) {
85
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
86
- if (pkg.name === "0xray" || pkg.dependencies?.["0xray"]) {
87
- return dir;
88
- }
89
- }
90
- } catch {
91
- continue;
92
- }
93
- }
94
- return process.cwd();
67
+ function resolveBridgeProjectRoot() {
68
+ return findProjectRoot([
69
+ join(dirname(__dirname), "..", ".."),
70
+ join(dirname(__dirname), "..", "..", ".."),
71
+ ]);
95
72
  }
96
73
 
97
74
  // ============================================================================
@@ -184,7 +161,7 @@ const BRIDGE_CODEX_FALLBACK = {
184
161
  // ============================================================================
185
162
 
186
163
  async function handleHealth(input) {
187
- const projectRoot = findProjectRoot();
164
+ const projectRoot = resolveBridgeProjectRoot();
188
165
  const loaded = await loadFramework(projectRoot);
189
166
 
190
167
  const pkgPath = join(projectRoot, "package.json");
@@ -313,6 +290,7 @@ async function handleGetCodexPrompt(input, projectRoot, logDir) {
313
290
 
314
291
  async function handleGetConfig(input, projectRoot, logDir) {
315
292
  logToActivity(logDir, "get-config");
293
+ const envDir = process.env.XRAY_CONFIG_DIR;
316
294
 
317
295
  const result = {
318
296
  status: "ok",
@@ -374,7 +352,7 @@ async function handleStats() {
374
352
  codexFormatterAvailable: !!CodexFormatterModule,
375
353
  qualityGateAvailable: !!QualityGateModule,
376
354
  nodeVersion: process.version,
377
- projectRoot: findProjectRoot(),
355
+ projectRoot: resolveBridgeProjectRoot(),
378
356
  };
379
357
  }
380
358
 
@@ -893,7 +871,7 @@ async function main() {
893
871
  }
894
872
  }
895
873
 
896
- const projectRoot = findProjectRoot();
874
+ const projectRoot = resolveBridgeProjectRoot();
897
875
  const logDir = ensureLogDir(projectRoot);
898
876
 
899
877
  // Lazy-load framework
@@ -66,6 +66,21 @@ export interface MultiAgentOrchestrationConfig {
66
66
  /** 3.5.0 — PreToolUse gate after delegate-deferred (default true when lead_dev_mode) */
67
67
  auto_chain_delegations?: boolean;
68
68
  }
69
+ /** Reflection triggers merged from legacy auto_reflection when synthesis is configured */
70
+ export interface SynthesisReflectionConfig {
71
+ mode?: AutoReflectionConfig['mode'];
72
+ triggers?: AutoReflectionConfig['triggers'];
73
+ thresholds?: AutoReflectionConfig['thresholds'];
74
+ }
75
+ /** 3.6.0 — periodic reflect & realign checkpoint (Synthesis PR1) */
76
+ export interface SynthesisConfig {
77
+ enabled: boolean;
78
+ every_n_gates: number;
79
+ every_n_turns: number;
80
+ every_n_todos_completed: number;
81
+ /** SSOT for reflection triggers when synthesis enabled; seeded from auto_reflection */
82
+ reflection?: SynthesisReflectionConfig;
83
+ }
69
84
  export interface AutonomousReportingConfig {
70
85
  enabled: boolean;
71
86
  interval_minutes: number;
@@ -369,6 +384,7 @@ export interface FeaturesConfig {
369
384
  auto_reflection?: AutoReflectionConfig;
370
385
  inference?: InferenceConfig;
371
386
  memory_routing?: MemoryRoutingConfig;
387
+ synthesis: SynthesisConfig;
372
388
  kernel?: KernelConfig;
373
389
  processors?: ProcessorsConfig;
374
390
  enforcement?: EnforcementConfig;
@@ -278,6 +278,24 @@ export class FeaturesConfigLoader {
278
278
  ...defaults.caching,
279
279
  ...configData.caching,
280
280
  },
281
+ synthesis: (() => {
282
+ const merged = {
283
+ ...defaults.synthesis,
284
+ ...configData.synthesis,
285
+ };
286
+ const reflection = configData.synthesis?.reflection ??
287
+ (configData.auto_reflection
288
+ ? {
289
+ mode: configData.auto_reflection.mode,
290
+ triggers: configData.auto_reflection.triggers,
291
+ thresholds: configData.auto_reflection.thresholds,
292
+ }
293
+ : undefined);
294
+ if (reflection) {
295
+ merged.reflection = reflection;
296
+ }
297
+ return merged;
298
+ })(),
281
299
  };
282
300
  }
283
301
  /**
@@ -391,6 +409,12 @@ export class FeaturesConfigLoader {
391
409
  cache_ttl_seconds: 300,
392
410
  max_cache_size_mb: 50,
393
411
  },
412
+ synthesis: {
413
+ enabled: false,
414
+ every_n_gates: 12,
415
+ every_n_turns: 0,
416
+ every_n_todos_completed: 0,
417
+ },
394
418
  };
395
419
  }
396
420
  /**
@@ -105,8 +105,8 @@ export function resolveCodexPath(root = workspaceRoot()) {
105
105
  return null;
106
106
  }
107
107
 
108
- export function loadFeatures() {
109
- const featuresPath = resolveFeaturesPath();
108
+ export function loadFeatures(root = workspaceRoot()) {
109
+ const featuresPath = resolveFeaturesPath(root);
110
110
  if (!featuresPath) return { lead_dev_mode: true, no_new_surface: true, per_suite_triage: true };
111
111
  try {
112
112
  const data = JSON.parse(fs.readFileSync(featuresPath, 'utf8'));
@@ -36,7 +36,7 @@ async function main() {
36
36
  if (isSubagentTool(toolName)) {
37
37
  const spawnCheck = validateSpawnMatchesTodo(toolInput, eventRoot);
38
38
  if (spawnCheck.valid && spawnCheck.expectedTodoId) {
39
- updatePlanTodoStatusInPlace(spawnCheck.expectedTodoId, 'in_progress', eventRoot);
39
+ updatePlanTodoStatusInPlace(spawnCheck.expectedTodoId, 'completed', eventRoot);
40
40
  }
41
41
 
42
42
  const result = satisfyDelegationsFromToolInput(toolInput, eventRoot);
@@ -4,11 +4,13 @@
4
4
  * Contract: stdin JSON → stdout {"decision":"allow"} | {"decision":"deny","reason":"..."}
5
5
  */
6
6
 
7
+ import {
8
+ evaluatePreToolGate,
9
+ loadDelegationGateFeatures,
10
+ } from '../../hooks/delegation-gate-runtime.mjs';
7
11
  import {
8
12
  checkCodexPatterns,
9
13
  checkFullTestSuite,
10
- checkPendingDelegationGate,
11
- checkSubagentGate,
12
14
  checkSurfaceArea,
13
15
  ensureSessionBoot,
14
16
  isShellTool,
@@ -43,45 +45,27 @@ async function main() {
43
45
  const eventRoot = event.workspaceRoot || event.cwd || root;
44
46
  ensureSessionBoot(eventRoot, '0xray/grok-pre-tool-use-boot');
45
47
 
46
- const features = loadFeatures();
48
+ const features = loadFeatures(eventRoot);
49
+ const gateFeatures = loadDelegationGateFeatures(eventRoot);
47
50
  const ctx = extractFromEvent(event);
48
51
  toolName = ctx.toolName;
49
52
  const { paths, content, cmd, toolInput } = ctx;
50
53
  const sessionId = resolveSessionId(event);
51
54
 
52
- const delegationBlock = checkPendingDelegationGate(
53
- toolName,
54
- toolInput,
55
- features,
56
- eventRoot,
57
- sessionId,
58
- );
59
- if (delegationBlock) {
60
- finish(
61
- eventRoot,
62
- 'deny',
63
- delegationBlock.reason,
64
- delegationBlock.hint,
65
- toolName,
66
- { gate: delegationBlock.gate },
67
- );
68
- }
69
-
70
- const subagentBlock = checkSubagentGate(
71
- toolName,
72
- features,
73
- eventRoot,
55
+ const gateBlock = evaluatePreToolGate(toolName, toolInput, {
56
+ projectRoot: eventRoot,
74
57
  sessionId,
75
- toolInput,
76
- );
77
- if (subagentBlock) {
58
+ features: gateFeatures,
59
+ host: 'grok',
60
+ });
61
+ if (!gateBlock.allow) {
78
62
  finish(
79
63
  eventRoot,
80
64
  'deny',
81
- subagentBlock.reason || subagentBlock,
82
- subagentBlock.hint || null,
65
+ gateBlock.reason,
66
+ gateBlock.hint,
83
67
  toolName,
84
- { gate: subagentBlock.gate || 'spawn-gate' },
68
+ { gate: gateBlock.gate },
85
69
  );
86
70
  }
87
71
 
@@ -110,7 +94,14 @@ async function main() {
110
94
  tool: toolName,
111
95
  error: err.message,
112
96
  });
113
- finish(root, 'allow', null, null, toolName);
97
+ finish(
98
+ root,
99
+ 'deny',
100
+ `PreToolUse hook error — blocked for safety: ${err.message}`,
101
+ null,
102
+ toolName,
103
+ { gate: 'hook-error' },
104
+ );
114
105
  }
115
106
  }
116
107
 
@@ -14,15 +14,34 @@ import {
14
14
  writeSessionBoot,
15
15
  } from './grok-hook-utils.js';
16
16
  import { appendHookActivity } from './grok-hook-activity.js';
17
+ import { recordSynthesisTurnSlice } from '../../hooks/synthesis-hook-runtime.mjs';
17
18
 
18
- const HOOK_EVENT = process.env.GROK_HOOK_EVENT || 'session_start';
19
+ function resolveHookEvent(event) {
20
+ if (process.env.GROK_HOOK_EVENT) return process.env.GROK_HOOK_EVENT;
21
+ const flag = process.argv.find((a) => a.startsWith('--hook-event='));
22
+ if (flag) return flag.slice('--hook-event='.length);
23
+ if (
24
+ event.prompt != null ||
25
+ event.userMessage != null ||
26
+ event.user_prompt != null ||
27
+ event.hook === 'UserPromptSubmit'
28
+ ) {
29
+ return 'user_prompt_submit';
30
+ }
31
+ return 'session_start';
32
+ }
19
33
 
20
34
  async function main() {
21
35
  const root = workspaceRoot();
36
+ let HOOK_EVENT = 'session_start';
22
37
  try {
23
38
  const event = await readStdinJson();
39
+ HOOK_EVENT = resolveHookEvent(event);
24
40
  const eventRoot = event.workspaceRoot || event.cwd || root;
25
41
  const sessionId = resolveSessionId(event);
42
+ if (HOOK_EVENT === 'user_prompt_submit' && sessionId) {
43
+ recordSynthesisTurnSlice(eventRoot, sessionId);
44
+ }
26
45
  if (clearPendingDelegationsForSessionChange(sessionId, eventRoot)) {
27
46
  appendHookActivity(eventRoot, 'grok-session-start', 'stale-pending-cleared', 'info', {
28
47
  sessionId,
@@ -36,7 +36,8 @@
36
36
  "type": "command",
37
37
  "command": "node",
38
38
  "args": [
39
- "${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/session-start.js"
39
+ "${XRAY_AI_PATH:-node_modules/0xray}/dist/integrations/grok/hooks/session-start.js",
40
+ "--hook-event=user_prompt_submit"
40
41
  ],
41
42
  "timeout": 15000
42
43
  }
@@ -39,6 +39,7 @@ import {
39
39
  import { join, dirname, relative } from "path";
40
40
  import { fileURLToPath } from "url";
41
41
  import { homedir } from "os";
42
+ import { findProjectRoot } from "../../../scripts/helpers/find-project-root.mjs";
42
43
 
43
44
  const __dirname = dirname(fileURLToPath(import.meta.url));
44
45
 
@@ -52,62 +53,19 @@ let frameworkReady = false;
52
53
  let frameworkLoadAttempted = false;
53
54
 
54
55
  // ── Project root detection ───────────────────────────────────
55
- function isConsumerWorkspace(dir) {
56
- return (
57
- existsSync(join(dir, ".xray", "features.json")) ||
58
- existsSync(join(dir, "node_modules", "0xray", "package.json"))
59
- );
60
- }
61
-
62
- function findProjectRoot() {
63
- const envHome = process.env.XRAY_HOME || process.env.XRAY_ROOT;
64
- if (envHome) {
65
- const resolved = join(envHome);
66
- if (existsSync(join(resolved, "package.json")) || isConsumerWorkspace(resolved)) {
67
- return resolved;
68
- }
69
- }
70
-
56
+ function resolveBridgeProjectRoot() {
71
57
  const consumerMarker = join(__dirname, "xray-consumer-root.txt");
72
58
  if (existsSync(consumerMarker)) {
73
59
  try {
74
60
  const marked = readFileSync(consumerMarker, "utf-8").trim();
75
- if (marked && (existsSync(join(marked, "package.json")) || isConsumerWorkspace(marked))) {
61
+ if (marked && existsSync(join(marked, "package.json"))) {
76
62
  return marked;
77
63
  }
78
64
  } catch {
79
- // fall through
65
+ // fall through to shared SSOT
80
66
  }
81
67
  }
82
-
83
- // Prefer consumer workspace (--cwd / process.chdir) over framework package root
84
- let dir = process.cwd();
85
- for (let i = 0; i < 12; i++) {
86
- if (isConsumerWorkspace(dir)) return dir;
87
- const parent = dirname(dir);
88
- if (parent === dir) break;
89
- dir = parent;
90
- }
91
-
92
- const candidates = [
93
- join(homedir(), "dev", "xray"),
94
- join(dirname(__dirname), "..", "..", "..", ".."), // plugin dir -> package root
95
- ];
96
-
97
- for (const candidate of candidates) {
98
- try {
99
- const pkgPath = join(candidate, "package.json");
100
- if (existsSync(pkgPath)) {
101
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
102
- if (pkg.name === "0xray" || pkg.dependencies?.["0xray"]) {
103
- return candidate;
104
- }
105
- }
106
- } catch {
107
- continue;
108
- }
109
- }
110
- return process.cwd();
68
+ return findProjectRoot([join(dirname(__dirname), "..", "..", "..", "..")]);
111
69
  }
112
70
 
113
71
  // ── Log directory ────────────────────────────────────────────
@@ -403,7 +361,7 @@ async function runProcessors(tool, args, phase, projectRoot, logDir) {
403
361
  // ── Command handlers ─────────────────────────────────────────
404
362
 
405
363
  async function handleHealth(input) {
406
- const projectRoot = findProjectRoot();
364
+ const projectRoot = resolveBridgeProjectRoot();
407
365
  const loaded = await loadFramework(projectRoot);
408
366
 
409
367
  const components = {
@@ -760,7 +718,7 @@ function handleStats() {
760
718
  qualityGateAvailable: !!runQualityGateWithLogging,
761
719
  processorsAvailable: !!ProcessorManager,
762
720
  nodeVersion: process.version,
763
- projectRoot: findProjectRoot(),
721
+ projectRoot: resolveBridgeProjectRoot(),
764
722
  };
765
723
  }
766
724
 
@@ -811,6 +769,16 @@ const KNOWN_COMMANDS = new Set([
811
769
  "skill-install", "skill-registry", "delegation-gate",
812
770
  ]);
813
771
 
772
+ async function recordHermesSynthesisTurn(projectRoot, sessionId) {
773
+ if (!sessionId) return;
774
+ try {
775
+ const { recordSynthesisTurnSlice } = await import("../hooks/synthesis-hook-runtime.mjs");
776
+ recordSynthesisTurnSlice(projectRoot, sessionId);
777
+ } catch {
778
+ /* optional */
779
+ }
780
+ }
781
+
814
782
  async function handleDelegationGate(command, projectRoot, logDir) {
815
783
  try {
816
784
  const gate = await import("../hooks/delegation-gate-runtime.mjs");
@@ -834,6 +802,10 @@ async function handleDelegationGate(command, projectRoot, logDir) {
834
802
  return { allow: true, phase: "post", ...result };
835
803
  }
836
804
 
805
+ if (command.recordTurn === true || command.event === 'user_prompt_submit') {
806
+ await recordHermesSynthesisTurn(projectRoot, sessionId);
807
+ }
808
+
837
809
  const features = gate.loadDelegationGateFeatures(projectRoot);
838
810
  const outcome = gate.evaluatePreToolGate(tool, args, {
839
811
  projectRoot,
@@ -935,7 +907,7 @@ async function main() {
935
907
  }
936
908
  }
937
909
 
938
- const projectRoot = findProjectRoot();
910
+ const projectRoot = resolveBridgeProjectRoot();
939
911
  const logDir = ensureLogDir(projectRoot);
940
912
 
941
913
  // Log session start for test verification