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
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Load compiled synthesis SSOT for Grok/Hermes hooks.
4
+ */
5
+ import { createRequire } from 'node:module';
6
+ import { existsSync } from 'node:fs';
7
+ import { dirname, join, resolve } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+
12
+ function resolvePackageRoot() {
13
+ let dir = __dirname;
14
+ for (let i = 0; i < 10; i++) {
15
+ const pkg = join(dir, 'package.json');
16
+ if (existsSync(pkg)) {
17
+ try {
18
+ const require = createRequire(join(dir, 'package.json'));
19
+ if (require('./package.json').name === '0xray') return dir;
20
+ } catch {
21
+ /* continue */
22
+ }
23
+ }
24
+ const nm = join(dir, 'node_modules', '0xray', 'package.json');
25
+ if (existsSync(nm)) return dirname(nm);
26
+ const parent = dirname(dir);
27
+ if (parent === dir) break;
28
+ dir = parent;
29
+ }
30
+ return resolve(__dirname, '../../..');
31
+ }
32
+
33
+ function loadSynthesis() {
34
+ const root = resolvePackageRoot();
35
+ const candidates = [
36
+ join(__dirname, '../../nucleus/synthesis.js'),
37
+ join(root, 'dist/nucleus/synthesis.js'),
38
+ join(process.cwd(), 'node_modules/0xray/dist/nucleus/synthesis.js'),
39
+ ];
40
+ const found = candidates.find((p) => existsSync(p));
41
+ if (!found) return null;
42
+ return createRequire(import.meta.url)(found);
43
+ }
44
+
45
+ export function recordSynthesisTurnSlice(projectRoot, sessionId) {
46
+ if (!sessionId) return null;
47
+ const mod = loadSynthesis();
48
+ if (!mod?.recordExecutionSlice) return null;
49
+ return mod.recordExecutionSlice('turn', { projectRoot, sessionId });
50
+ }
@@ -27,11 +27,14 @@ export declare class ComplexityHandler {
27
27
  handleAnalyzeComplexity(args: {
28
28
  tasks: AnalyzeComplexityInput[];
29
29
  sessionId?: string;
30
+ synthesisCheckpoint?: boolean;
31
+ synthesisDueReason?: string | null;
30
32
  }, asideId?: string): Promise<{
31
33
  content: Array<{
32
34
  type: string;
33
35
  text: string;
34
36
  }>;
37
+ ok: boolean;
35
38
  }>;
36
39
  /**
37
40
  * Generate recommendations based on complexity analysis
@@ -4,7 +4,7 @@
4
4
  * Handles complexity analysis requests
5
5
  */
6
6
  import { frameworkLogger } from '../../../core/framework-logger.js';
7
- import { buildLeadDevPlan, isLeadDevModeActive, persistLeadDevPlan, } from '../../../nucleus/autonomy-kernel.js';
7
+ import { buildLeadDevPlan, buildSynthesisCheckpointPlan, isLeadDevModeActive, persistLeadDevPlan, } from '../../../nucleus/autonomy-kernel.js';
8
8
  import { bindPlanToSession } from '../../../nucleus/lead-dev-plan-persistence.js';
9
9
  import { clearPendingDelegations } from '../../../nucleus/pending-delegations.js';
10
10
  import { getExecutionPlanner } from '../execution/execution-planner.js';
@@ -60,38 +60,50 @@ export class ComplexityHandler {
60
60
  const planTaskInputs = tasks
61
61
  .filter((t) => t.description?.trim())
62
62
  .map((t) => ({ description: t.description, type: t.type }));
63
- const leadDevPlan = isLeadDevModeActive() && (primaryDescription || planTaskInputs.length > 0)
64
- ? buildLeadDevPlan(primaryDescription, taskTypes.length ? taskTypes : ['implement'], planTaskInputs, analysis.overallComplexity)
65
- : null;
63
+ const synthesisCheckpoint = args.synthesisCheckpoint === true;
64
+ const leadDevPlan = synthesisCheckpoint
65
+ ? buildSynthesisCheckpointPlan(args.synthesisDueReason ?? null)
66
+ : isLeadDevModeActive() && (primaryDescription || planTaskInputs.length > 0)
67
+ ? buildLeadDevPlan(primaryDescription, taskTypes.length ? taskTypes : ['implement'], planTaskInputs, analysis.overallComplexity)
68
+ : null;
69
+ let planPersisted = synthesisCheckpoint ? leadDevPlan !== null : !leadDevPlan;
66
70
  if (leadDevPlan) {
67
71
  try {
68
72
  const sessionId = args.sessionId ?? `analyze-${Date.now()}`;
69
- clearPendingDelegations();
73
+ if (!synthesisCheckpoint) {
74
+ clearPendingDelegations();
75
+ }
70
76
  const planPath = persistLeadDevPlan(leadDevPlan);
71
77
  const bound = bindPlanToSession(sessionId);
72
- await frameworkLogger.log('orchestrator.server', 'lead-dev-plan-persisted', 'info', {
78
+ planPersisted = true;
79
+ await frameworkLogger.log('orchestrator.server', synthesisCheckpoint ? 'synthesis-plan-persisted' : 'lead-dev-plan-persisted', 'info', {
73
80
  planPath,
74
81
  sessionId,
75
82
  planGeneration: bound?.planGeneration,
76
83
  phaseCount: leadDevPlan.phases.length,
77
84
  todoCount: leadDevPlan.phases.reduce((n, p) => n + p.todos.length, 0),
85
+ synthesisCheckpoint,
78
86
  });
79
87
  }
80
88
  catch (err) {
81
- await frameworkLogger.log('orchestrator.server', 'lead-dev-plan-persist-failed', 'warning', { error: err instanceof Error ? err.message : String(err) });
89
+ planPersisted = false;
90
+ await frameworkLogger.log('orchestrator.server', synthesisCheckpoint ? 'synthesis-plan-persist-failed' : 'lead-dev-plan-persist-failed', 'warning', { error: err instanceof Error ? err.message : String(err) });
82
91
  }
83
92
  }
93
+ const ok = planPersisted;
84
94
  return {
95
+ ok,
85
96
  content: [
86
97
  {
87
98
  type: 'text',
88
- text: this.formatComplexityResponse(analysis, recommendations, tasks.length, leadDevPlan),
99
+ text: this.formatComplexityResponse(analysis, recommendations, tasks.length, leadDevPlan, synthesisCheckpoint),
89
100
  },
90
101
  ],
91
102
  };
92
103
  }
93
104
  catch (error) {
94
105
  return {
106
+ ok: false,
95
107
  content: [
96
108
  {
97
109
  type: 'text',
@@ -129,7 +141,7 @@ export class ComplexityHandler {
129
141
  /**
130
142
  * Format complexity response
131
143
  */
132
- formatComplexityResponse(analysis, recommendations, taskCount, leadDevPlan = null) {
144
+ formatComplexityResponse(analysis, recommendations, taskCount, leadDevPlan = null, synthesisCheckpoint = false) {
133
145
  const leadDevSection = leadDevPlan
134
146
  ? `
135
147
 
@@ -146,7 +158,10 @@ ${leadDevPlan.phases
146
158
  ${JSON.stringify(leadDevPlan, null, 2)}
147
159
  \`\`\``
148
160
  : '';
149
- return `🔍 Complexity Analysis Results
161
+ const header = synthesisCheckpoint
162
+ ? '🔄 Synthesis Checkpoint — Reflect & Realign'
163
+ : '🔍 Complexity Analysis Results';
164
+ return `${header}
150
165
 
151
166
  **Tasks Analyzed:** ${taskCount}
152
167
  **Overall Complexity:** ${analysis.overallComplexity}/100
@@ -105,10 +105,14 @@ export class OrchestratorServer {
105
105
  },
106
106
  {
107
107
  name: 'analyze-complexity',
108
- description: 'Analyze task complexity and recommend orchestration strategy. When lead_dev_mode is on (features.json), includes phased plan + todos + subagent routes.',
108
+ description: 'Analyze task complexity and recommend orchestration strategy. When lead_dev_mode is on (features.json), includes phased plan + todos + subagent routes. During synthesis checkpoint, returns collocated context and mandatory-consult realignment plan — pass sessionId from the active session.',
109
109
  inputSchema: {
110
110
  type: 'object',
111
111
  properties: {
112
+ sessionId: {
113
+ type: 'string',
114
+ description: 'Active session id (required to clear synthesis checkpoint; inferred from checkpoint state when omitted)',
115
+ },
112
116
  tasks: {
113
117
  type: 'array',
114
118
  items: {
@@ -229,11 +233,97 @@ export class OrchestratorServer {
229
233
  }
230
234
  case 'analyze-complexity': {
231
235
  const complexityArgs = args;
236
+ const projectRoot = process.cwd();
237
+ const { isSynthesisCheckpointDue, getSynthesisDueReason, getSynthesisCheckpointSessionId, } = await import('../../nucleus/synthesis.js');
238
+ const { appendSynthesisContextToResponse, prepareSynthesisCollocatedContext } = await import('../../nucleus/synthesis-context.js');
239
+ const { loadPersistedLeadDevPlan, isSynthesisRealignmentPlan, areSynthesisConsultTodosComplete, getSynthesisConsultTodos, getNextRequiredTodo, } = await import('../../nucleus/lead-dev-plan-persistence.js');
240
+ const checkpointSession = getSynthesisCheckpointSessionId(projectRoot);
241
+ if (checkpointSession &&
242
+ complexityArgs.sessionId &&
243
+ complexityArgs.sessionId !== checkpointSession) {
244
+ return {
245
+ content: [
246
+ {
247
+ type: 'text',
248
+ text: `❌ Synthesis sessionId mismatch — expected \`${checkpointSession}\` ` +
249
+ `(from synthesis-checkpoint.json), got \`${complexityArgs.sessionId}\`.`,
250
+ },
251
+ ],
252
+ };
253
+ }
254
+ const sessionId = checkpointSession ?? complexityArgs.sessionId ?? null;
255
+ const synthesisDue = checkpointSession
256
+ ? isSynthesisCheckpointDue(projectRoot, checkpointSession)
257
+ : false;
258
+ const dueReason = synthesisDue
259
+ ? getSynthesisDueReason(projectRoot, sessionId)
260
+ : null;
261
+ if (synthesisDue && !sessionId) {
262
+ return {
263
+ content: [
264
+ {
265
+ type: 'text',
266
+ text: '❌ Synthesis checkpoint due but sessionId could not be resolved. ' +
267
+ 'Pass sessionId matching .xray/state/synthesis-checkpoint.json.',
268
+ },
269
+ ],
270
+ };
271
+ }
272
+ const existingPlan = loadPersistedLeadDevPlan(projectRoot);
273
+ const realignmentInProgress = synthesisDue &&
274
+ existingPlan &&
275
+ isSynthesisRealignmentPlan(existingPlan) &&
276
+ !areSynthesisConsultTodosComplete(existingPlan);
277
+ if (realignmentInProgress) {
278
+ const collocated = await prepareSynthesisCollocatedContext(projectRoot, dueReason);
279
+ const nextTodo = getNextRequiredTodo(existingPlan);
280
+ const consultTodos = getSynthesisConsultTodos(existingPlan);
281
+ const todoList = consultTodos
282
+ .map((t) => `- [${t.status === 'completed' ? 'x' : ' '}] ${t.id} (${t.subagent}): ${t.task}`)
283
+ .join('\n');
284
+ let text = '🔄 Synthesis realignment in progress — consult todos preserved (analyze-complexity no-op).\n\n' +
285
+ `**Next required todo:** ${nextTodo?.id ?? 'none'} (${nextTodo?.subagent ?? ''})\n\n` +
286
+ `${todoList}\n\n` +
287
+ 'Complete mandatory consult todos via Task spawns before other gated work.';
288
+ if (collocated.collatedText) {
289
+ text = appendSynthesisContextToResponse([{ type: 'text', text }], collocated.collatedText)[0].text;
290
+ }
291
+ return { content: [{ type: 'text', text }] };
292
+ }
293
+ const collocated = synthesisDue
294
+ ? await prepareSynthesisCollocatedContext(projectRoot, dueReason)
295
+ : null;
296
+ const inheritedContext = synthesisDue && collocated
297
+ ? { synthesis: true, ...collocated }
298
+ : { taskCount: complexityArgs.tasks.length };
232
299
  const aside = await spawnAside({
233
- description: `Complexity analysis: ${complexityArgs.tasks.length} tasks`,
300
+ description: synthesisDue
301
+ ? 'Synthesis checkpoint: reflect & realign'
302
+ : `Complexity analysis: ${complexityArgs.tasks.length} tasks`,
303
+ ...(sessionId ? { sessionId } : {}),
304
+ inheritedContext,
234
305
  });
235
306
  try {
236
- return await this.complexityHandler.handleAnalyzeComplexity(complexityArgs, aside.asideId);
307
+ const result = await this.complexityHandler.handleAnalyzeComplexity({
308
+ ...complexityArgs,
309
+ ...(sessionId ? { sessionId } : {}),
310
+ ...(synthesisDue
311
+ ? { synthesisCheckpoint: true, synthesisDueReason: dueReason }
312
+ : {}),
313
+ }, aside.asideId);
314
+ let content = result.content;
315
+ if (synthesisDue && collocated?.collatedText) {
316
+ content = appendSynthesisContextToResponse(content, collocated.collatedText);
317
+ }
318
+ if (synthesisDue && result.ok) {
319
+ content = content.map((block, index) => index === 0
320
+ ? {
321
+ ...block,
322
+ text: `${block.text}\n\n**Synthesis checkpoint remains active** until mandatory consult todos (s.1–s.3) are completed via Task spawns.`,
323
+ }
324
+ : block);
325
+ }
326
+ return { content };
237
327
  }
238
328
  finally {
239
329
  closeAside(aside.asideId);
@@ -281,12 +371,38 @@ export class OrchestratorServer {
281
371
  }
282
372
  async handleGovernAndApply(args) {
283
373
  try {
374
+ const projectRoot = process.cwd();
375
+ const { isSynthesisCheckpointDue, getSynthesisDueReason, getSynthesisCheckpointSessionId, recordExecutionSlice, } = await import('../../nucleus/synthesis.js');
376
+ const { loadPersistedLeadDevPlan, isSynthesisRealignmentPlan, areSynthesisConsultTodosComplete, } = await import('../../nucleus/lead-dev-plan-persistence.js');
377
+ const checkpointSession = getSynthesisCheckpointSessionId(projectRoot);
378
+ const sessionId = checkpointSession ?? null;
379
+ if (sessionId && isSynthesisCheckpointDue(projectRoot, sessionId)) {
380
+ const dueReason = getSynthesisDueReason(projectRoot, sessionId);
381
+ const plan = loadPersistedLeadDevPlan(projectRoot);
382
+ const realignmentPending = plan && isSynthesisRealignmentPlan(plan) && !areSynthesisConsultTodosComplete(plan);
383
+ return {
384
+ content: [{
385
+ type: 'text',
386
+ text: JSON.stringify({
387
+ blocked: true,
388
+ gate: 'synthesis-checkpoint',
389
+ dueReason,
390
+ primitive: 'synthesis',
391
+ realignmentPending,
392
+ hint: realignmentPending
393
+ ? 'Complete mandatory consult todos (s.1–s.3) before govern-and-apply'
394
+ : 'Run analyze-complexity to reflect and realign before govern-and-apply',
395
+ }, null, 2),
396
+ }],
397
+ };
398
+ }
284
399
  const { InferenceCycle } = await import('../../inference/inference-cycle.js');
285
- const cycle = InferenceCycle.getInstance(process.cwd(), {
400
+ const cycle = InferenceCycle.getInstance(projectRoot, {
286
401
  skipApply: args.skipApply ?? false,
287
402
  });
288
403
  const aside = await spawnAside({
289
404
  description: `Governance cycle: ${args.proposals.length} proposals`,
405
+ ...(sessionId ? { sessionId } : {}),
290
406
  priorVerdictContext: { proposalCount: args.proposals.length },
291
407
  });
292
408
  let result;
@@ -299,12 +415,16 @@ export class OrchestratorServer {
299
415
  }
300
416
  const approved = result.votes.filter((v) => v.decision === 'approve').length;
301
417
  const rejected = result.votes.length - approved;
418
+ if (sessionId) {
419
+ recordExecutionSlice('gate', { projectRoot, sessionId });
420
+ }
302
421
  return {
303
422
  content: [{
304
423
  type: 'text',
305
424
  text: JSON.stringify({
306
425
  cycleId: result.cycleId,
307
426
  asideId: aside.asideId,
427
+ synthesisBridged: Boolean(sessionId),
308
428
  approved,
309
429
  rejected,
310
430
  votes: result.votes,
@@ -95,6 +95,13 @@ export interface MemoryRoutingProvider {
95
95
  resolveThinDispatch(baseAgent: string, operation: string, complexityScore: number): MemoryThinDispatchResult;
96
96
  getTaskConfidence?(task: MemoryOrchestrationTask): MemoryTaskConfidence;
97
97
  ingestFeedback?(entry: OrchestratorFeedbackEntry): void;
98
+ buildSynthesisContext?(opts: {
99
+ projectRoot: string;
100
+ dueReason?: string | null;
101
+ }): Record<string, unknown> | null;
102
+ refreshMetaInference?(): Promise<{
103
+ refreshed: boolean;
104
+ }>;
98
105
  }
99
106
  export interface MemoryRoutingConfig {
100
107
  enabled: boolean;
@@ -3,6 +3,7 @@
3
3
  * Config: features.json → multi_agent_orchestration (lead_dev_mode), NOT a separate surface.
4
4
  */
5
5
  export declare const LEAD_DEV_RULES: readonly ["Phased plan + detailed todos; assign best subagent; monitor output; iterate fully", "Lead dev takes helm — loop test fix until complete; no permission pings", "Per-suite test triage after major changes; full suite only when individuals pass", "Lead stays main thread; subagents execute; update todos continuously", "Read all console and test output; triage fix rerun", "Never defer errors as pre-existing — add todo and resolve", "Resolve all errors before phase completion"];
6
+ export declare const SYNTHESIS_REALIGNMENT_PHASE_ID = "phase-synthesis";
6
7
  export declare const MANDATORY_MAJOR_CONSULTS: readonly ["researcher", "architect-tools", "code-review"];
7
8
  export type SubagentRoute = 'strategist' | 'architect-tools' | 'researcher' | 'backend-engineer' | 'frontend-engineer' | 'bug-triage' | 'code-review' | 'security-audit' | 'enforcer';
8
9
  export interface LeadDevTodo {
@@ -41,6 +42,8 @@ export interface LeadDevPlan {
41
42
  export declare function isLeadDevModeActive(): boolean;
42
43
  export declare function routeSubagent(taskType: string): SubagentRoute;
43
44
  export declare function shouldFlagFullTestSuite(command: string): boolean;
45
+ /** Mandatory-consult plan for synthesis checkpoint realignment (no implementation phase). */
46
+ export declare function buildSynthesisCheckpointPlan(dueReason: string | null): LeadDevPlan | null;
44
47
  export declare function buildLeadDevPlan(description: string, taskTypes?: string[], taskInputs?: LeadDevPlanTaskInput[], mcpOverallComplexity?: number): LeadDevPlan | null;
45
48
  export declare function buildSessionBootContext(): Record<string, unknown>;
46
49
  /** Persist plan for PreToolUse spawn_subagent gate */
@@ -15,6 +15,7 @@ export const LEAD_DEV_RULES = [
15
15
  'Never defer errors as pre-existing — add todo and resolve',
16
16
  'Resolve all errors before phase completion',
17
17
  ];
18
+ export const SYNTHESIS_REALIGNMENT_PHASE_ID = 'phase-synthesis';
18
19
  export const MANDATORY_MAJOR_CONSULTS = [
19
20
  'researcher',
20
21
  'architect-tools',
@@ -73,6 +74,45 @@ function buildImplementationTodos(description, taskTypes, taskInputs, idPrefix)
73
74
  status: 'pending',
74
75
  }));
75
76
  }
77
+ /** Mandatory-consult plan for synthesis checkpoint realignment (no implementation phase). */
78
+ export function buildSynthesisCheckpointPlan(dueReason) {
79
+ if (!isLeadDevModeActive())
80
+ return null;
81
+ const cfg = orchestrationConfig();
82
+ const mandatoryConsults = cfg.auto_consult_major_work !== false ? [...MANDATORY_MAJOR_CONSULTS] : [];
83
+ if (mandatoryConsults.length === 0)
84
+ return null;
85
+ const reasonSuffix = dueReason ? ` (${dueReason})` : '';
86
+ return {
87
+ active: true,
88
+ rules: LEAD_DEV_RULES,
89
+ codexTerms: [59, 67, 68, 69],
90
+ description: `Synthesis checkpoint: reflect and realign${reasonSuffix}`,
91
+ complexity: 30,
92
+ requiresPhasedPlan: true,
93
+ recommendedStrategy: 'sequential',
94
+ mandatoryConsults,
95
+ phases: [
96
+ {
97
+ id: SYNTHESIS_REALIGNMENT_PHASE_ID,
98
+ name: 'Reflect & realign',
99
+ goal: 'Review collocated context and mandatory consults before resuming execution',
100
+ definitionOfDone: 'researcher + architect-tools + code-review consulted; todos realigned',
101
+ todos: mandatoryConsults.map((subagent, i) => ({
102
+ id: `s.${i + 1}`,
103
+ task: `Synthesis consult ${subagent}: review checkpoint context and realign plan`,
104
+ subagent: subagent,
105
+ status: 'pending',
106
+ })),
107
+ },
108
+ ],
109
+ testProtocol: {
110
+ perSuiteFirst: cfg.per_suite_test_triage !== false,
111
+ fullSuiteGate: false,
112
+ hint: 'Synthesis checkpoint — consult mandatory agents before resuming gated work',
113
+ },
114
+ };
115
+ }
76
116
  export function buildLeadDevPlan(description, taskTypes = ['implement'], taskInputs = [], mcpOverallComplexity) {
77
117
  if (!isLeadDevModeActive())
78
118
  return null;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Multi-host delegation gate SSOT — pending-delegations + spawn todo enforcement.
3
- * Grok / Hermes / OpenCode adapters call evaluatePreToolGate; Grok-only logic was
4
- * previously duplicated in grok-hook-utils.js.
3
+ * Grok / Hermes / OpenCode adapters call evaluatePreToolGate via delegation-gate-runtime.
4
+ * Grok-only codex/surface checks remain in pre-tool-use.js after the SSOT gate.
5
5
  */
6
6
  import { type PendingDelegation } from './pending-delegations.js';
7
7
  export { getActivePendingDelegations } from './pending-delegations.js';
@@ -68,11 +68,13 @@ export declare function isWriteTool(toolName: string, host?: DelegationGateHost)
68
68
  export declare function isReadOnlyTool(toolName: string): boolean;
69
69
  /** Spawn plan missing — applies before auto_chain opt-out for subagent tools. */
70
70
  export declare function evaluateSpawnPlanGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
71
+ /** Block writes / spawns / shell while synthesis checkpoint is due. */
72
+ export declare function evaluateSynthesisGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
71
73
  /** Block writes / unrelated work while pending delegations exist. */
72
74
  export declare function evaluatePendingDelegationGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
73
75
  /**
74
- * Full pre-tool evaluation — same order as Grok PreToolUse:
75
- * pending gate first, then spawn todo gate for subagent tools.
76
+ * Full pre-tool evaluation — synthesis gate first, slice recording, then
77
+ * pending gate, then spawn todo gate for subagent tools.
76
78
  */
77
79
  export declare function evaluatePreToolGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
78
80
  export declare function evaluatePostToolSpawn(toolName: string, toolInput: ToolGateInput, projectRoot: string): PostToolSpawnResult;
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Multi-host delegation gate SSOT — pending-delegations + spawn todo enforcement.
3
- * Grok / Hermes / OpenCode adapters call evaluatePreToolGate; Grok-only logic was
4
- * previously duplicated in grok-hook-utils.js.
3
+ * Grok / Hermes / OpenCode adapters call evaluatePreToolGate via delegation-gate-runtime.
4
+ * Grok-only codex/surface checks remain in pre-tool-use.js after the SSOT gate.
5
5
  */
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import { getActivePendingDelegations, satisfyDelegation, } from './pending-delegations.js';
9
9
  export { getActivePendingDelegations } from './pending-delegations.js';
10
- import { allPlanTodos, hasValidLeadDevPlanForSpawn, loadPersistedLeadDevPlan, validateSpawnMatchesTodo, updatePlanTodoStatus, } from './lead-dev-plan-persistence.js';
10
+ import { allPlanTodos, areSynthesisConsultTodosComplete, hasValidLeadDevPlanForSpawn, isSynthesisRealignmentPlan, loadPersistedLeadDevPlan, getNextRequiredTodo, validateSpawnMatchesTodo, updatePlanTodoStatus, } from './lead-dev-plan-persistence.js';
11
+ import { getSynthesisDueReason, isSynthesisCheckpointDue, recordExecutionSlice, } from './synthesis.js';
11
12
  export { validateSpawnMatchesTodo, updatePlanTodoStatus, savePersistedLeadDevPlan, } from './lead-dev-plan-persistence.js';
12
13
  export const updatePlanTodoStatusInPlace = updatePlanTodoStatus;
13
14
  const READ_TOOLS = new Set([
@@ -30,11 +31,20 @@ const SUBAGENT_TOOLS = new Set(['spawn_subagent', 'task', 'Task', 'delegate_task
30
31
  const ORCHESTRATOR_CONSULT_TOOLS = new Set([
31
32
  'analyze-complexity',
32
33
  'analyze_complexity',
34
+ 'govern-and-apply',
35
+ 'govern_and_apply',
33
36
  'get-orchestration-status',
34
37
  'get_orchestration_status',
35
38
  'orchestrate-task',
36
39
  'orchestrate_task',
37
40
  ]);
41
+ /** Allowed orchestrator MCP tools while synthesis checkpoint is due (govern-and-apply excluded). */
42
+ const SYNTHESIS_ALLOWED_CONSULT_TOOLS = new Set([
43
+ 'analyze-complexity',
44
+ 'analyze_complexity',
45
+ 'get-orchestration-status',
46
+ 'get_orchestration_status',
47
+ ]);
38
48
  const HERMES_WRITE_TOOLS = new Set(['write_file', 'patch', 'write', 'edit']);
39
49
  const OPENCODE_WRITE_TOOLS = new Set(['write', 'edit', 'multiedit']);
40
50
  export function loadDelegationGateFeatures(projectRoot = process.cwd()) {
@@ -122,6 +132,18 @@ function isOrchestratorConsultMcp(toolName, toolInput) {
122
132
  }
123
133
  return false;
124
134
  }
135
+ function isSynthesisAllowedOrchestratorConsult(toolName, toolInput) {
136
+ if (!/mcp|CallMcpTool/i.test(toolName))
137
+ return false;
138
+ const inner = extractMcpToolName(toolInput).toLowerCase();
139
+ if (!inner)
140
+ return false;
141
+ for (const t of SYNTHESIS_ALLOWED_CONSULT_TOOLS) {
142
+ if (inner.includes(t) || inner.includes(t.replace(/-/g, '_')))
143
+ return true;
144
+ }
145
+ return false;
146
+ }
125
147
  function isFocusedTestCommand(cmd) {
126
148
  const cmdText = (cmd || '').toLowerCase();
127
149
  return (/\b(npm\s+(run\s+)?test|vitest\s+run|pnpm\s+test|yarn\s+test)\b/.test(cmdText) &&
@@ -180,6 +202,44 @@ export function evaluateSpawnPlanGate(toolName, toolInput, ctx) {
180
202
  }
181
203
  return { allow: true };
182
204
  }
205
+ /** Block writes / spawns / shell while synthesis checkpoint is due. */
206
+ export function evaluateSynthesisGate(toolName, toolInput, ctx) {
207
+ if (!ctx.sessionId || !isSynthesisCheckpointDue(ctx.projectRoot, ctx.sessionId)) {
208
+ return { allow: true };
209
+ }
210
+ if (isReadOnlyTool(toolName))
211
+ return { allow: true };
212
+ if (isSynthesisAllowedOrchestratorConsult(toolName, toolInput))
213
+ return { allow: true };
214
+ const plan = loadPersistedLeadDevPlan(ctx.projectRoot);
215
+ if (plan &&
216
+ isSynthesisRealignmentPlan(plan) &&
217
+ !areSynthesisConsultTodosComplete(plan) &&
218
+ isSubagentTool(toolName)) {
219
+ const nextTodo = getNextRequiredTodo(plan);
220
+ const validation = validateSpawnMatchesTodo(normalizeHostToolInput(toolInput), ctx.projectRoot, nextTodo);
221
+ if (validation.valid)
222
+ return { allow: true };
223
+ }
224
+ const dueReason = getSynthesisDueReason(ctx.projectRoot, ctx.sessionId);
225
+ const realignmentPending = plan && isSynthesisRealignmentPlan(plan) && !areSynthesisConsultTodosComplete(plan);
226
+ return {
227
+ allow: false,
228
+ reason: realignmentPending
229
+ ? 'Synthesis realignment in progress — complete mandatory consult todos (s.1–s.3) before other work'
230
+ : 'Synthesis checkpoint due — call xray-orchestrator analyze-complexity to reflect and realign before continuing',
231
+ gate: 'synthesis-checkpoint',
232
+ hint: {
233
+ tool: realignmentPending ? 'Task' : 'analyze-complexity',
234
+ dueReason,
235
+ primitive: 'synthesis',
236
+ sessionId: ctx.sessionId,
237
+ ...(realignmentPending && plan
238
+ ? { nextTodoId: getNextRequiredTodo(plan)?.id }
239
+ : {}),
240
+ },
241
+ };
242
+ }
183
243
  /** Block writes / unrelated work while pending delegations exist. */
184
244
  export function evaluatePendingDelegationGate(toolName, toolInput, ctx) {
185
245
  if (!ctx.features.lead_dev_mode || ctx.features.auto_chain_delegations === false) {
@@ -204,10 +264,19 @@ export function evaluatePendingDelegationGate(toolName, toolInput, ctx) {
204
264
  return denyFromPending(pending[0], pending.length);
205
265
  }
206
266
  /**
207
- * Full pre-tool evaluation — same order as Grok PreToolUse:
208
- * pending gate first, then spawn todo gate for subagent tools.
267
+ * Full pre-tool evaluation — synthesis gate first, slice recording, then
268
+ * pending gate, then spawn todo gate for subagent tools.
209
269
  */
210
270
  export function evaluatePreToolGate(toolName, toolInput, ctx) {
271
+ const synthesisBlock = evaluateSynthesisGate(toolName, toolInput, ctx);
272
+ if (!synthesisBlock.allow)
273
+ return synthesisBlock;
274
+ if (!isSynthesisCheckpointDue(ctx.projectRoot, ctx.sessionId)) {
275
+ recordExecutionSlice('gate', {
276
+ projectRoot: ctx.projectRoot,
277
+ sessionId: ctx.sessionId,
278
+ });
279
+ }
211
280
  const pendingBlock = evaluatePendingDelegationGate(toolName, toolInput, ctx);
212
281
  if (!pendingBlock.allow)
213
282
  return pendingBlock;
@@ -230,6 +299,9 @@ export function evaluatePostToolSpawn(toolName, toolInput, projectRoot) {
230
299
  if (normalized.planTodoId)
231
300
  satisfyInput.planTodoId = normalized.planTodoId;
232
301
  const result = satisfyDelegation(satisfyInput, projectRoot);
302
+ if (spawnCheck.valid && spawnCheck.expectedTodoId) {
303
+ updatePlanTodoStatus(spawnCheck.expectedTodoId, 'completed', projectRoot);
304
+ }
233
305
  return {
234
306
  ...result,
235
307
  expectedTodoId: spawnCheck.expectedTodoId ?? null,
@@ -28,6 +28,9 @@ export declare function getNextRequiredTodo(plan: PersistedLeadDevPlan): LeadDev
28
28
  * - all todos complete and persistedAt within TTL
29
29
  */
30
30
  export declare function hasValidLeadDevPlanForSpawn(projectRoot?: string, _sessionId?: string | null): boolean;
31
+ export declare function isSynthesisRealignmentPlan(plan: PersistedLeadDevPlan): boolean;
32
+ export declare function getSynthesisConsultTodos(plan: PersistedLeadDevPlan): LeadDevTodo[];
33
+ export declare function areSynthesisConsultTodosComplete(plan: PersistedLeadDevPlan): boolean;
31
34
  export declare function updatePlanTodoStatus(todoId: string, status: LeadDevTodo['status'], projectRoot?: string): boolean;
32
35
  export declare function bindPlanToSession(sessionId: string, projectRoot?: string): PersistedLeadDevPlan | null;
33
36
  export interface SpawnToolInput {
@@ -4,7 +4,9 @@
4
4
  */
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
+ import { SYNTHESIS_REALIGNMENT_PHASE_ID } from './autonomy-kernel.js';
7
8
  import { DEFAULT_PENDING_TTL_MS } from './pending-delegations.js';
9
+ import { completeSynthesisCheckpoint, getSynthesisCheckpointSessionId, isSynthesisCheckpointDue, recordExecutionSlice, } from './synthesis.js';
8
10
  export function leadDevPlanStatePath(projectRoot = process.cwd()) {
9
11
  return path.join(projectRoot, '.xray', 'state', 'lead-dev-plan.json');
10
12
  }
@@ -64,6 +66,37 @@ export function hasValidLeadDevPlanForSpawn(projectRoot = process.cwd(), _sessio
64
66
  }
65
67
  return Date.now() - new Date(persistedAt).getTime() <= DEFAULT_PENDING_TTL_MS;
66
68
  }
69
+ export function isSynthesisRealignmentPlan(plan) {
70
+ return (plan.phases ?? []).some((p) => p.id === SYNTHESIS_REALIGNMENT_PHASE_ID);
71
+ }
72
+ export function getSynthesisConsultTodos(plan) {
73
+ const phase = (plan.phases ?? []).find((p) => p.id === SYNTHESIS_REALIGNMENT_PHASE_ID);
74
+ return phase?.todos ?? [];
75
+ }
76
+ export function areSynthesisConsultTodosComplete(plan) {
77
+ const todos = getSynthesisConsultTodos(plan);
78
+ return todos.length > 0 && todos.every((t) => t.status === 'completed');
79
+ }
80
+ function shouldRecordGeneralTodoSlice(plan, todoId) {
81
+ if (!isSynthesisRealignmentPlan(plan))
82
+ return true;
83
+ const consultIds = new Set(getSynthesisConsultTodos(plan).map((t) => t.id));
84
+ return !consultIds.has(todoId);
85
+ }
86
+ function tryCompleteSynthesisCheckpointAfterTodo(projectRoot, sessionId) {
87
+ const sid = sessionId ?? getSynthesisCheckpointSessionId(projectRoot);
88
+ if (!sid || !isSynthesisCheckpointDue(projectRoot, sid))
89
+ return;
90
+ const plan = loadPersistedLeadDevPlan(projectRoot);
91
+ if (!plan || !isSynthesisRealignmentPlan(plan))
92
+ return;
93
+ if (!areSynthesisConsultTodosComplete(plan))
94
+ return;
95
+ const completed = completeSynthesisCheckpoint(projectRoot, sid);
96
+ if (completed) {
97
+ void import('./synthesis-completion.js').then(({ runSynthesisCheckpointSideEffects }) => runSynthesisCheckpointSideEffects(projectRoot, sid, plan, completed));
98
+ }
99
+ }
67
100
  export function updatePlanTodoStatus(todoId, status, projectRoot = process.cwd()) {
68
101
  const plan = loadPersistedLeadDevPlan(projectRoot);
69
102
  if (!plan)
@@ -77,8 +110,18 @@ export function updatePlanTodoStatus(todoId, status, projectRoot = process.cwd()
77
110
  }
78
111
  }
79
112
  }
80
- if (updated)
113
+ if (updated) {
81
114
  savePersistedLeadDevPlan(plan, projectRoot);
115
+ if (status === 'completed') {
116
+ if (shouldRecordGeneralTodoSlice(plan, todoId)) {
117
+ const sid = plan.sessionId ?? getSynthesisCheckpointSessionId(projectRoot);
118
+ if (sid) {
119
+ recordExecutionSlice('todo_completed', { projectRoot, sessionId: sid });
120
+ }
121
+ }
122
+ tryCompleteSynthesisCheckpointAfterTodo(projectRoot, plan.sessionId);
123
+ }
124
+ }
82
125
  return updated;
83
126
  }
84
127
  export function bindPlanToSession(sessionId, projectRoot = process.cwd()) {