0xray 3.5.0 → 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 (60) 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 +32 -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 +32 -385
  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/__init__.py +50 -3
  23. package/dist/integrations/hermes-agent/bridge.mjs +71 -30
  24. package/dist/integrations/hooks/delegation-gate-runtime.mjs +75 -0
  25. package/dist/integrations/hooks/synthesis-hook-runtime.mjs +50 -0
  26. package/dist/mcps/orchestrator/handlers/complexity-handler.d.ts +3 -0
  27. package/dist/mcps/orchestrator/handlers/complexity-handler.js +25 -10
  28. package/dist/mcps/orchestrator/server.js +124 -4
  29. package/dist/memory-routing/types.d.ts +7 -0
  30. package/dist/nucleus/autonomy-kernel.d.ts +3 -0
  31. package/dist/nucleus/autonomy-kernel.js +40 -0
  32. package/dist/nucleus/delegation-gate.d.ts +88 -0
  33. package/dist/nucleus/delegation-gate.js +347 -0
  34. package/dist/nucleus/lead-dev-plan-persistence.d.ts +3 -0
  35. package/dist/nucleus/lead-dev-plan-persistence.js +47 -4
  36. package/dist/nucleus/synthesis-completion.d.ts +6 -0
  37. package/dist/nucleus/synthesis-completion.js +29 -0
  38. package/dist/nucleus/synthesis-context.d.ts +31 -0
  39. package/dist/nucleus/synthesis-context.js +86 -0
  40. package/dist/nucleus/synthesis.d.ts +54 -0
  41. package/dist/nucleus/synthesis.js +196 -0
  42. package/dist/plugin/xray-codex-injection.js +50 -0
  43. package/package.json +11 -2
  44. package/scripts/helpers/find-project-root.mjs +92 -0
  45. package/scripts/hooks/install-hooks.cjs +136 -36
  46. package/scripts/hooks/run-hook.js +46 -9
  47. package/scripts/mjs/delegation-gate-fixture.mjs +121 -0
  48. package/scripts/mjs/verify-delegation-gate-core.mjs +99 -0
  49. package/scripts/mjs/verify-grok-delegation-gate.mjs +9 -69
  50. package/scripts/mjs/verify-hermes-delegation-gate.mjs +89 -0
  51. package/scripts/node/auto-reflection-generator.mjs +7 -0
  52. package/scripts/node/consumer-gitignore.cjs +101 -0
  53. package/scripts/node/load-reflection-config.mjs +77 -0
  54. package/scripts/node/postinstall.cjs +9 -6
  55. package/scripts/node/universal-version-manager.js +1 -1
  56. package/src/integrations/grok/plugin/0xray/hooks/hooks.json +2 -1
  57. package/src/mcps/orchestrator/handlers/complexity-handler.ts +33 -8
  58. package/src/mcps/orchestrator/server.ts +181 -5
  59. package/xray/features.json +6 -0
  60. package/xray/features.schema.json +17 -0
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Multi-host delegation gate SSOT — pending-delegations + spawn todo enforcement.
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
+ */
6
+ import { type PendingDelegation } from './pending-delegations.js';
7
+ export { getActivePendingDelegations } from './pending-delegations.js';
8
+ import { updatePlanTodoStatus, type SpawnToolInput } from './lead-dev-plan-persistence.js';
9
+ export { validateSpawnMatchesTodo, updatePlanTodoStatus, savePersistedLeadDevPlan, } from './lead-dev-plan-persistence.js';
10
+ export declare const updatePlanTodoStatusInPlace: typeof updatePlanTodoStatus;
11
+ export type DelegationGateHost = 'grok' | 'hermes' | 'opencode' | 'openclaw' | 'generic';
12
+ export interface DelegationGateFeatures {
13
+ lead_dev_mode: boolean;
14
+ auto_chain_delegations: boolean;
15
+ }
16
+ export interface ToolGateInput {
17
+ prompt?: string;
18
+ description?: string;
19
+ task?: string;
20
+ subagent_type?: string;
21
+ agent?: string;
22
+ delegationId?: string;
23
+ delegation_id?: string;
24
+ planTodoId?: string;
25
+ command?: string;
26
+ path?: string;
27
+ file_path?: string;
28
+ filePath?: string;
29
+ toolName?: string;
30
+ tool?: string;
31
+ name?: string;
32
+ server?: string;
33
+ mcpServer?: string;
34
+ arguments?: Record<string, unknown>;
35
+ args?: Record<string, unknown>;
36
+ [key: string]: unknown;
37
+ }
38
+ export interface PreToolGateContext {
39
+ projectRoot: string;
40
+ sessionId: string | null;
41
+ features: DelegationGateFeatures;
42
+ host?: DelegationGateHost;
43
+ }
44
+ export interface PreToolGateDeny {
45
+ allow: false;
46
+ reason: string;
47
+ gate: string;
48
+ hint?: Record<string, unknown>;
49
+ pendingCount?: number;
50
+ delegationId?: string;
51
+ }
52
+ export interface PreToolGateAllow {
53
+ allow: true;
54
+ }
55
+ export type PreToolGateResult = PreToolGateAllow | PreToolGateDeny;
56
+ export interface PostToolSpawnResult {
57
+ satisfied: PendingDelegation[];
58
+ clearedAll: boolean;
59
+ expectedTodoId?: string | null;
60
+ }
61
+ export declare function loadDelegationGateFeatures(projectRoot?: string): DelegationGateFeatures;
62
+ export declare function normalizeHostToolInput(toolInput: ToolGateInput): SpawnToolInput & {
63
+ command?: string;
64
+ };
65
+ export declare function isSubagentTool(toolName: string): boolean;
66
+ export declare function isShellTool(toolName: string): boolean;
67
+ export declare function isWriteTool(toolName: string, host?: DelegationGateHost): boolean;
68
+ export declare function isReadOnlyTool(toolName: string): boolean;
69
+ /** Spawn plan missing — applies before auto_chain opt-out for subagent tools. */
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;
73
+ /** Block writes / unrelated work while pending delegations exist. */
74
+ export declare function evaluatePendingDelegationGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
75
+ /**
76
+ * Full pre-tool evaluation — synthesis gate first, slice recording, then
77
+ * pending gate, then spawn todo gate for subagent tools.
78
+ */
79
+ export declare function evaluatePreToolGate(toolName: string, toolInput: ToolGateInput, ctx: PreToolGateContext): PreToolGateResult;
80
+ export declare function evaluatePostToolSpawn(toolName: string, toolInput: ToolGateInput, projectRoot: string): PostToolSpawnResult;
81
+ export declare function isOrchestrateToolEvent(toolName: string, toolInput?: ToolGateInput): boolean;
82
+ /** Back-compat shims for Grok hook-utils and tests. */
83
+ export declare function checkPendingDelegationGate(toolName: string, toolInput: ToolGateInput, features: DelegationGateFeatures, projectRoot: string, sessionId: string | null): PreToolGateDeny | null;
84
+ export declare function checkSubagentGate(toolName: string, features: DelegationGateFeatures, projectRoot: string, sessionId: string | null, toolInput?: ToolGateInput): PreToolGateDeny | null;
85
+ export declare function satisfyDelegationsFromToolInput(toolInput: ToolGateInput, projectRoot: string): {
86
+ satisfied: PendingDelegation[];
87
+ clearedAll: boolean;
88
+ };
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Multi-host delegation gate SSOT — pending-delegations + spawn todo enforcement.
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
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { getActivePendingDelegations, satisfyDelegation, } from './pending-delegations.js';
9
+ export { getActivePendingDelegations } from './pending-delegations.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';
12
+ export { validateSpawnMatchesTodo, updatePlanTodoStatus, savePersistedLeadDevPlan, } from './lead-dev-plan-persistence.js';
13
+ export const updatePlanTodoStatusInPlace = updatePlanTodoStatus;
14
+ const READ_TOOLS = new Set([
15
+ 'read_file',
16
+ 'Read',
17
+ 'read',
18
+ 'grep',
19
+ 'Grep',
20
+ 'glob',
21
+ 'Glob',
22
+ 'list_dir',
23
+ 'ListDir',
24
+ 'web_search',
25
+ 'WebSearch',
26
+ 'codebase_search',
27
+ 'SemanticSearch',
28
+ 'search_files',
29
+ ]);
30
+ const SUBAGENT_TOOLS = new Set(['spawn_subagent', 'task', 'Task', 'delegate_task']);
31
+ const ORCHESTRATOR_CONSULT_TOOLS = new Set([
32
+ 'analyze-complexity',
33
+ 'analyze_complexity',
34
+ 'govern-and-apply',
35
+ 'govern_and_apply',
36
+ 'get-orchestration-status',
37
+ 'get_orchestration_status',
38
+ 'orchestrate-task',
39
+ 'orchestrate_task',
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
+ ]);
48
+ const HERMES_WRITE_TOOLS = new Set(['write_file', 'patch', 'write', 'edit']);
49
+ const OPENCODE_WRITE_TOOLS = new Set(['write', 'edit', 'multiedit']);
50
+ export function loadDelegationGateFeatures(projectRoot = process.cwd()) {
51
+ const featuresPath = path.join(projectRoot, '.xray', 'features.json');
52
+ if (!fs.existsSync(featuresPath)) {
53
+ return { lead_dev_mode: true, auto_chain_delegations: true };
54
+ }
55
+ try {
56
+ const data = JSON.parse(fs.readFileSync(featuresPath, 'utf8'));
57
+ const orch = data.multi_agent_orchestration ?? {};
58
+ return {
59
+ lead_dev_mode: orch.enabled !== false && orch.lead_dev_mode !== false,
60
+ auto_chain_delegations: orch.auto_chain_delegations !== false,
61
+ };
62
+ }
63
+ catch {
64
+ return { lead_dev_mode: true, auto_chain_delegations: true };
65
+ }
66
+ }
67
+ export function normalizeHostToolInput(toolInput) {
68
+ const normalized = {
69
+ prompt: String(toolInput.prompt ?? toolInput.description ?? toolInput.task ?? ''),
70
+ subagent_type: String(toolInput.subagent_type ?? toolInput.agent ?? ''),
71
+ };
72
+ if (toolInput.description != null)
73
+ normalized.description = String(toolInput.description);
74
+ if (toolInput.task != null)
75
+ normalized.task = String(toolInput.task);
76
+ if (toolInput.agent != null)
77
+ normalized.agent = String(toolInput.agent);
78
+ const delegationId = toolInput.delegationId ?? toolInput.delegation_id;
79
+ if (delegationId != null)
80
+ normalized.delegationId = String(delegationId);
81
+ if (toolInput.planTodoId != null)
82
+ normalized.planTodoId = String(toolInput.planTodoId);
83
+ if (toolInput.command != null)
84
+ normalized.command = String(toolInput.command);
85
+ return normalized;
86
+ }
87
+ export function isSubagentTool(toolName) {
88
+ return SUBAGENT_TOOLS.has(toolName) || /^task$/i.test(toolName) || toolName === 'spawn_subagent';
89
+ }
90
+ export function isShellTool(toolName) {
91
+ return /terminal|bash|shell|run_terminal/i.test(toolName);
92
+ }
93
+ export function isWriteTool(toolName, host = 'generic') {
94
+ if (host === 'hermes' && HERMES_WRITE_TOOLS.has(toolName))
95
+ return true;
96
+ if (host === 'opencode' && OPENCODE_WRITE_TOOLS.has(toolName))
97
+ return true;
98
+ return (/write|edit|replace|patch|notebook/i.test(toolName) &&
99
+ !isReadOnlyTool(toolName));
100
+ }
101
+ export function isReadOnlyTool(toolName) {
102
+ return READ_TOOLS.has(toolName) || /^(read|grep|glob|list|search_files)/i.test(toolName);
103
+ }
104
+ function extractMcpToolName(toolInput) {
105
+ const candidates = [
106
+ toolInput.toolName,
107
+ toolInput.tool,
108
+ toolInput.name,
109
+ toolInput.mcpToolName,
110
+ ];
111
+ for (const c of candidates) {
112
+ if (c)
113
+ return String(c);
114
+ }
115
+ const server = String(toolInput.server ?? toolInput.mcpServer ?? '');
116
+ const args = (toolInput.arguments ?? toolInput.args ?? {});
117
+ if (args.toolName)
118
+ return String(args.toolName);
119
+ if (server.includes('orchestrator') && args.name)
120
+ return String(args.name);
121
+ return '';
122
+ }
123
+ function isOrchestratorConsultMcp(toolName, toolInput) {
124
+ if (!/mcp|CallMcpTool/i.test(toolName))
125
+ return false;
126
+ const inner = extractMcpToolName(toolInput).toLowerCase();
127
+ if (!inner)
128
+ return false;
129
+ for (const t of ORCHESTRATOR_CONSULT_TOOLS) {
130
+ if (inner.includes(t) || inner.includes(t.replace(/-/g, '_')))
131
+ return true;
132
+ }
133
+ return false;
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
+ }
147
+ function isFocusedTestCommand(cmd) {
148
+ const cmdText = (cmd || '').toLowerCase();
149
+ return (/\b(npm\s+(run\s+)?test|vitest\s+run|pnpm\s+test|yarn\s+test)\b/.test(cmdText) &&
150
+ (/--\s+\S+\.test/.test(cmdText) || /vitest\s+run\s+\S+\.test/.test(cmdText)));
151
+ }
152
+ function denyFromSpawnValidation(validation) {
153
+ const deny = {
154
+ allow: false,
155
+ reason: validation.reason ?? 'Spawn blocked by lead-dev plan',
156
+ gate: validation.gate ?? 'spawn-todo-persistence',
157
+ };
158
+ if (validation.hint)
159
+ deny.hint = validation.hint;
160
+ return deny;
161
+ }
162
+ function denyFromPending(primary, pendingCount) {
163
+ return {
164
+ allow: false,
165
+ reason: 'Pending implementation delegation — spawn host Task before other work',
166
+ gate: 'auto-chain-pending',
167
+ hint: { ...(primary.spawnHint ?? {
168
+ tool: 'Task',
169
+ subagent_type: primary.agent,
170
+ description: primary.taskDescription,
171
+ planTodoId: primary.planTodoId,
172
+ delegationId: primary.id,
173
+ }) },
174
+ pendingCount,
175
+ delegationId: primary.id,
176
+ };
177
+ }
178
+ /** Spawn plan missing — applies before auto_chain opt-out for subagent tools. */
179
+ export function evaluateSpawnPlanGate(toolName, toolInput, ctx) {
180
+ if (!ctx.features.lead_dev_mode || !isSubagentTool(toolName)) {
181
+ return { allow: true };
182
+ }
183
+ if (!hasValidLeadDevPlanForSpawn(ctx.projectRoot, ctx.sessionId)) {
184
+ return {
185
+ allow: false,
186
+ reason: 'Codex 59/67: call xray-orchestrator analyze-complexity first — ' +
187
+ 'writes .xray/state/lead-dev-plan.json required before spawn_subagent',
188
+ gate: 'spawn-plan-missing',
189
+ };
190
+ }
191
+ if (ctx.features.auto_chain_delegations === false) {
192
+ return { allow: true };
193
+ }
194
+ const pending = getActivePendingDelegations(ctx.sessionId, ctx.projectRoot);
195
+ const plan = loadPersistedLeadDevPlan(ctx.projectRoot);
196
+ const expectedTodo = pending[0]?.planTodoId && plan
197
+ ? allPlanTodos(plan).find((t) => t.id === pending[0].planTodoId) ?? null
198
+ : null;
199
+ const validation = validateSpawnMatchesTodo(normalizeHostToolInput(toolInput), ctx.projectRoot, expectedTodo);
200
+ if (!validation.valid) {
201
+ return denyFromSpawnValidation(validation);
202
+ }
203
+ return { allow: true };
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
+ }
243
+ /** Block writes / unrelated work while pending delegations exist. */
244
+ export function evaluatePendingDelegationGate(toolName, toolInput, ctx) {
245
+ if (!ctx.features.lead_dev_mode || ctx.features.auto_chain_delegations === false) {
246
+ return { allow: true };
247
+ }
248
+ if (!ctx.sessionId)
249
+ return { allow: true };
250
+ const pending = getActivePendingDelegations(ctx.sessionId, ctx.projectRoot);
251
+ if (pending.length === 0)
252
+ return { allow: true };
253
+ if (isSubagentTool(toolName))
254
+ return { allow: true };
255
+ if (isReadOnlyTool(toolName))
256
+ return { allow: true };
257
+ if (isOrchestratorConsultMcp(toolName, toolInput))
258
+ return { allow: true };
259
+ if (isShellTool(toolName)) {
260
+ const cmd = String(toolInput.command ?? '');
261
+ if (isFocusedTestCommand(cmd))
262
+ return { allow: true };
263
+ }
264
+ return denyFromPending(pending[0], pending.length);
265
+ }
266
+ /**
267
+ * Full pre-tool evaluation — synthesis gate first, slice recording, then
268
+ * pending gate, then spawn todo gate for subagent tools.
269
+ */
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
+ }
280
+ const pendingBlock = evaluatePendingDelegationGate(toolName, toolInput, ctx);
281
+ if (!pendingBlock.allow)
282
+ return pendingBlock;
283
+ const spawnBlock = evaluateSpawnPlanGate(toolName, toolInput, ctx);
284
+ if (!spawnBlock.allow)
285
+ return spawnBlock;
286
+ return { allow: true };
287
+ }
288
+ export function evaluatePostToolSpawn(toolName, toolInput, projectRoot) {
289
+ const normalized = normalizeHostToolInput(toolInput);
290
+ const spawnCheck = validateSpawnMatchesTodo(normalized, projectRoot);
291
+ const satisfyInput = {
292
+ toolPrompt: normalized.prompt ?? '',
293
+ };
294
+ if (normalized.delegationId)
295
+ satisfyInput.delegationId = normalized.delegationId;
296
+ const agent = normalized.subagent_type || normalized.agent;
297
+ if (agent)
298
+ satisfyInput.agent = agent;
299
+ if (normalized.planTodoId)
300
+ satisfyInput.planTodoId = normalized.planTodoId;
301
+ const result = satisfyDelegation(satisfyInput, projectRoot);
302
+ if (spawnCheck.valid && spawnCheck.expectedTodoId) {
303
+ updatePlanTodoStatus(spawnCheck.expectedTodoId, 'completed', projectRoot);
304
+ }
305
+ return {
306
+ ...result,
307
+ expectedTodoId: spawnCheck.expectedTodoId ?? null,
308
+ };
309
+ }
310
+ export function isOrchestrateToolEvent(toolName, toolInput = {}) {
311
+ const inner = extractMcpToolName(toolInput).toLowerCase();
312
+ if (/orchestrate[-_]?task/.test(inner))
313
+ return true;
314
+ const blob = JSON.stringify(toolInput).toLowerCase();
315
+ return /orchestrate[-_]?task/.test(blob);
316
+ }
317
+ /** Back-compat shims for Grok hook-utils and tests. */
318
+ export function checkPendingDelegationGate(toolName, toolInput, features, projectRoot, sessionId) {
319
+ const result = evaluatePendingDelegationGate(toolName, toolInput, {
320
+ projectRoot,
321
+ sessionId,
322
+ features,
323
+ });
324
+ return result.allow ? null : result;
325
+ }
326
+ export function checkSubagentGate(toolName, features, projectRoot, sessionId, toolInput = {}) {
327
+ const result = evaluateSpawnPlanGate(toolName, toolInput, {
328
+ projectRoot,
329
+ sessionId,
330
+ features,
331
+ });
332
+ return result.allow ? null : result;
333
+ }
334
+ export function satisfyDelegationsFromToolInput(toolInput, projectRoot) {
335
+ const normalized = normalizeHostToolInput(toolInput);
336
+ const satisfyInput = {
337
+ toolPrompt: normalized.prompt ?? '',
338
+ };
339
+ if (normalized.delegationId)
340
+ satisfyInput.delegationId = normalized.delegationId;
341
+ const agent = normalized.subagent_type || normalized.agent;
342
+ if (agent)
343
+ satisfyInput.agent = agent;
344
+ if (normalized.planTodoId)
345
+ satisfyInput.planTodoId = normalized.planTodoId;
346
+ return satisfyDelegation(satisfyInput, projectRoot);
347
+ }
@@ -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
  }
@@ -26,15 +28,15 @@ export function savePersistedLeadDevPlan(plan, projectRoot = process.cwd()) {
26
28
  return planPath;
27
29
  }
28
30
  export function allPlanTodos(plan) {
29
- return plan.phases.flatMap((p) => p.todos);
31
+ return (plan.phases ?? []).flatMap((p) => p.todos ?? []);
30
32
  }
31
33
  export function getOutstandingTodos(plan) {
32
34
  return allPlanTodos(plan).filter((t) => t.status === 'pending' || t.status === 'in_progress');
33
35
  }
34
36
  /** First outstanding todo in phase order */
35
37
  export function getNextRequiredTodo(plan) {
36
- for (const phase of plan.phases) {
37
- const next = phase.todos.find((t) => t.status === 'pending' || t.status === 'in_progress');
38
+ for (const phase of plan.phases ?? []) {
39
+ const next = (phase.todos ?? []).find((t) => t.status === 'pending' || t.status === 'in_progress');
38
40
  if (next)
39
41
  return next;
40
42
  }
@@ -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()) {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Post-checkpoint side effects — meta-inference refresh + repertoire feedback.
3
+ */
4
+ import type { PersistedLeadDevPlan } from './lead-dev-plan-persistence.js';
5
+ import type { SynthesisCheckpointState } from './synthesis.js';
6
+ export declare function runSynthesisCheckpointSideEffects(projectRoot: string, sessionId: string, plan: PersistedLeadDevPlan | null, completedState: SynthesisCheckpointState): Promise<void>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Post-checkpoint side effects — meta-inference refresh + repertoire feedback.
3
+ */
4
+ import { getMemoryRoutingProvider } from '../memory-routing/index.js';
5
+ import { getSynthesisConsultTodos, isSynthesisRealignmentPlan } from './lead-dev-plan-persistence.js';
6
+ export async function runSynthesisCheckpointSideEffects(projectRoot, sessionId, plan, completedState) {
7
+ try {
8
+ const provider = await getMemoryRoutingProvider();
9
+ if (provider.refreshMetaInference) {
10
+ await provider.refreshMetaInference().catch(() => undefined);
11
+ }
12
+ if (provider.ingestFeedback && plan && isSynthesisRealignmentPlan(plan)) {
13
+ const consultTodos = getSynthesisConsultTodos(plan);
14
+ provider.ingestFeedback({
15
+ timestamp: new Date().toISOString(),
16
+ sessionId,
17
+ taskId: `synthesis-checkpoint-${completedState.synthesisCount}`,
18
+ assignedAgent: 'orchestrator',
19
+ memorySignals: ['synthesis', ...consultTodos.map((t) => t.subagent)],
20
+ complexity: 30,
21
+ success: true,
22
+ durationMs: 0,
23
+ });
24
+ }
25
+ }
26
+ catch {
27
+ // non-blocking — checkpoint already cleared
28
+ }
29
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Collocated context for synthesis Aside — delegates to memory routing provider SSOT.
3
+ */
4
+ export interface SynthesisCollocatedContext {
5
+ primitive: 'synthesis';
6
+ dueReason: string | null;
7
+ collatedText: string;
8
+ matchedSignals?: Array<{
9
+ name: string;
10
+ definition: string;
11
+ priority: string;
12
+ }>;
13
+ codexTermCount?: number;
14
+ codexExcerpt?: string;
15
+ planExcerpt?: string;
16
+ synthesisExcerpt?: string;
17
+ }
18
+ export declare function buildSynthesisCollocatedContext(projectRoot?: string, dueReason?: string | null): Promise<SynthesisCollocatedContext>;
19
+ /** Refresh meta-inference before collation so synthesis.md is current for reflect. */
20
+ export declare function prepareSynthesisCollocatedContext(projectRoot?: string, dueReason?: string | null): Promise<SynthesisCollocatedContext>;
21
+ export declare function isAnalyzeComplexitySuccess(content: Array<{
22
+ type: string;
23
+ text: string;
24
+ }>): boolean;
25
+ export declare function appendSynthesisContextToResponse(content: Array<{
26
+ type: string;
27
+ text: string;
28
+ }>, collatedText: string): Array<{
29
+ type: string;
30
+ text: string;
31
+ }>;