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.
- package/.gitignore.default +8 -0
- package/.opencode/hooks/post-commit +7 -27
- package/.xray/features.json +6 -0
- package/.xray/features.schema.json +17 -0
- package/AGENTS-consumer.md +1 -1
- package/AGENTS.md +1 -1
- package/README.md +1 -1
- package/SKILLS.md +1 -1
- package/dist/.gitignore.default +8 -0
- package/dist/AGENTS.md +1 -1
- package/dist/CHANGELOG.md +16 -0
- package/dist/README.md +1 -1
- package/dist/SKILLS.md +1 -1
- package/dist/core/bridge.mjs +10 -32
- package/dist/core/features-config.d.ts +16 -0
- package/dist/core/features-config.js +24 -0
- package/dist/integrations/grok/hooks/grok-hook-utils.js +2 -2
- package/dist/integrations/grok/hooks/post-tool-use.js +1 -1
- package/dist/integrations/grok/hooks/pre-tool-use.js +23 -32
- package/dist/integrations/grok/hooks/session-start.js +20 -1
- package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +2 -1
- package/dist/integrations/hermes-agent/bridge.mjs +22 -50
- package/dist/integrations/hooks/synthesis-hook-runtime.mjs +50 -0
- package/dist/mcps/orchestrator/handlers/complexity-handler.d.ts +3 -0
- package/dist/mcps/orchestrator/handlers/complexity-handler.js +25 -10
- package/dist/mcps/orchestrator/server.js +124 -4
- package/dist/memory-routing/types.d.ts +7 -0
- package/dist/nucleus/autonomy-kernel.d.ts +3 -0
- package/dist/nucleus/autonomy-kernel.js +40 -0
- package/dist/nucleus/delegation-gate.d.ts +6 -4
- package/dist/nucleus/delegation-gate.js +77 -5
- package/dist/nucleus/lead-dev-plan-persistence.d.ts +3 -0
- package/dist/nucleus/lead-dev-plan-persistence.js +44 -1
- package/dist/nucleus/synthesis-completion.d.ts +6 -0
- package/dist/nucleus/synthesis-completion.js +29 -0
- package/dist/nucleus/synthesis-context.d.ts +31 -0
- package/dist/nucleus/synthesis-context.js +86 -0
- package/dist/nucleus/synthesis.d.ts +54 -0
- package/dist/nucleus/synthesis.js +196 -0
- package/dist/plugin/xray-codex-injection.js +17 -0
- package/package.json +7 -3
- package/scripts/helpers/find-project-root.mjs +92 -0
- package/scripts/hooks/install-hooks.cjs +136 -36
- package/scripts/hooks/run-hook.js +46 -9
- package/scripts/mjs/delegation-gate-fixture.mjs +37 -0
- package/scripts/node/auto-reflection-generator.mjs +7 -0
- package/scripts/node/consumer-gitignore.cjs +101 -0
- package/scripts/node/load-reflection-config.mjs +77 -0
- package/scripts/node/postinstall.cjs +9 -6
- package/src/integrations/grok/plugin/0xray/hooks/hooks.json +2 -1
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +33 -8
- package/src/mcps/orchestrator/server.ts +181 -5
- package/xray/features.json +6 -0
- 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
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
73
|
+
if (!synthesisCheckpoint) {
|
|
74
|
+
clearPendingDelegations();
|
|
75
|
+
}
|
|
70
76
|
const planPath = persistLeadDevPlan(leadDevPlan);
|
|
71
77
|
const bound = bindPlanToSession(sessionId);
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
|
4
|
-
*
|
|
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 —
|
|
75
|
-
* pending gate
|
|
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
|
|
4
|
-
*
|
|
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 —
|
|
208
|
-
* pending gate
|
|
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()) {
|