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
@@ -39,6 +39,7 @@ import {
39
39
  import { join, dirname, relative } from "path";
40
40
  import { fileURLToPath } from "url";
41
41
  import { homedir } from "os";
42
+ import { findProjectRoot } from "../../../scripts/helpers/find-project-root.mjs";
42
43
 
43
44
  const __dirname = dirname(fileURLToPath(import.meta.url));
44
45
 
@@ -52,40 +53,19 @@ let frameworkReady = false;
52
53
  let frameworkLoadAttempted = false;
53
54
 
54
55
  // ── Project root detection ───────────────────────────────────
55
- function findProjectRoot() {
56
- const envHome = process.env.XRAY_HOME || process.env.XRAY_ROOT;
57
- if (envHome && existsSync(join(envHome, "package.json"))) return envHome;
58
-
56
+ function resolveBridgeProjectRoot() {
59
57
  const consumerMarker = join(__dirname, "xray-consumer-root.txt");
60
58
  if (existsSync(consumerMarker)) {
61
59
  try {
62
60
  const marked = readFileSync(consumerMarker, "utf-8").trim();
63
- if (marked && existsSync(join(marked, "package.json"))) return marked;
64
- } catch {
65
- // fall through
66
- }
67
- }
68
-
69
- const candidates = [
70
- process.cwd(),
71
- join(homedir(), "dev", "xray"),
72
- join(dirname(__dirname), "..", "..", "..", ".."), // plugin dir -> project root
73
- ];
74
-
75
- for (const dir of candidates) {
76
- try {
77
- const pkgPath = join(dir, "package.json");
78
- if (existsSync(pkgPath)) {
79
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
80
- if (pkg.name === "0xray" || pkg.dependencies?.["0xray"] || pkg.name === "0xray" || pkg.dependencies?.["0xray"]) {
81
- return dir;
82
- }
61
+ if (marked && existsSync(join(marked, "package.json"))) {
62
+ return marked;
83
63
  }
84
64
  } catch {
85
- continue;
65
+ // fall through to shared SSOT
86
66
  }
87
67
  }
88
- return process.cwd();
68
+ return findProjectRoot([join(dirname(__dirname), "..", "..", "..", "..")]);
89
69
  }
90
70
 
91
71
  // ── Log directory ────────────────────────────────────────────
@@ -381,7 +361,7 @@ async function runProcessors(tool, args, phase, projectRoot, logDir) {
381
361
  // ── Command handlers ─────────────────────────────────────────
382
362
 
383
363
  async function handleHealth(input) {
384
- const projectRoot = findProjectRoot();
364
+ const projectRoot = resolveBridgeProjectRoot();
385
365
  const loaded = await loadFramework(projectRoot);
386
366
 
387
367
  const components = {
@@ -738,7 +718,7 @@ function handleStats() {
738
718
  qualityGateAvailable: !!runQualityGateWithLogging,
739
719
  processorsAvailable: !!ProcessorManager,
740
720
  nodeVersion: process.version,
741
- projectRoot: findProjectRoot(),
721
+ projectRoot: resolveBridgeProjectRoot(),
742
722
  };
743
723
  }
744
724
 
@@ -786,9 +766,67 @@ async function handleApply(input, projectRoot, logDir) {
786
766
  // ── Known commands for positional-arg mode ──────────────────
787
767
  const KNOWN_COMMANDS = new Set([
788
768
  "health", "stats", "pre-process", "post-process", "validate", "codex-check", "hooks", "govern", "apply",
789
- "skill-install", "skill-registry",
769
+ "skill-install", "skill-registry", "delegation-gate",
790
770
  ]);
791
771
 
772
+ async function recordHermesSynthesisTurn(projectRoot, sessionId) {
773
+ if (!sessionId) return;
774
+ try {
775
+ const { recordSynthesisTurnSlice } = await import("../hooks/synthesis-hook-runtime.mjs");
776
+ recordSynthesisTurnSlice(projectRoot, sessionId);
777
+ } catch {
778
+ /* optional */
779
+ }
780
+ }
781
+
782
+ async function handleDelegationGate(command, projectRoot, logDir) {
783
+ try {
784
+ const gate = await import("../hooks/delegation-gate-runtime.mjs");
785
+ const phase = command.phase || "pre";
786
+ const tool = command.tool || command.toolName || "unknown";
787
+ const args = command.args || command.toolInput || {};
788
+ const sessionId = command.sessionId || command.taskId || null;
789
+ const host = command.host || "hermes";
790
+
791
+ if (phase === "post") {
792
+ if (!gate.isSubagentTool(tool)) {
793
+ return { allow: true, phase: "post", skipped: true };
794
+ }
795
+ const result = gate.evaluatePostToolSpawn(tool, args, projectRoot);
796
+ if (result.satisfied.length > 0) {
797
+ logToActivity(
798
+ logDir,
799
+ `[delegation-gate-post] satisfied=${result.satisfied.map((d) => d.id).join(",")} clearedAll=${result.clearedAll}`,
800
+ );
801
+ }
802
+ return { allow: true, phase: "post", ...result };
803
+ }
804
+
805
+ if (command.recordTurn === true || command.event === 'user_prompt_submit') {
806
+ await recordHermesSynthesisTurn(projectRoot, sessionId);
807
+ }
808
+
809
+ const features = gate.loadDelegationGateFeatures(projectRoot);
810
+ const outcome = gate.evaluatePreToolGate(tool, args, {
811
+ projectRoot,
812
+ sessionId,
813
+ features,
814
+ host,
815
+ });
816
+
817
+ if (!outcome.allow) {
818
+ logToActivity(
819
+ logDir,
820
+ `[delegation-gate] deny tool=${tool} gate=${outcome.gate} reason=${outcome.reason}`,
821
+ );
822
+ }
823
+
824
+ return { phase: "pre", ...outcome };
825
+ } catch (error) {
826
+ return { error: `delegation-gate failed: ${error.message || error}` };
827
+ }
828
+ }
829
+
792
830
  // ── Skill management ──────────────────────────────────────────
793
831
 
794
832
  async function handleSkillInstall(command, projectRoot, logDir) {
@@ -869,7 +907,7 @@ async function main() {
869
907
  }
870
908
  }
871
909
 
872
- const projectRoot = findProjectRoot();
910
+ const projectRoot = resolveBridgeProjectRoot();
873
911
  const logDir = ensureLogDir(projectRoot);
874
912
 
875
913
  // Log session start for test verification
@@ -918,6 +956,9 @@ async function main() {
918
956
  case "skill-registry":
919
957
  response = await handleSkillRegistry(command, projectRoot, logDir);
920
958
  break;
959
+ case "delegation-gate":
960
+ response = await handleDelegationGate(command, projectRoot, logDir);
961
+ break;
921
962
  default:
922
963
  response = { error: `Unknown command: ${cmd}` };
923
964
  }
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Load compiled delegation-gate SSOT from consumer or package dist.
4
+ * Used by Grok hooks, Hermes bridge, and verify fixtures.
5
+ */
6
+ import { createRequire } from 'node:module';
7
+ import { existsSync } from 'node:fs';
8
+ import { dirname, join, resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+
13
+ /** 0xray package root — never use consumer workspace env (GROK_WORKSPACE_ROOT / XRAY_ROOT). */
14
+ function resolvePackageRoot() {
15
+ let dir = __dirname;
16
+ for (let i = 0; i < 10; i++) {
17
+ const pkg = join(dir, 'package.json');
18
+ if (existsSync(pkg)) {
19
+ try {
20
+ const require = createRequire(join(dir, 'package.json'));
21
+ const name = require('./package.json').name;
22
+ if (name === '0xray') return dir;
23
+ } catch {
24
+ /* continue */
25
+ }
26
+ }
27
+ const nm = join(dir, 'node_modules', '0xray', 'package.json');
28
+ if (existsSync(nm)) {
29
+ return dirname(nm);
30
+ }
31
+ const parent = dirname(dir);
32
+ if (parent === dir) break;
33
+ dir = parent;
34
+ }
35
+ return resolve(__dirname, '../../..');
36
+ }
37
+
38
+ function loadDelegationGate() {
39
+ const root = resolvePackageRoot();
40
+ const candidates = [
41
+ join(__dirname, '../../nucleus/delegation-gate.js'),
42
+ join(root, 'dist/nucleus/delegation-gate.js'),
43
+ join(process.cwd(), 'node_modules/0xray/dist/nucleus/delegation-gate.js'),
44
+ ];
45
+ const found = candidates.find((p) => existsSync(p));
46
+ if (!found) {
47
+ throw new Error(
48
+ 'delegation-gate.js missing — run npm run build in 0xray or npm install 0xray@^3.5.1',
49
+ );
50
+ }
51
+ return createRequire(import.meta.url)(found);
52
+ }
53
+
54
+ const gate = loadDelegationGate();
55
+
56
+ export const {
57
+ loadDelegationGateFeatures,
58
+ normalizeHostToolInput,
59
+ evaluatePreToolGate,
60
+ evaluatePendingDelegationGate,
61
+ evaluateSpawnPlanGate,
62
+ evaluatePostToolSpawn,
63
+ checkPendingDelegationGate,
64
+ checkSubagentGate,
65
+ satisfyDelegationsFromToolInput,
66
+ isSubagentTool,
67
+ isOrchestrateToolEvent,
68
+ isReadOnlyTool,
69
+ isWriteTool,
70
+ isShellTool,
71
+ getActivePendingDelegations,
72
+ validateSpawnMatchesTodo,
73
+ updatePlanTodoStatus,
74
+ updatePlanTodoStatusInPlace,
75
+ } = gate;
@@ -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;