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,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
|
+
}>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collocated context for synthesis Aside — delegates to memory routing provider SSOT.
|
|
3
|
+
*/
|
|
4
|
+
import { getMemoryRoutingProvider } from '../memory-routing/index.js';
|
|
5
|
+
function fallbackContext(dueReason) {
|
|
6
|
+
const lines = [
|
|
7
|
+
'# Synthesis checkpoint',
|
|
8
|
+
dueReason ? `Due: ${dueReason}` : '',
|
|
9
|
+
'(Enable memory_routing with Repertoire for full signal + codex + plan collation)',
|
|
10
|
+
].filter(Boolean);
|
|
11
|
+
return {
|
|
12
|
+
primitive: 'synthesis',
|
|
13
|
+
dueReason,
|
|
14
|
+
collatedText: lines.join('\n\n'),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function normalizeProviderContext(raw, dueReason) {
|
|
18
|
+
const collatedText = typeof raw.collatedText === 'string' && raw.collatedText.trim()
|
|
19
|
+
? dueReason && !raw.collatedText.includes('Due:')
|
|
20
|
+
? `# Synthesis checkpoint\n\nDue: ${dueReason}\n\n${raw.collatedText}`
|
|
21
|
+
: raw.collatedText
|
|
22
|
+
: fallbackContext(dueReason).collatedText;
|
|
23
|
+
const matchedSignals = Array.isArray(raw.matchedSignals)
|
|
24
|
+
? raw.matchedSignals
|
|
25
|
+
: undefined;
|
|
26
|
+
return {
|
|
27
|
+
primitive: 'synthesis',
|
|
28
|
+
dueReason,
|
|
29
|
+
collatedText,
|
|
30
|
+
...(matchedSignals ? { matchedSignals } : {}),
|
|
31
|
+
...(typeof raw.codexTermCount === 'number' ? { codexTermCount: raw.codexTermCount } : {}),
|
|
32
|
+
...(typeof raw.codexExcerpt === 'string' ? { codexExcerpt: raw.codexExcerpt } : {}),
|
|
33
|
+
...(typeof raw.planExcerpt === 'string' ? { planExcerpt: raw.planExcerpt } : {}),
|
|
34
|
+
...(typeof raw.synthesisExcerpt === 'string'
|
|
35
|
+
? { synthesisExcerpt: raw.synthesisExcerpt }
|
|
36
|
+
: {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export async function buildSynthesisCollocatedContext(projectRoot = process.cwd(), dueReason = null) {
|
|
40
|
+
try {
|
|
41
|
+
const provider = await getMemoryRoutingProvider();
|
|
42
|
+
if (provider.isAvailable() && provider.buildSynthesisContext) {
|
|
43
|
+
const raw = provider.buildSynthesisContext({ projectRoot, dueReason });
|
|
44
|
+
if (raw)
|
|
45
|
+
return normalizeProviderContext(raw, dueReason);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// fall through to minimal fallback
|
|
50
|
+
}
|
|
51
|
+
return fallbackContext(dueReason);
|
|
52
|
+
}
|
|
53
|
+
/** Refresh meta-inference before collation so synthesis.md is current for reflect. */
|
|
54
|
+
export async function prepareSynthesisCollocatedContext(projectRoot = process.cwd(), dueReason = null) {
|
|
55
|
+
try {
|
|
56
|
+
const provider = await getMemoryRoutingProvider();
|
|
57
|
+
if (provider.refreshMetaInference) {
|
|
58
|
+
await provider.refreshMetaInference().catch(() => undefined);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// non-blocking — collation still proceeds
|
|
63
|
+
}
|
|
64
|
+
return buildSynthesisCollocatedContext(projectRoot, dueReason);
|
|
65
|
+
}
|
|
66
|
+
export function isAnalyzeComplexitySuccess(content) {
|
|
67
|
+
const text = content.map((c) => c.text).join('\n');
|
|
68
|
+
return !text.includes('❌') && !/^error handling tool/i.test(text);
|
|
69
|
+
}
|
|
70
|
+
export function appendSynthesisContextToResponse(content, collatedText) {
|
|
71
|
+
if (!collatedText.trim())
|
|
72
|
+
return content;
|
|
73
|
+
const synthesisSection = `
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Synthesis checkpoint — collocated context
|
|
78
|
+
|
|
79
|
+
${collatedText}
|
|
80
|
+
|
|
81
|
+
**Next:** Spawn Task for each mandatory consult todo (s.1 researcher, s.2 architect-tools, s.3 code-review). Checkpoint clears only when all consult todos complete.`;
|
|
82
|
+
if (content.length === 0) {
|
|
83
|
+
return [{ type: 'text', text: synthesisSection.trim() }];
|
|
84
|
+
}
|
|
85
|
+
return content.map((block, index) => index === 0 ? { ...block, text: `${block.text}${synthesisSection}` } : block);
|
|
86
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesis — periodic reflect & realign checkpoint SSOT.
|
|
3
|
+
* Slice counter + feature flag + `.xray/state/synthesis-checkpoint.json`.
|
|
4
|
+
* PR2 wires pre-tool gate deny via delegation-gate.ts.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SYNTHESIS_PRIMITIVE = "synthesis";
|
|
7
|
+
export type ExecutionSliceKind = 'gate' | 'turn' | 'todo_completed';
|
|
8
|
+
export interface SynthesisConfig {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
every_n_gates: number;
|
|
11
|
+
every_n_turns: number;
|
|
12
|
+
every_n_todos_completed: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SynthesisSliceCounters {
|
|
15
|
+
gates: number;
|
|
16
|
+
turns: number;
|
|
17
|
+
todosCompleted: number;
|
|
18
|
+
}
|
|
19
|
+
export interface SynthesisCheckpointState {
|
|
20
|
+
version: 1;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
slicesSinceLastSynthesis: SynthesisSliceCounters;
|
|
23
|
+
lifetimeSlices: SynthesisSliceCounters;
|
|
24
|
+
synthesisDue: boolean;
|
|
25
|
+
dueReason: string | null;
|
|
26
|
+
lastSynthesisAt: string | null;
|
|
27
|
+
lastSliceAt: string;
|
|
28
|
+
synthesisCount: number;
|
|
29
|
+
}
|
|
30
|
+
export interface RecordSliceResult {
|
|
31
|
+
state: SynthesisCheckpointState;
|
|
32
|
+
becameDue: boolean;
|
|
33
|
+
dueReason: string | null;
|
|
34
|
+
}
|
|
35
|
+
export interface SynthesisDueEvaluation {
|
|
36
|
+
due: boolean;
|
|
37
|
+
reason: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare function synthesisCheckpointPath(projectRoot?: string): string;
|
|
40
|
+
export declare function defaultSynthesisConfig(): SynthesisConfig;
|
|
41
|
+
export declare function loadSynthesisConfig(projectRoot?: string): SynthesisConfig;
|
|
42
|
+
export declare function createInitialSynthesisState(sessionId: string): SynthesisCheckpointState;
|
|
43
|
+
export declare function loadSynthesisCheckpointState(projectRoot?: string): SynthesisCheckpointState | null;
|
|
44
|
+
export declare function saveSynthesisCheckpointState(state: SynthesisCheckpointState, projectRoot?: string): string;
|
|
45
|
+
export declare function evaluateSynthesisDue(counters: SynthesisSliceCounters, config: SynthesisConfig): SynthesisDueEvaluation;
|
|
46
|
+
export declare function recordExecutionSlice(kind: ExecutionSliceKind, ctx: {
|
|
47
|
+
projectRoot?: string;
|
|
48
|
+
sessionId: string | null;
|
|
49
|
+
}): RecordSliceResult | null;
|
|
50
|
+
export declare function completeSynthesisCheckpoint(projectRoot?: string, sessionId?: string | null): SynthesisCheckpointState | null;
|
|
51
|
+
export declare function isSynthesisCheckpointDue(projectRoot?: string, sessionId?: string | null): boolean;
|
|
52
|
+
export declare function getSynthesisDueReason(projectRoot?: string, sessionId?: string | null): string | null;
|
|
53
|
+
/** Session bound to an active synthesis checkpoint (for MCP when host omits sessionId). */
|
|
54
|
+
export declare function getSynthesisCheckpointSessionId(projectRoot?: string): string | null;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesis — periodic reflect & realign checkpoint SSOT.
|
|
3
|
+
* Slice counter + feature flag + `.xray/state/synthesis-checkpoint.json`.
|
|
4
|
+
* PR2 wires pre-tool gate deny via delegation-gate.ts.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
export const SYNTHESIS_PRIMITIVE = 'synthesis';
|
|
9
|
+
const STATE_VERSION = 1;
|
|
10
|
+
export function synthesisCheckpointPath(projectRoot = process.cwd()) {
|
|
11
|
+
return path.join(projectRoot, '.xray', 'state', 'synthesis-checkpoint.json');
|
|
12
|
+
}
|
|
13
|
+
export function defaultSynthesisConfig() {
|
|
14
|
+
return {
|
|
15
|
+
enabled: false,
|
|
16
|
+
every_n_gates: 12,
|
|
17
|
+
every_n_turns: 0,
|
|
18
|
+
every_n_todos_completed: 0,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function loadSynthesisConfig(projectRoot = process.cwd()) {
|
|
22
|
+
const featuresPath = path.join(projectRoot, '.xray', 'features.json');
|
|
23
|
+
if (!fs.existsSync(featuresPath)) {
|
|
24
|
+
return defaultSynthesisConfig();
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const data = JSON.parse(fs.readFileSync(featuresPath, 'utf8'));
|
|
28
|
+
const raw = data.synthesis ?? {};
|
|
29
|
+
const defaults = defaultSynthesisConfig();
|
|
30
|
+
return {
|
|
31
|
+
enabled: raw.enabled === true,
|
|
32
|
+
every_n_gates: normalizeThreshold(raw.every_n_gates, defaults.every_n_gates),
|
|
33
|
+
every_n_turns: normalizeThreshold(raw.every_n_turns, defaults.every_n_turns),
|
|
34
|
+
every_n_todos_completed: normalizeThreshold(raw.every_n_todos_completed, defaults.every_n_todos_completed),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return defaultSynthesisConfig();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function normalizeThreshold(value, fallback) {
|
|
42
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
43
|
+
return fallback;
|
|
44
|
+
}
|
|
45
|
+
return Math.floor(value);
|
|
46
|
+
}
|
|
47
|
+
function emptyCounters() {
|
|
48
|
+
return { gates: 0, turns: 0, todosCompleted: 0 };
|
|
49
|
+
}
|
|
50
|
+
export function createInitialSynthesisState(sessionId) {
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
return {
|
|
53
|
+
version: STATE_VERSION,
|
|
54
|
+
sessionId,
|
|
55
|
+
slicesSinceLastSynthesis: emptyCounters(),
|
|
56
|
+
lifetimeSlices: emptyCounters(),
|
|
57
|
+
synthesisDue: false,
|
|
58
|
+
dueReason: null,
|
|
59
|
+
lastSynthesisAt: null,
|
|
60
|
+
lastSliceAt: now,
|
|
61
|
+
synthesisCount: 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function loadSynthesisCheckpointState(projectRoot = process.cwd()) {
|
|
65
|
+
const statePath = synthesisCheckpointPath(projectRoot);
|
|
66
|
+
if (!fs.existsSync(statePath))
|
|
67
|
+
return null;
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
70
|
+
if (parsed.version !== STATE_VERSION)
|
|
71
|
+
return null;
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function saveSynthesisCheckpointState(state, projectRoot = process.cwd()) {
|
|
79
|
+
const statePath = synthesisCheckpointPath(projectRoot);
|
|
80
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
81
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
82
|
+
return statePath;
|
|
83
|
+
}
|
|
84
|
+
export function evaluateSynthesisDue(counters, config) {
|
|
85
|
+
if (!config.enabled) {
|
|
86
|
+
return { due: false, reason: null };
|
|
87
|
+
}
|
|
88
|
+
if (config.every_n_gates > 0 && counters.gates >= config.every_n_gates) {
|
|
89
|
+
return {
|
|
90
|
+
due: true,
|
|
91
|
+
reason: `gate threshold (${counters.gates}/${config.every_n_gates})`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (config.every_n_turns > 0 && counters.turns >= config.every_n_turns) {
|
|
95
|
+
return {
|
|
96
|
+
due: true,
|
|
97
|
+
reason: `turn threshold (${counters.turns}/${config.every_n_turns})`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (config.every_n_todos_completed > 0 &&
|
|
101
|
+
counters.todosCompleted >= config.every_n_todos_completed) {
|
|
102
|
+
return {
|
|
103
|
+
due: true,
|
|
104
|
+
reason: `todo threshold (${counters.todosCompleted}/${config.every_n_todos_completed})`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { due: false, reason: null };
|
|
108
|
+
}
|
|
109
|
+
function incrementCounter(counters, kind) {
|
|
110
|
+
const next = { ...counters };
|
|
111
|
+
if (kind === 'gate')
|
|
112
|
+
next.gates += 1;
|
|
113
|
+
else if (kind === 'turn')
|
|
114
|
+
next.turns += 1;
|
|
115
|
+
else
|
|
116
|
+
next.todosCompleted += 1;
|
|
117
|
+
return next;
|
|
118
|
+
}
|
|
119
|
+
export function recordExecutionSlice(kind, ctx) {
|
|
120
|
+
if (!ctx.sessionId)
|
|
121
|
+
return null;
|
|
122
|
+
const projectRoot = ctx.projectRoot ?? process.cwd();
|
|
123
|
+
const config = loadSynthesisConfig(projectRoot);
|
|
124
|
+
if (!config.enabled)
|
|
125
|
+
return null;
|
|
126
|
+
const existing = loadSynthesisCheckpointState(projectRoot);
|
|
127
|
+
const base = existing && existing.sessionId === ctx.sessionId
|
|
128
|
+
? existing
|
|
129
|
+
: createInitialSynthesisState(ctx.sessionId);
|
|
130
|
+
if (base.synthesisDue && kind !== 'todo_completed')
|
|
131
|
+
return null;
|
|
132
|
+
const now = new Date().toISOString();
|
|
133
|
+
const slicesSinceLastSynthesis = incrementCounter(base.slicesSinceLastSynthesis, kind);
|
|
134
|
+
const lifetimeSlices = incrementCounter(base.lifetimeSlices, kind);
|
|
135
|
+
let synthesisDue = base.synthesisDue;
|
|
136
|
+
let dueReason = base.dueReason;
|
|
137
|
+
let becameDue = false;
|
|
138
|
+
if (!synthesisDue) {
|
|
139
|
+
const evaluation = evaluateSynthesisDue(slicesSinceLastSynthesis, config);
|
|
140
|
+
if (evaluation.due) {
|
|
141
|
+
synthesisDue = true;
|
|
142
|
+
dueReason = evaluation.reason;
|
|
143
|
+
becameDue = true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const state = {
|
|
147
|
+
...base,
|
|
148
|
+
slicesSinceLastSynthesis,
|
|
149
|
+
lifetimeSlices,
|
|
150
|
+
synthesisDue,
|
|
151
|
+
dueReason,
|
|
152
|
+
lastSliceAt: now,
|
|
153
|
+
};
|
|
154
|
+
saveSynthesisCheckpointState(state, projectRoot);
|
|
155
|
+
return { state, becameDue, dueReason };
|
|
156
|
+
}
|
|
157
|
+
export function completeSynthesisCheckpoint(projectRoot = process.cwd(), sessionId) {
|
|
158
|
+
const state = loadSynthesisCheckpointState(projectRoot);
|
|
159
|
+
if (!state)
|
|
160
|
+
return null;
|
|
161
|
+
if (sessionId && state.sessionId !== sessionId)
|
|
162
|
+
return null;
|
|
163
|
+
const completed = {
|
|
164
|
+
...state,
|
|
165
|
+
slicesSinceLastSynthesis: emptyCounters(),
|
|
166
|
+
synthesisDue: false,
|
|
167
|
+
dueReason: null,
|
|
168
|
+
lastSynthesisAt: new Date().toISOString(),
|
|
169
|
+
synthesisCount: state.synthesisCount + 1,
|
|
170
|
+
};
|
|
171
|
+
saveSynthesisCheckpointState(completed, projectRoot);
|
|
172
|
+
return completed;
|
|
173
|
+
}
|
|
174
|
+
export function isSynthesisCheckpointDue(projectRoot = process.cwd(), sessionId) {
|
|
175
|
+
const state = loadSynthesisCheckpointState(projectRoot);
|
|
176
|
+
if (!state?.synthesisDue)
|
|
177
|
+
return false;
|
|
178
|
+
if (sessionId && state.sessionId !== sessionId)
|
|
179
|
+
return false;
|
|
180
|
+
return loadSynthesisConfig(projectRoot).enabled;
|
|
181
|
+
}
|
|
182
|
+
export function getSynthesisDueReason(projectRoot = process.cwd(), sessionId) {
|
|
183
|
+
const state = loadSynthesisCheckpointState(projectRoot);
|
|
184
|
+
if (!state?.synthesisDue)
|
|
185
|
+
return null;
|
|
186
|
+
if (sessionId && state.sessionId !== sessionId)
|
|
187
|
+
return null;
|
|
188
|
+
return state.dueReason;
|
|
189
|
+
}
|
|
190
|
+
/** Session bound to an active synthesis checkpoint (for MCP when host omits sessionId). */
|
|
191
|
+
export function getSynthesisCheckpointSessionId(projectRoot = process.cwd()) {
|
|
192
|
+
const state = loadSynthesisCheckpointState(projectRoot);
|
|
193
|
+
if (!state?.synthesisDue)
|
|
194
|
+
return null;
|
|
195
|
+
return state.sessionId;
|
|
196
|
+
}
|
|
@@ -281,6 +281,23 @@ export default async function xrayCodexPlugin(input) {
|
|
|
281
281
|
if (!textContent) {
|
|
282
282
|
return;
|
|
283
283
|
}
|
|
284
|
+
try {
|
|
285
|
+
const { recordExecutionSlice } = await import("../nucleus/synthesis.js");
|
|
286
|
+
let sessionId = process.env.GROK_SESSION_ID ?? null;
|
|
287
|
+
try {
|
|
288
|
+
const boot = JSON.parse(readFileSync(join(directory, ".xray", "state", "session-boot.json"), "utf8"));
|
|
289
|
+
sessionId = boot.sessionId ?? sessionId;
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
/* no session boot */
|
|
293
|
+
}
|
|
294
|
+
if (sessionId) {
|
|
295
|
+
recordExecutionSlice("turn", { projectRoot: directory, sessionId });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
/* synthesis turn slice optional */
|
|
300
|
+
}
|
|
284
301
|
const agentMentionRegex = /@([\w-]+)(?:\s+(.+?))?(?=$|\n\n|\r\r)/g;
|
|
285
302
|
let match;
|
|
286
303
|
let hasAgentMention = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0xray",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.2",
|
|
4
4
|
"description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
|
|
5
5
|
"readme": "README.md",
|
|
6
6
|
"license": "MIT",
|
|
@@ -127,9 +127,13 @@
|
|
|
127
127
|
"publish": "npm run release:gate && npm run safe-publish && npm publish --access public",
|
|
128
128
|
"release:patch": "node scripts/node/release.mjs patch",
|
|
129
129
|
"verify:orchestrator-behavior": "npm run build && node scripts/mjs/verify-orchestrator-behavior.mjs",
|
|
130
|
-
"verify:grok-delegation-gate": "npm run build && node scripts/mjs/verify-grok-delegation-gate.mjs",
|
|
130
|
+
"verify:grok-delegation-gate": "npm run build && node scripts/mjs/verify-grok-delegation-gate.mjs && node scripts/mjs/verify-grok-synthesis-gate.mjs",
|
|
131
|
+
"verify:grok-synthesis-gate": "npm run build && node scripts/mjs/verify-grok-synthesis-gate.mjs",
|
|
131
132
|
"verify:hermes-delegation-gate": "npm run build && node scripts/mjs/verify-hermes-delegation-gate.mjs",
|
|
132
|
-
"verify:delegation-gate": "npm run build && node scripts/mjs/verify-delegation-gate-core.mjs --host=grok && node scripts/mjs/verify-delegation-gate-core.mjs --host=hermes && node scripts/mjs/verify-delegation-gate-core.mjs --host=opencode && node scripts/mjs/verify-grok-delegation-gate.mjs && node scripts/mjs/verify-hermes-delegation-gate.mjs"
|
|
133
|
+
"verify:delegation-gate": "npm run build && node scripts/mjs/verify-delegation-gate-core.mjs --host=grok && node scripts/mjs/verify-delegation-gate-core.mjs --host=hermes && node scripts/mjs/verify-delegation-gate-core.mjs --host=opencode && node scripts/mjs/verify-grok-delegation-gate.mjs && node scripts/mjs/verify-grok-synthesis-gate.mjs && node scripts/mjs/verify-hermes-delegation-gate.mjs",
|
|
134
|
+
"verify:synthesis": "npm run build && node scripts/mjs/verify-synthesis-core.mjs && node scripts/mjs/verify-synthesis-gate-core.mjs && node scripts/mjs/verify-synthesis-outcome-core.mjs && node scripts/mjs/verify-synthesis-p2-core.mjs && node scripts/mjs/verify-synthesis-e2e-core.mjs",
|
|
135
|
+
"verify:find-project-root": "node scripts/mjs/verify-find-project-root-core.mjs",
|
|
136
|
+
"verify:consumer-gitignore": "node scripts/mjs/verify-consumer-gitignore-core.mjs"
|
|
133
137
|
},
|
|
134
138
|
"files": [
|
|
135
139
|
"dist/",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer project root SSOT for bridge hooks and installers.
|
|
3
|
+
* Prefers git root when 0xray is a dependency or devDependency.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
6
|
+
import { join, dirname, resolve } from 'node:path';
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
export function hasXrayDependency(pkg) {
|
|
11
|
+
if (!pkg || typeof pkg !== 'object') return false;
|
|
12
|
+
return (
|
|
13
|
+
pkg.name === '0xray' ||
|
|
14
|
+
Boolean(pkg.dependencies?.['0xray']) ||
|
|
15
|
+
Boolean(pkg.devDependencies?.['0xray'])
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isConsumerWorkspace(dir) {
|
|
20
|
+
return (
|
|
21
|
+
existsSync(join(dir, '.xray', 'features.json')) ||
|
|
22
|
+
existsSync(join(dir, 'node_modules', '0xray', 'package.json'))
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizePath(dir) {
|
|
27
|
+
try {
|
|
28
|
+
return realpathSync(dir);
|
|
29
|
+
} catch {
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readPkg(dir) {
|
|
35
|
+
try {
|
|
36
|
+
const pkgPath = join(dir, 'package.json');
|
|
37
|
+
if (!existsSync(pkgPath)) return null;
|
|
38
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function findGitRoot(cwd = process.cwd()) {
|
|
45
|
+
try {
|
|
46
|
+
const gitRoot = execSync('git rev-parse --show-toplevel 2>/dev/null', {
|
|
47
|
+
cwd,
|
|
48
|
+
encoding: 'utf8',
|
|
49
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
50
|
+
}).trim();
|
|
51
|
+
return gitRoot ? normalizePath(gitRoot) : null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function findProjectRoot(extraCandidates = []) {
|
|
58
|
+
const envHome = process.env.XRAY_HOME || process.env.XRAY_ROOT;
|
|
59
|
+
if (envHome) {
|
|
60
|
+
const resolved = normalizePath(resolve(envHome));
|
|
61
|
+
const pkg = readPkg(resolved);
|
|
62
|
+
if (pkg && (isConsumerWorkspace(resolved) || hasXrayDependency(pkg))) {
|
|
63
|
+
return resolved;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const gitRoot = findGitRoot();
|
|
68
|
+
if (gitRoot) {
|
|
69
|
+
const pkg = readPkg(gitRoot);
|
|
70
|
+
if (pkg && hasXrayDependency(pkg) && pkg.name !== '0xray') {
|
|
71
|
+
return gitRoot;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let dir = process.cwd();
|
|
76
|
+
for (let i = 0; i < 12; i++) {
|
|
77
|
+
if (isConsumerWorkspace(dir)) return dir;
|
|
78
|
+
const pkg = readPkg(dir);
|
|
79
|
+
if (pkg && hasXrayDependency(pkg) && pkg.name !== '0xray') return dir;
|
|
80
|
+
const parent = dirname(dir);
|
|
81
|
+
if (parent === dir) break;
|
|
82
|
+
dir = parent;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const candidates = [process.cwd(), join(homedir(), 'dev', 'xray'), ...extraCandidates];
|
|
86
|
+
for (const candidate of candidates) {
|
|
87
|
+
const pkg = readPkg(candidate);
|
|
88
|
+
if (pkg && pkg.name === '0xray') return candidate;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return normalizePath(gitRoot || process.cwd());
|
|
92
|
+
}
|