0xray 3.5.3 → 3.5.5
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/.xray/features.json +5 -1
- package/.xray/features.schema.json +56 -0
- package/AGENTS-consumer.md +1 -1
- package/AGENTS.md +11 -1
- package/README.md +1 -1
- package/SKILLS.md +1 -1
- package/dist/AGENTS.md +11 -1
- package/dist/CHANGELOG.md +54 -0
- package/dist/README.md +1 -1
- package/dist/SKILLS.md +1 -1
- package/dist/cli/commands/hermes-install.js +3 -1
- package/dist/cli/index.js +0 -0
- package/dist/core/features-config.d.ts +15 -0
- package/dist/governance/governance-service.d.ts +1 -0
- package/dist/governance/governance-service.js +81 -6
- package/dist/integrations/enforcement-gate.js +15 -5
- package/dist/integrations/grok/grok-cli.js +1 -10
- package/dist/integrations/grok/hooks/grok-hook-utils.js +46 -2
- package/dist/integrations/grok/hooks/post-tool-use.js +48 -14
- package/dist/integrations/grok/hooks/pre-tool-use.js +20 -0
- package/dist/integrations/grok/hooks/session-start.js +28 -0
- package/dist/integrations/hermes-agent/__init__.py +11 -1
- package/dist/integrations/hermes-agent/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/integrations/hermes-agent/__pycache__/schemas.cpython-313.pyc +0 -0
- package/dist/integrations/hermes-agent/__pycache__/tools.cpython-313.pyc +0 -0
- package/dist/integrations/hermes-agent/bridge.mjs +73 -10
- package/dist/integrations/hooks/confer-hook-runtime.mjs +49 -0
- package/dist/integrations/hooks/delegation-gate-runtime.mjs +6 -0
- package/dist/integrations/hooks/pipeline-hook-runtime.mjs +211 -0
- package/dist/integrations/hooks/plan-hook-runtime.mjs +68 -0
- package/dist/integrations/hooks/user-aside-hook-runtime.mjs +49 -0
- package/dist/mcps/architect-tools.server.js +6 -1
- package/dist/mcps/orchestrator/handlers/complexity-handler.d.ts +7 -0
- package/dist/mcps/orchestrator/handlers/complexity-handler.js +106 -7
- package/dist/mcps/orchestrator/handlers/task-handler.d.ts +6 -0
- package/dist/mcps/orchestrator/handlers/task-handler.js +75 -0
- package/dist/mcps/orchestrator/server.js +54 -5
- package/dist/memory-routing/provider-loader.js +30 -2
- package/dist/nucleus/aside-worktree.d.ts +41 -0
- package/dist/nucleus/aside-worktree.js +164 -0
- package/dist/nucleus/confer.d.ts +57 -0
- package/dist/nucleus/confer.js +308 -0
- package/dist/nucleus/delegation-gate.d.ts +12 -1
- package/dist/nucleus/delegation-gate.js +113 -13
- package/dist/nucleus/lead-dev-plan-persistence.d.ts +27 -3
- package/dist/nucleus/lead-dev-plan-persistence.js +222 -18
- package/dist/nucleus/spawn-plan-resolution.d.ts +20 -0
- package/dist/nucleus/spawn-plan-resolution.js +42 -0
- package/dist/nucleus/synthesis-consult-receipt.d.ts +32 -0
- package/dist/nucleus/synthesis-consult-receipt.js +171 -0
- package/dist/nucleus/synthesis-context.js +1 -1
- package/dist/nucleus/user-aside.d.ts +70 -0
- package/dist/nucleus/user-aside.js +393 -0
- package/dist/plugin/xray-codex-injection.js +1 -1
- package/dist/postprocessor/PostProcessor.d.ts +1 -0
- package/dist/postprocessor/PostProcessor.js +11 -0
- package/dist/processors/implementations/session-summary-processor.d.ts +1 -1
- package/dist/processors/implementations/session-summary-processor.js +9 -6
- package/package.json +14 -5
- package/scripts/hooks/install-hooks.cjs +3 -3
- package/scripts/hooks/run-hook.js +171 -86
- package/scripts/mjs/verify-confer-core.mjs +103 -0
- package/scripts/mjs/verify-delegation-gate-core.mjs +48 -2
- package/scripts/mjs/verify-hermes-session-start.mjs +137 -0
- package/scripts/mjs/verify-pre-push-diff-core.mjs +87 -0
- package/scripts/mjs/verify-user-aside-core.mjs +137 -0
- package/scripts/node/bridge-mcp-wiring.cjs +10 -0
- package/scripts/node/install-bridges.cjs +3 -11
- package/scripts/node/load-reflection-config.mjs +2 -2
- package/scripts/node/release-gate.mjs +8 -4
- package/src/mcps/architect-tools.server.ts +6 -1
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +178 -20
- package/src/mcps/orchestrator/handlers/task-handler.ts +99 -0
- package/src/mcps/orchestrator/server.ts +56 -5
- package/xray/features.json +5 -1
- package/xray/features.schema.json +56 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User aside — parallel work tracks (worktree-like) declared by the user.
|
|
3
|
+
* SSOT: `.xray/state/asides/{id}.json` + `_active.json`.
|
|
4
|
+
* Todo ids: `{asideId}.a.{phase}.{todo}` — globally unique per aside.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { buildLeadDevPlan, isLeadDevModeActive, } from './autonomy-kernel.js';
|
|
9
|
+
import { allPlanTodos, getNextRequiredTodo, getOutstandingTodos, } from './lead-dev-plan-persistence.js';
|
|
10
|
+
export const USER_ASIDE_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
11
|
+
/** Namespaced aside todo: `suit-nft.a.1.1` */
|
|
12
|
+
export const USER_ASIDE_TODO_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}\.a\.\d+\.\d+$/;
|
|
13
|
+
export const USER_ASIDE_STATE_VERSION = 1;
|
|
14
|
+
export class UserAsideValidationError extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'UserAsideValidationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function assertSafeAsideId(asideId) {
|
|
21
|
+
const trimmed = asideId.trim();
|
|
22
|
+
if (!trimmed || !USER_ASIDE_ID_PATTERN.test(trimmed)) {
|
|
23
|
+
throw new UserAsideValidationError(`Invalid aside id "${asideId}" — use [a-zA-Z0-9][a-zA-Z0-9._-]{0,63}`);
|
|
24
|
+
}
|
|
25
|
+
if (trimmed.includes('..') || trimmed.includes('/') || trimmed.includes('\\')) {
|
|
26
|
+
throw new UserAsideValidationError(`Invalid aside id "${asideId}" — path characters forbidden`);
|
|
27
|
+
}
|
|
28
|
+
return trimmed;
|
|
29
|
+
}
|
|
30
|
+
export function asideTodoId(asideId, phaseIndex, todoIndex) {
|
|
31
|
+
return `${assertSafeAsideId(asideId)}.a.${phaseIndex + 1}.${todoIndex + 1}`;
|
|
32
|
+
}
|
|
33
|
+
export function isUserAsideTodoId(todoId) {
|
|
34
|
+
return USER_ASIDE_TODO_PATTERN.test(todoId);
|
|
35
|
+
}
|
|
36
|
+
export function parseAsideIdFromTodoId(todoId) {
|
|
37
|
+
if (!isUserAsideTodoId(todoId))
|
|
38
|
+
return null;
|
|
39
|
+
const dotA = todoId.indexOf('.a.');
|
|
40
|
+
if (dotA <= 0)
|
|
41
|
+
return null;
|
|
42
|
+
const asideId = todoId.slice(0, dotA);
|
|
43
|
+
try {
|
|
44
|
+
return assertSafeAsideId(asideId);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function userAsidesDir(projectRoot = process.cwd()) {
|
|
51
|
+
return path.join(projectRoot, '.xray', 'state', 'asides');
|
|
52
|
+
}
|
|
53
|
+
export function userAsidePath(asideId, projectRoot = process.cwd()) {
|
|
54
|
+
const safe = assertSafeAsideId(asideId);
|
|
55
|
+
const resolved = path.resolve(userAsidesDir(projectRoot), `${safe}.json`);
|
|
56
|
+
const base = path.resolve(userAsidesDir(projectRoot));
|
|
57
|
+
if (!resolved.startsWith(base + path.sep) && resolved !== base) {
|
|
58
|
+
throw new UserAsideValidationError(`Aside path escapes asides directory: ${asideId}`);
|
|
59
|
+
}
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
export function activeAsidePointerPath(projectRoot = process.cwd()) {
|
|
63
|
+
return path.join(userAsidesDir(projectRoot), '_active.json');
|
|
64
|
+
}
|
|
65
|
+
export function isUserAsidesEnabled(projectRoot = process.cwd()) {
|
|
66
|
+
const featuresPath = path.join(projectRoot, '.xray', 'features.json');
|
|
67
|
+
if (!fs.existsSync(featuresPath))
|
|
68
|
+
return isLeadDevModeActive();
|
|
69
|
+
try {
|
|
70
|
+
const data = JSON.parse(fs.readFileSync(featuresPath, 'utf8'));
|
|
71
|
+
const raw = data.multi_agent_orchestration?.user_asides;
|
|
72
|
+
if (raw?.enabled === false)
|
|
73
|
+
return false;
|
|
74
|
+
return isLeadDevModeActive();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return isLeadDevModeActive();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function validateLoadedAside(parsed, expectedId) {
|
|
81
|
+
if (!parsed || typeof parsed !== 'object')
|
|
82
|
+
return null;
|
|
83
|
+
const row = parsed;
|
|
84
|
+
if (row.version !== USER_ASIDE_STATE_VERSION)
|
|
85
|
+
return null;
|
|
86
|
+
if (!row.id || !row.title || !row.description || !row.plan)
|
|
87
|
+
return null;
|
|
88
|
+
if (!Array.isArray(row.plan.phases))
|
|
89
|
+
return null;
|
|
90
|
+
try {
|
|
91
|
+
assertSafeAsideId(row.id);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (expectedId && row.id !== expectedId)
|
|
97
|
+
return null;
|
|
98
|
+
for (const phase of row.plan.phases) {
|
|
99
|
+
if (!phase?.todos || !Array.isArray(phase.todos))
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return row;
|
|
103
|
+
}
|
|
104
|
+
function remapPhasesToAsidePrefix(asideId, phases) {
|
|
105
|
+
const safeId = assertSafeAsideId(asideId);
|
|
106
|
+
return phases.map((phase, phaseIndex) => ({
|
|
107
|
+
...phase,
|
|
108
|
+
id: phase.id.startsWith('phase-') ? `aside-phase-${phaseIndex + 1}` : phase.id,
|
|
109
|
+
todos: phase.todos.map((todo, todoIndex) => ({
|
|
110
|
+
...todo,
|
|
111
|
+
id: asideTodoId(safeId, phaseIndex, todoIndex),
|
|
112
|
+
status: todo.status ?? 'pending',
|
|
113
|
+
})),
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
/** Build a phased aside plan from task intake (mirrors lead-dev plan, namespaced todo ids). */
|
|
117
|
+
export function buildUserAsidePlan(asideId, title, description, taskInputs = [], mcpOverallComplexity, projectRoot = process.cwd()) {
|
|
118
|
+
if (!isUserAsidesEnabled(projectRoot))
|
|
119
|
+
return null;
|
|
120
|
+
const safeId = assertSafeAsideId(asideId);
|
|
121
|
+
const taskTypes = taskInputs.map((t) => t.type ?? 'implement');
|
|
122
|
+
const basePlan = buildLeadDevPlan(description, taskTypes, taskInputs, mcpOverallComplexity);
|
|
123
|
+
if (!basePlan)
|
|
124
|
+
return null;
|
|
125
|
+
return {
|
|
126
|
+
version: USER_ASIDE_STATE_VERSION,
|
|
127
|
+
id: safeId,
|
|
128
|
+
title,
|
|
129
|
+
status: 'active',
|
|
130
|
+
description,
|
|
131
|
+
persistedAt: new Date().toISOString(),
|
|
132
|
+
plan: {
|
|
133
|
+
...basePlan,
|
|
134
|
+
description: `[Aside ${safeId}] ${description}`,
|
|
135
|
+
phases: remapPhasesToAsidePrefix(safeId, basePlan.phases),
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Merge new task intake into an existing aside (re-intake). */
|
|
140
|
+
export function mergeUserAsideIntake(existing, description, taskInputs, mcpOverallComplexity, projectRoot = process.cwd()) {
|
|
141
|
+
const fresh = buildUserAsidePlan(existing.id, existing.title, description || existing.description, taskInputs.length > 0 ? taskInputs : [{ description: existing.description, type: 'implement' }], mcpOverallComplexity, projectRoot);
|
|
142
|
+
if (!fresh)
|
|
143
|
+
return existing;
|
|
144
|
+
const mergedPhases = [...existing.plan.phases];
|
|
145
|
+
for (const phase of fresh.plan.phases) {
|
|
146
|
+
const match = mergedPhases.find((p) => p.id === phase.id);
|
|
147
|
+
if (match) {
|
|
148
|
+
const existingIds = new Set(match.todos.map((t) => t.id));
|
|
149
|
+
for (const todo of phase.todos) {
|
|
150
|
+
if (!existingIds.has(todo.id))
|
|
151
|
+
match.todos.push(todo);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
mergedPhases.push(phase);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
...existing,
|
|
160
|
+
description: description || existing.description,
|
|
161
|
+
status: existing.status === 'completed' ? 'active' : existing.status,
|
|
162
|
+
plan: { ...existing.plan, phases: mergedPhases },
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function loadUserAside(asideId, projectRoot = process.cwd()) {
|
|
166
|
+
try {
|
|
167
|
+
const filePath = userAsidePath(asideId, projectRoot);
|
|
168
|
+
if (!fs.existsSync(filePath))
|
|
169
|
+
return null;
|
|
170
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
171
|
+
return validateLoadedAside(parsed, assertSafeAsideId(asideId));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export function saveUserAside(aside, projectRoot = process.cwd()) {
|
|
178
|
+
assertSafeAsideId(aside.id);
|
|
179
|
+
const dir = userAsidesDir(projectRoot);
|
|
180
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
181
|
+
const filePath = userAsidePath(aside.id, projectRoot);
|
|
182
|
+
const payload = {
|
|
183
|
+
...aside,
|
|
184
|
+
version: USER_ASIDE_STATE_VERSION,
|
|
185
|
+
persistedAt: new Date().toISOString(),
|
|
186
|
+
};
|
|
187
|
+
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2));
|
|
188
|
+
return filePath;
|
|
189
|
+
}
|
|
190
|
+
export function listUserAsideIds(projectRoot = process.cwd()) {
|
|
191
|
+
const dir = userAsidesDir(projectRoot);
|
|
192
|
+
if (!fs.existsSync(dir))
|
|
193
|
+
return [];
|
|
194
|
+
return fs
|
|
195
|
+
.readdirSync(dir)
|
|
196
|
+
.filter((f) => f.endsWith('.json') && f !== '_active.json')
|
|
197
|
+
.map((f) => f.replace(/\.json$/, ''))
|
|
198
|
+
.filter((id) => USER_ASIDE_ID_PATTERN.test(id));
|
|
199
|
+
}
|
|
200
|
+
export function loadActiveAsidePointer(projectRoot = process.cwd()) {
|
|
201
|
+
const pointerPath = activeAsidePointerPath(projectRoot);
|
|
202
|
+
if (!fs.existsSync(pointerPath))
|
|
203
|
+
return null;
|
|
204
|
+
try {
|
|
205
|
+
const parsed = JSON.parse(fs.readFileSync(pointerPath, 'utf8'));
|
|
206
|
+
if (!parsed?.asideId)
|
|
207
|
+
return null;
|
|
208
|
+
assertSafeAsideId(parsed.asideId);
|
|
209
|
+
return parsed;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
export function getActiveAsideId(projectRoot = process.cwd(), sessionId) {
|
|
216
|
+
if (!isUserAsidesEnabled(projectRoot))
|
|
217
|
+
return null;
|
|
218
|
+
const pointer = loadActiveAsidePointer(projectRoot);
|
|
219
|
+
if (!pointer)
|
|
220
|
+
return null;
|
|
221
|
+
if (pointer.sessionId) {
|
|
222
|
+
if (!sessionId || pointer.sessionId !== sessionId)
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const aside = loadUserAside(pointer.asideId, projectRoot);
|
|
226
|
+
if (!aside || aside.status !== 'active')
|
|
227
|
+
return null;
|
|
228
|
+
return pointer.asideId;
|
|
229
|
+
}
|
|
230
|
+
export function setActiveAsideId(asideId, projectRoot = process.cwd(), sessionId) {
|
|
231
|
+
const safeId = assertSafeAsideId(asideId);
|
|
232
|
+
const aside = loadUserAside(safeId, projectRoot);
|
|
233
|
+
if (!aside) {
|
|
234
|
+
throw new UserAsideValidationError(`Cannot activate aside "${safeId}" — file not found at .xray/state/asides/${safeId}.json`);
|
|
235
|
+
}
|
|
236
|
+
if (aside.status !== 'active' && aside.status !== 'paused') {
|
|
237
|
+
throw new UserAsideValidationError(`Cannot activate aside "${safeId}" — status is ${aside.status}`);
|
|
238
|
+
}
|
|
239
|
+
if (aside.status === 'paused') {
|
|
240
|
+
aside.status = 'active';
|
|
241
|
+
saveUserAside(aside, projectRoot);
|
|
242
|
+
}
|
|
243
|
+
const dir = userAsidesDir(projectRoot);
|
|
244
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
245
|
+
const pointer = {
|
|
246
|
+
asideId: safeId,
|
|
247
|
+
activatedAt: new Date().toISOString(),
|
|
248
|
+
...(sessionId ? { sessionId } : {}),
|
|
249
|
+
};
|
|
250
|
+
fs.writeFileSync(activeAsidePointerPath(projectRoot), JSON.stringify(pointer, null, 2));
|
|
251
|
+
return pointer;
|
|
252
|
+
}
|
|
253
|
+
export function clearActiveAside(projectRoot = process.cwd()) {
|
|
254
|
+
const pointerPath = activeAsidePointerPath(projectRoot);
|
|
255
|
+
if (!fs.existsSync(pointerPath))
|
|
256
|
+
return false;
|
|
257
|
+
fs.unlinkSync(pointerPath);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
export function loadActiveUserAside(projectRoot = process.cwd(), sessionId) {
|
|
261
|
+
const id = getActiveAsideId(projectRoot, sessionId);
|
|
262
|
+
if (!id) {
|
|
263
|
+
const pointer = loadActiveAsidePointer(projectRoot);
|
|
264
|
+
if (pointer) {
|
|
265
|
+
const aside = loadUserAside(pointer.asideId, projectRoot);
|
|
266
|
+
if (aside && aside.status !== 'active')
|
|
267
|
+
clearActiveAside(projectRoot);
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
const aside = loadUserAside(id, projectRoot);
|
|
272
|
+
if (!aside || aside.status !== 'active') {
|
|
273
|
+
clearActiveAside(projectRoot);
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return aside;
|
|
277
|
+
}
|
|
278
|
+
/** PersistedLeadDevPlan view of an aside for spawn-gate reuse. */
|
|
279
|
+
export function asideToSpawnPlan(aside) {
|
|
280
|
+
const plan = { ...aside.plan };
|
|
281
|
+
if (aside.persistedAt)
|
|
282
|
+
plan.persistedAt = aside.persistedAt;
|
|
283
|
+
if (aside.sessionId)
|
|
284
|
+
plan.sessionId = aside.sessionId;
|
|
285
|
+
return plan;
|
|
286
|
+
}
|
|
287
|
+
/** Aside routable when active for session, or pointer is unscoped (accepted exception). */
|
|
288
|
+
export function isAsideActiveForSession(asideId, projectRoot = process.cwd(), sessionId) {
|
|
289
|
+
const activeId = getActiveAsideId(projectRoot, sessionId);
|
|
290
|
+
if (activeId === asideId)
|
|
291
|
+
return true;
|
|
292
|
+
const pointer = loadActiveAsidePointer(projectRoot);
|
|
293
|
+
if (!pointer || pointer.asideId !== asideId)
|
|
294
|
+
return false;
|
|
295
|
+
if (!pointer.sessionId)
|
|
296
|
+
return true;
|
|
297
|
+
return Boolean(sessionId && pointer.sessionId === sessionId);
|
|
298
|
+
}
|
|
299
|
+
export function findAsideContainingTodo(todoId, projectRoot = process.cwd(), preferredAsideId) {
|
|
300
|
+
if (!isUserAsideTodoId(todoId))
|
|
301
|
+
return null;
|
|
302
|
+
if (preferredAsideId) {
|
|
303
|
+
const direct = loadUserAside(preferredAsideId, projectRoot);
|
|
304
|
+
if (direct) {
|
|
305
|
+
const hit = allPlanTodos(asideToSpawnPlan(direct)).some((t) => t.id === todoId);
|
|
306
|
+
if (hit)
|
|
307
|
+
return direct;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const parsedId = parseAsideIdFromTodoId(todoId);
|
|
311
|
+
if (parsedId) {
|
|
312
|
+
const aside = loadUserAside(parsedId, projectRoot);
|
|
313
|
+
if (aside) {
|
|
314
|
+
const hit = allPlanTodos(asideToSpawnPlan(aside)).some((t) => t.id === todoId);
|
|
315
|
+
if (hit)
|
|
316
|
+
return aside;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
export function updateUserAsideTodoStatus(todoId, status, projectRoot = process.cwd(), sessionId) {
|
|
322
|
+
const parsedId = parseAsideIdFromTodoId(todoId);
|
|
323
|
+
let aside = parsedId
|
|
324
|
+
? findAsideContainingTodo(todoId, projectRoot, parsedId)
|
|
325
|
+
: null;
|
|
326
|
+
if (aside && parsedId && !isAsideActiveForSession(parsedId, projectRoot, sessionId)) {
|
|
327
|
+
aside = null;
|
|
328
|
+
}
|
|
329
|
+
if (!aside)
|
|
330
|
+
aside = loadActiveUserAside(projectRoot, sessionId);
|
|
331
|
+
if (!aside)
|
|
332
|
+
return false;
|
|
333
|
+
let updated = false;
|
|
334
|
+
for (const phase of aside.plan.phases) {
|
|
335
|
+
for (const todo of phase.todos) {
|
|
336
|
+
if (todo.id === todoId) {
|
|
337
|
+
todo.status = status;
|
|
338
|
+
updated = true;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (!updated)
|
|
343
|
+
return false;
|
|
344
|
+
const outstanding = getOutstandingTodos(asideToSpawnPlan(aside));
|
|
345
|
+
if (outstanding.length === 0) {
|
|
346
|
+
aside.status = 'completed';
|
|
347
|
+
clearActiveAside(projectRoot);
|
|
348
|
+
}
|
|
349
|
+
saveUserAside(aside, projectRoot);
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
export function getNextAsideTodo(aside) {
|
|
353
|
+
return getNextRequiredTodo(asideToSpawnPlan(aside));
|
|
354
|
+
}
|
|
355
|
+
export function formatUserAsideSummary(aside) {
|
|
356
|
+
const todos = allPlanTodos(asideToSpawnPlan(aside));
|
|
357
|
+
const done = todos.filter((t) => t.status === 'completed').length;
|
|
358
|
+
const lines = [
|
|
359
|
+
`**Aside \`${aside.id}\`** — ${aside.title}`,
|
|
360
|
+
`Status: ${aside.status} | Todos: ${done}/${todos.length}`,
|
|
361
|
+
aside.worktree ? `Worktree: \`${aside.worktree}\`` : null,
|
|
362
|
+
aside.branch ? `Branch: \`${aside.branch}\`` : null,
|
|
363
|
+
'',
|
|
364
|
+
...todos.map((t) => `- [${t.status === 'completed' ? 'x' : ' '}] ${t.id} (${t.subagent}): ${t.task}`),
|
|
365
|
+
].filter(Boolean);
|
|
366
|
+
return lines.join('\n');
|
|
367
|
+
}
|
|
368
|
+
export function getUserAsideBootHints(projectRoot = process.cwd(), sessionId) {
|
|
369
|
+
if (!isUserAsidesEnabled(projectRoot))
|
|
370
|
+
return null;
|
|
371
|
+
const aside = loadActiveUserAside(projectRoot, sessionId);
|
|
372
|
+
if (!aside)
|
|
373
|
+
return null;
|
|
374
|
+
const hints = {
|
|
375
|
+
activeAside: aside.id,
|
|
376
|
+
asideTitle: aside.title,
|
|
377
|
+
asideStatus: aside.status,
|
|
378
|
+
asideTodoPrefix: `${aside.id}.a.*`,
|
|
379
|
+
asideHint: 'Spawns route to active aside todos; orchestrate-task clearActiveAside to resume main',
|
|
380
|
+
};
|
|
381
|
+
if (aside.worktree)
|
|
382
|
+
hints.asideWorktree = aside.worktree;
|
|
383
|
+
if (aside.branch)
|
|
384
|
+
hints.asideBranch = aside.branch;
|
|
385
|
+
if (aside.governance?.trapSignals?.length) {
|
|
386
|
+
hints.asideTrapSignals = aside.governance.trapSignals;
|
|
387
|
+
}
|
|
388
|
+
if (aside.governance?.conferOnPhaseStart) {
|
|
389
|
+
hints.asideConferRecommended = true;
|
|
390
|
+
hints.asideConferTrigger = 'orchestrate-task { confer: true } or synthesis analyze-complexity';
|
|
391
|
+
}
|
|
392
|
+
return hints;
|
|
393
|
+
}
|
|
@@ -182,7 +182,7 @@ export default async function xrayCodexPlugin(input) {
|
|
|
182
182
|
const { tool, args, result } = input;
|
|
183
183
|
if (isSubagentTool(tool)) {
|
|
184
184
|
try {
|
|
185
|
-
const spawnResult = evaluatePostToolSpawn(tool, (args ?? {}), directory);
|
|
185
|
+
const spawnResult = evaluatePostToolSpawn(tool, (args ?? {}), directory, { toolOutput: result });
|
|
186
186
|
if (spawnResult.satisfied.length > 0) {
|
|
187
187
|
logger.log(`🔗 Delegation gate post: satisfied ${spawnResult.satisfied.map((d) => d.id).join(", ")}`);
|
|
188
188
|
}
|
|
@@ -23,6 +23,7 @@ export declare class PostProcessor {
|
|
|
23
23
|
private escalationEngine;
|
|
24
24
|
private successHandler;
|
|
25
25
|
private triggers;
|
|
26
|
+
private metamorphosisEngines;
|
|
26
27
|
constructor(stateManager: XrayStateManager, sessionMonitor?: SessionMonitor | null, config?: Partial<PostProcessorConfig>);
|
|
27
28
|
/**
|
|
28
29
|
* Generate automated framework report if conditions are met
|
|
@@ -25,6 +25,7 @@ import { defaultConfig } from "./config.js";
|
|
|
25
25
|
import { frameworkReportingSystem } from "../reporting/framework-reporting-system.js";
|
|
26
26
|
import { ReportContentValidator } from "../validation/report-content-validator.js";
|
|
27
27
|
import { RegressionAnalysisService } from "./services/RegressionAnalysisService.js";
|
|
28
|
+
import { SelfProposalEngine } from "./metamorphosis/SelfProposalEngine.js";
|
|
28
29
|
export class PostProcessor {
|
|
29
30
|
stateManager;
|
|
30
31
|
sessionMonitor;
|
|
@@ -39,6 +40,7 @@ export class PostProcessor {
|
|
|
39
40
|
escalationEngine;
|
|
40
41
|
successHandler;
|
|
41
42
|
triggers;
|
|
43
|
+
metamorphosisEngines;
|
|
42
44
|
constructor(stateManager, sessionMonitor = null, config = {}) {
|
|
43
45
|
this.stateManager = stateManager;
|
|
44
46
|
this.sessionMonitor = sessionMonitor;
|
|
@@ -56,6 +58,7 @@ export class PostProcessor {
|
|
|
56
58
|
// Initialize escalation and success handlers
|
|
57
59
|
this.escalationEngine = new EscalationEngine(this.config.escalation);
|
|
58
60
|
this.successHandler = new SuccessHandler(this.config.success);
|
|
61
|
+
this.metamorphosisEngines = [new SelfProposalEngine({ projectRoot: process.cwd() })];
|
|
59
62
|
// Initialize trigger mechanisms
|
|
60
63
|
this.triggers = {
|
|
61
64
|
gitHook: new GitHookTrigger(this),
|
|
@@ -736,6 +739,14 @@ All path violations will be automatically detected and blocked.
|
|
|
736
739
|
};
|
|
737
740
|
// Handle successful completion
|
|
738
741
|
await this.successHandler.handleSuccess(context, result, monitoringResults);
|
|
742
|
+
for (const engine of this.metamorphosisEngines) {
|
|
743
|
+
try {
|
|
744
|
+
await engine.onPhase('post-process-complete', { context, result, monitoringResults });
|
|
745
|
+
}
|
|
746
|
+
catch (metaErr) {
|
|
747
|
+
await frameworkLogger.log('-post-processor', 'self-proposal-phase-error', 'warning', { error: String(metaErr) });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
739
750
|
// AGENTS.md auto-update with smart triggers
|
|
740
751
|
// Only updates when agent-related files have changed
|
|
741
752
|
const agentChangePatterns = [
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Session Summary Post-Processor
|
|
3
3
|
*
|
|
4
4
|
* Generates emoji-formatted session summaries after tool execution.
|
|
5
|
-
* Uses features.json ->
|
|
5
|
+
* Uses features.json -> autonomous_reporting config for control.
|
|
6
6
|
*
|
|
7
7
|
* @since 2026-04-04
|
|
8
8
|
*/
|
|
@@ -29,12 +29,15 @@ export class SessionSummaryProcessor extends PostProcessor {
|
|
|
29
29
|
loadConfig() {
|
|
30
30
|
try {
|
|
31
31
|
const configData = featuresConfigLoader.loadConfig();
|
|
32
|
-
const autoReporting = configData.auto_reporting;
|
|
32
|
+
const autoReporting = configData.autonomous_reporting ?? configData.auto_reporting;
|
|
33
33
|
if (autoReporting) {
|
|
34
|
-
this.config.enabled = autoReporting.
|
|
35
|
-
this.config.include_emojis =
|
|
36
|
-
|
|
37
|
-
this.config.
|
|
34
|
+
this.config.enabled = autoReporting.enabled !== false;
|
|
35
|
+
this.config.include_emojis =
|
|
36
|
+
autoReporting.include_agent_activities !== false;
|
|
37
|
+
this.config.include_recommendations =
|
|
38
|
+
autoReporting.include_recommendations !== false;
|
|
39
|
+
this.config.include_agent_activities =
|
|
40
|
+
autoReporting.include_agent_activities !== false;
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0xray",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.5",
|
|
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",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"version:bump": "node scripts/node/version-manager.mjs",
|
|
65
65
|
"version": "node scripts/node/version-manager.mjs",
|
|
66
66
|
"prebuild": "rm -rf dist tsconfig.tsbuildinfo tsconfig.*.tsbuildinfo",
|
|
67
|
-
"build": "tsc && mkdir -p dist/public dist/scripts && cp -r public/* dist/public/ && cp scripts/hooks/pre-command dist/scripts/ && cp scripts/hooks/pre-command.mjs dist/scripts/ && cp README.md AGENTS.md SKILLS.md CHANGELOG.md LICENSE .gitignore.default dist/ && find src -name '*.mjs' ! -path '*/__tests__/*' | while read f; do tgt=\"dist/${f#src/}\"; mkdir -p \"$(dirname $tgt)\"; cp \"$f\" \"$tgt\"; done && for dir in skills integrations mcps; do find src/$dir -type f ! -name '*.ts' ! -path '*/.pytest_cache/*' | while read f; do tgt=\"dist/${f#src/}\"; mkdir -p \"$(dirname $tgt)\"; cp \"$f\" \"$tgt\"; done; done && mkdir -p dist/plugin && (find dist/plugin -name '*codex-injection*.js' -exec cp {} dist/plugin/xray-codex-injection.js \\; 2>/dev/null || true) && cp -r src/opencode/ .opencode/ && mkdir -p .xray && for f in codex.json features.json features.schema.json config.json; do cp xray/$f .xray/$f 2>/dev/null || true; done",
|
|
67
|
+
"build": "tsc && chmod +x dist/cli/index.js && mkdir -p dist/public dist/scripts && cp -r public/* dist/public/ && cp scripts/hooks/pre-command dist/scripts/ && cp scripts/hooks/pre-command.mjs dist/scripts/ && cp README.md AGENTS.md SKILLS.md CHANGELOG.md LICENSE .gitignore.default dist/ && find src -name '*.mjs' ! -path '*/__tests__/*' | while read f; do tgt=\"dist/${f#src/}\"; mkdir -p \"$(dirname $tgt)\"; cp \"$f\" \"$tgt\"; done && for dir in skills integrations mcps; do find src/$dir -type f ! -name '*.ts' ! -path '*/.pytest_cache/*' | while read f; do tgt=\"dist/${f#src/}\"; mkdir -p \"$(dirname $tgt)\"; cp \"$f\" \"$tgt\"; done; done && mkdir -p dist/plugin && (find dist/plugin -name '*codex-injection*.js' -exec cp {} dist/plugin/xray-codex-injection.js \\; 2>/dev/null || true) && cp -r src/opencode/ .opencode/ && mkdir -p .xray && for f in codex.json features.json features.schema.json config.json; do cp xray/$f .xray/$f 2>/dev/null || true; done",
|
|
68
68
|
"build:all": "npm run build",
|
|
69
69
|
"ci-install": "npm ci",
|
|
70
70
|
"clean": "rm -rf dist tsconfig.tsbuildinfo tsconfig.*.tsbuildinfo",
|
|
@@ -129,11 +129,16 @@
|
|
|
129
129
|
"verify:orchestrator-behavior": "npm run build && node scripts/mjs/verify-orchestrator-behavior.mjs",
|
|
130
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
131
|
"verify:grok-synthesis-gate": "npm run build && node scripts/mjs/verify-grok-synthesis-gate.mjs",
|
|
132
|
-
"verify:hermes-delegation-gate": "npm run build && node scripts/mjs/verify-hermes-delegation-gate.mjs",
|
|
132
|
+
"verify:hermes-delegation-gate": "npm run build && node scripts/mjs/verify-hermes-delegation-gate.mjs && node scripts/mjs/verify-hermes-session-start.mjs",
|
|
133
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",
|
|
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 && node scripts/mjs/verify-confer-core.mjs",
|
|
135
|
+
"verify:confer": "npm run build && node scripts/mjs/verify-confer-core.mjs",
|
|
136
|
+
"verify:user-aside": "npm run build && node scripts/mjs/verify-user-aside-core.mjs",
|
|
137
|
+
"verify:pipeline-facets": "npm run build && node scripts/mjs/verify-pipeline-facets.mjs",
|
|
135
138
|
"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"
|
|
139
|
+
"verify:consumer-gitignore": "node scripts/mjs/verify-consumer-gitignore-core.mjs",
|
|
140
|
+
"verify:pre-commit-diff": "node scripts/mjs/verify-pre-commit-diff-core.mjs",
|
|
141
|
+
"verify:pre-push-diff": "node scripts/mjs/verify-pre-push-diff-core.mjs"
|
|
137
142
|
},
|
|
138
143
|
"files": [
|
|
139
144
|
"dist/",
|
|
@@ -149,6 +154,10 @@
|
|
|
149
154
|
"scripts/mjs/verify-grok-delegation-gate.mjs",
|
|
150
155
|
"scripts/mjs/verify-grok-synthesis-gate.mjs",
|
|
151
156
|
"scripts/mjs/verify-hermes-delegation-gate.mjs",
|
|
157
|
+
"scripts/mjs/verify-hermes-session-start.mjs",
|
|
158
|
+
"scripts/mjs/verify-pre-push-diff-core.mjs",
|
|
159
|
+
"scripts/mjs/verify-confer-core.mjs",
|
|
160
|
+
"scripts/mjs/verify-user-aside-core.mjs",
|
|
152
161
|
"scripts/mjs/verify-delegation-gate-core.mjs",
|
|
153
162
|
"scripts/mjs/delegation-gate-fixture.mjs",
|
|
154
163
|
"src/opencode/",
|
|
@@ -108,9 +108,9 @@ BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
|
108
108
|
) &
|
|
109
109
|
|
|
110
110
|
MODE="minimal"
|
|
111
|
-
COMMIT_THRESHOLD=
|
|
111
|
+
COMMIT_THRESHOLD=50
|
|
112
112
|
DAYS_THRESHOLD=14
|
|
113
|
-
AUTO_GENERATE=
|
|
113
|
+
AUTO_GENERATE=false
|
|
114
114
|
PROMPT_USER=true
|
|
115
115
|
|
|
116
116
|
if command -v node >/dev/null 2>&1; then
|
|
@@ -119,7 +119,7 @@ if command -v node >/dev/null 2>&1; then
|
|
|
119
119
|
export PROJECT_ROOT="$PROJECT_ROOT"
|
|
120
120
|
REFLECTION_JSON=$(node "$LOAD_SCRIPT" --json 2>/dev/null || echo '{}')
|
|
121
121
|
MODE=$(node -e 'const c=JSON.parse(process.argv[1]);process.stdout.write(String(c.mode||"minimal"))' "$REFLECTION_JSON")
|
|
122
|
-
COMMIT_THRESHOLD=$(node -e 'const c=JSON.parse(process.argv[1]);process.stdout.write(String(c.commitThreshold??
|
|
122
|
+
COMMIT_THRESHOLD=$(node -e 'const c=JSON.parse(process.argv[1]);process.stdout.write(String(c.commitThreshold??50))' "$REFLECTION_JSON")
|
|
123
123
|
DAYS_THRESHOLD=$(node -e 'const c=JSON.parse(process.argv[1]);process.stdout.write(String(c.daysThreshold??14))' "$REFLECTION_JSON")
|
|
124
124
|
AUTO_GENERATE=$(node -e 'const c=JSON.parse(process.argv[1]);process.stdout.write(c.autoGenerate===false?"false":"true")' "$REFLECTION_JSON")
|
|
125
125
|
PROMPT_USER=$(node -e 'const c=JSON.parse(process.argv[1]);process.stdout.write(c.promptUser===false?"false":"true")' "$REFLECTION_JSON")
|