@adhdev/daemon-core 0.9.82-rc.59 → 0.9.82-rc.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.59",
3
+ "version": "0.9.82-rc.60",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,7 +38,7 @@ import { createInteractionId, getRecentDebugTrace, recordDebugTrace } from '../l
38
38
  import { getSessionHostSurfaceKind, partitionSessionHostRecords } from '../session-host/runtime-surface.js';
39
39
  import { createHermesManualMeshCoordinatorSetup, resolveMeshCoordinatorSetup } from './mesh-coordinator.js';
40
40
  import { buildSessionEntries } from '../status/builders.js';
41
- import { handleMeshForwardEvent, drainPendingMeshCoordinatorEvents } from '../mesh/mesh-events.js';
41
+ import { handleMeshForwardEvent, drainPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent } from '../mesh/mesh-events.js';
42
42
  import { buildMeshHostRequiredFailure, normalizeMeshDaemonRole, resolveMeshHostStatus } from '../mesh/mesh-host-ownership.js';
43
43
  import { fastForwardMeshNode } from '../mesh/mesh-fast-forward.js';
44
44
  import {
@@ -909,6 +909,35 @@ type MeshRefinePatchEquivalenceSummary = {
909
909
  stderr?: string;
910
910
  };
911
911
 
912
+ type MeshRefineAsyncJobStatus = 'accepted' | 'completed' | 'failed';
913
+
914
+ type MeshRefineJobHandle = {
915
+ success: true;
916
+ async: true;
917
+ status: MeshRefineAsyncJobStatus;
918
+ jobId: string;
919
+ interactionId: string;
920
+ meshId: string;
921
+ nodeId: string;
922
+ targetNodeId: string;
923
+ targetDaemonId?: string;
924
+ workspace?: string;
925
+ startedAt: string;
926
+ completedAt?: string;
927
+ duplicate?: boolean;
928
+ eventDelivery: {
929
+ pendingEvents: true;
930
+ ledger: true;
931
+ };
932
+ evidence: {
933
+ pendingEventsCommand: 'get_pending_mesh_events';
934
+ ledgerCommand: 'get_mesh_ledger_slice';
935
+ taskHistoryKind: 'task_dispatched' | 'task_completed' | 'task_failed';
936
+ };
937
+ };
938
+
939
+ type MeshRefineTerminalJob = MeshRefineJobHandle & { result?: Record<string, unknown> };
940
+
912
941
  const REFINE_VALIDATION_CATEGORIES = ['typecheck', 'test', 'lint', 'build'] as const;
913
942
  const REFINE_VALIDATION_TIMEOUT_MS = 120_000;
914
943
  const REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
@@ -1395,6 +1424,10 @@ export class DaemonCommandRouter {
1395
1424
  private inlineMeshCache = new Map<string, any>();
1396
1425
  /** Coordinator-owned whole-mesh aggregate status snapshots. Browser callers read this by default. */
1397
1426
  private aggregateMeshStatusCache = new Map<string, { builtAt: number; snapshot: any }>();
1427
+ /** In-memory async Refinery jobs keyed by meshId:nodeId to reject/return duplicate in-flight requests. */
1428
+ private runningRefineJobs = new Map<string, MeshRefineJobHandle>();
1429
+ /** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
1430
+ private terminalRefineJobs = new Map<string, MeshRefineTerminalJob>();
1398
1431
 
1399
1432
  constructor(deps: CommandRouterDeps) {
1400
1433
  this.deps = deps;
@@ -2141,6 +2174,376 @@ export class DaemonCommandRouter {
2141
2174
  }
2142
2175
  }
2143
2176
 
2177
+
2178
+ private buildRefineJobKey(meshId: string, nodeId: string): string {
2179
+ return `${meshId}:${nodeId}`;
2180
+ }
2181
+
2182
+ private buildRefineJobHandle(args: {
2183
+ meshId: string;
2184
+ nodeId: string;
2185
+ node?: any;
2186
+ status?: MeshRefineAsyncJobStatus;
2187
+ startedAt?: string;
2188
+ completedAt?: string;
2189
+ jobId?: string;
2190
+ interactionId?: string;
2191
+ }): MeshRefineJobHandle {
2192
+ return {
2193
+ success: true,
2194
+ async: true,
2195
+ status: args.status || 'accepted',
2196
+ jobId: args.jobId || `refine_${createInteractionId()}`,
2197
+ interactionId: args.interactionId || createInteractionId(),
2198
+ meshId: args.meshId,
2199
+ nodeId: args.nodeId,
2200
+ targetNodeId: args.nodeId,
2201
+ targetDaemonId: readStringValue(args.node?.daemonId),
2202
+ workspace: readStringValue(args.node?.workspace),
2203
+ startedAt: args.startedAt || new Date().toISOString(),
2204
+ ...(args.completedAt ? { completedAt: args.completedAt } : {}),
2205
+ eventDelivery: { pendingEvents: true, ledger: true },
2206
+ evidence: {
2207
+ pendingEventsCommand: 'get_pending_mesh_events',
2208
+ ledgerCommand: 'get_mesh_ledger_slice',
2209
+ taskHistoryKind: args.status === 'completed' ? 'task_completed' : args.status === 'failed' ? 'task_failed' : 'task_dispatched',
2210
+ },
2211
+ };
2212
+ }
2213
+
2214
+ private queueRefineJobEvent(event: 'refine:accepted' | 'refine:completed' | 'refine:failed', handle: MeshRefineJobHandle, result?: Record<string, unknown>): void {
2215
+ queuePendingMeshCoordinatorEvent({
2216
+ event,
2217
+ meshId: handle.meshId,
2218
+ nodeLabel: handle.targetNodeId,
2219
+ nodeId: handle.targetNodeId,
2220
+ workspace: handle.workspace,
2221
+ metadataEvent: {
2222
+ source: 'refine_mesh_node_async_job',
2223
+ jobId: handle.jobId,
2224
+ interactionId: handle.interactionId,
2225
+ meshId: handle.meshId,
2226
+ nodeId: handle.targetNodeId,
2227
+ targetDaemonId: handle.targetDaemonId,
2228
+ workspace: handle.workspace,
2229
+ status: handle.status,
2230
+ startedAt: handle.startedAt,
2231
+ completedAt: handle.completedAt,
2232
+ ...(result ? { result } : {}),
2233
+ },
2234
+ queuedAt: Date.now(),
2235
+ });
2236
+ }
2237
+
2238
+ private async appendRefineJobLedger(kind: 'task_dispatched' | 'task_completed' | 'task_failed', handle: MeshRefineJobHandle, result?: Record<string, unknown>): Promise<void> {
2239
+ try {
2240
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2241
+ appendLedgerEntry(handle.meshId, {
2242
+ kind,
2243
+ nodeId: handle.targetNodeId,
2244
+ payload: {
2245
+ source: 'refine_mesh_node_async_job',
2246
+ refineJob: {
2247
+ jobId: handle.jobId,
2248
+ interactionId: handle.interactionId,
2249
+ status: handle.status,
2250
+ meshId: handle.meshId,
2251
+ nodeId: handle.targetNodeId,
2252
+ targetDaemonId: handle.targetDaemonId,
2253
+ workspace: handle.workspace,
2254
+ startedAt: handle.startedAt,
2255
+ completedAt: handle.completedAt,
2256
+ },
2257
+ async: true,
2258
+ ...(result ? {
2259
+ success: result.success === true,
2260
+ result,
2261
+ finalBranchConvergenceState: result.finalBranchConvergenceState,
2262
+ } : {}),
2263
+ },
2264
+ });
2265
+ } catch (e: any) {
2266
+ LOG.warn('Mesh', `[Refinery] Failed to append async refine ledger entry: ${e?.message || e}`);
2267
+ }
2268
+ }
2269
+
2270
+ private async executeMeshRefineNodeSynchronously(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
2271
+ const refineStages: Array<Record<string, unknown>> = [];
2272
+ try {
2273
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2274
+ const mesh = meshRecord?.mesh;
2275
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2276
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh`, refineStages };
2277
+
2278
+ if (!node.isLocalWorktree || !node.workspace) {
2279
+ return { success: false, error: `Refinery requires a local worktree node`, refineStages };
2280
+ }
2281
+
2282
+ const sourceNode = node.clonedFromNodeId
2283
+ ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
2284
+ : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
2285
+ const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
2286
+ if (!repoRoot) return { success: false, error: 'Source node repoRoot not found', refineStages };
2287
+
2288
+ const { execFile } = await import('node:child_process');
2289
+ const { promisify } = await import('node:util');
2290
+ const execFileAsync = promisify(execFile);
2291
+
2292
+ const resolveStarted = Date.now();
2293
+ const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
2294
+ const branch = branchStdout.trim();
2295
+ if (!branch) return { success: false, error: 'Could not determine branch of the worktree node', refineStages };
2296
+
2297
+ const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
2298
+ const baseBranch = baseBranchStdout.trim();
2299
+ const { stdout: baseHeadStdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
2300
+ const { stdout: branchHeadStdout } = await execFileAsync('git', ['rev-parse', branch], { cwd: node.workspace, encoding: 'utf8' });
2301
+ const baseHead = baseHeadStdout.trim();
2302
+ const branchHead = branchHeadStdout.trim();
2303
+ recordMeshRefineStage(refineStages, 'resolve_refs', 'passed', resolveStarted, { branch, baseBranch, baseHead, branchHead });
2304
+
2305
+ const validationStarted = Date.now();
2306
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
2307
+ recordMeshRefineStage(
2308
+ refineStages,
2309
+ 'validation',
2310
+ validationSummary.status === 'passed' ? 'passed' : validationSummary.status === 'failed' ? 'failed' : 'skipped',
2311
+ validationStarted,
2312
+ { validationStatus: validationSummary.status, commandsRun: validationSummary.commandsRun.length },
2313
+ );
2314
+ if (validationSummary.status === 'failed') {
2315
+ return {
2316
+ success: false,
2317
+ code: 'validation_failed',
2318
+ convergenceStatus: 'blocked_review',
2319
+ error: 'Refinery validation gate failed; merge/refine was not attempted.',
2320
+ branch,
2321
+ into: baseBranch,
2322
+ validationSummary,
2323
+ refineStages,
2324
+ finalBranchConvergenceState: {
2325
+ branch,
2326
+ baseBranch,
2327
+ merged: false,
2328
+ removed: false,
2329
+ validation: 'failed',
2330
+ status: 'blocked_review',
2331
+ },
2332
+ };
2333
+ }
2334
+ if (validationSummary.status === 'skipped') {
2335
+ return {
2336
+ success: false,
2337
+ code: 'validation_unavailable',
2338
+ convergenceStatus: 'blocked_review',
2339
+ error: 'Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.',
2340
+ branch,
2341
+ into: baseBranch,
2342
+ validationSummary,
2343
+ refineStages,
2344
+ finalBranchConvergenceState: {
2345
+ branch,
2346
+ baseBranch,
2347
+ merged: false,
2348
+ removed: false,
2349
+ validation: 'unavailable',
2350
+ status: 'blocked_review',
2351
+ },
2352
+ };
2353
+ }
2354
+
2355
+ const patchEquivalenceStarted = Date.now();
2356
+ const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
2357
+ recordMeshRefineStage(refineStages, 'patch_equivalence', patchEquivalence.status, patchEquivalenceStarted, {
2358
+ equivalent: patchEquivalence.equivalent,
2359
+ expectedPatchId: patchEquivalence.expectedPatchId,
2360
+ actualPatchId: patchEquivalence.actualPatchId,
2361
+ error: patchEquivalence.error,
2362
+ });
2363
+ if (!patchEquivalence.equivalent) {
2364
+ return {
2365
+ success: false,
2366
+ code: 'patch_equivalence_failed',
2367
+ convergenceStatus: 'blocked_review',
2368
+ error: 'Refinery patch-equivalence preflight failed; merge/refine was not attempted.',
2369
+ branch,
2370
+ into: baseBranch,
2371
+ validationSummary,
2372
+ patchEquivalence,
2373
+ refineStages,
2374
+ finalBranchConvergenceState: {
2375
+ branch,
2376
+ baseBranch,
2377
+ merged: false,
2378
+ removed: false,
2379
+ validation: 'passed',
2380
+ patchEquivalence: 'failed',
2381
+ status: 'blocked_review',
2382
+ },
2383
+ };
2384
+ }
2385
+
2386
+ let mergeResult: Record<string, unknown> | undefined;
2387
+ const mergeStarted = Date.now();
2388
+ try {
2389
+ const result = await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
2390
+ mergeResult = {
2391
+ stdout: truncateValidationOutput(result.stdout),
2392
+ stderr: truncateValidationOutput(result.stderr),
2393
+ durationMs: Date.now() - mergeStarted,
2394
+ };
2395
+ recordMeshRefineStage(refineStages, 'merge', 'passed', mergeStarted, mergeResult);
2396
+ } catch (e: any) {
2397
+ recordMeshRefineStage(refineStages, 'merge', 'failed', mergeStarted, {
2398
+ error: e?.message || String(e),
2399
+ stdout: truncateValidationOutput(e?.stdout),
2400
+ stderr: truncateValidationOutput(e?.stderr),
2401
+ });
2402
+ return {
2403
+ success: false,
2404
+ error: `Merge failed (conflicts?): ${e.message}`,
2405
+ validationSummary,
2406
+ patchEquivalence,
2407
+ refineStages,
2408
+ finalBranchConvergenceState: {
2409
+ branch,
2410
+ baseBranch,
2411
+ merged: false,
2412
+ removed: false,
2413
+ validation: 'passed',
2414
+ patchEquivalence: 'passed',
2415
+ status: 'not_mergeable',
2416
+ },
2417
+ };
2418
+ }
2419
+
2420
+ const cleanupStarted = Date.now();
2421
+ const removeResult = await this.execute('remove_mesh_node', {
2422
+ meshId,
2423
+ nodeId,
2424
+ sessionCleanupMode: 'preserve',
2425
+ inlineMesh: args?.inlineMesh,
2426
+ });
2427
+ recordMeshRefineStage(refineStages, 'cleanup', removeResult?.success === false ? 'failed' : 'passed', cleanupStarted, {
2428
+ removed: removeResult?.removed,
2429
+ code: removeResult?.code,
2430
+ error: removeResult?.error,
2431
+ });
2432
+
2433
+ let ledgerError: string | undefined;
2434
+ const ledgerStarted = Date.now();
2435
+ try {
2436
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2437
+ appendLedgerEntry(meshId, {
2438
+ kind: 'node_removed',
2439
+ nodeId,
2440
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence },
2441
+ });
2442
+ recordMeshRefineStage(refineStages, 'ledger', 'passed', ledgerStarted);
2443
+ } catch (e: any) {
2444
+ ledgerError = e?.message || String(e);
2445
+ recordMeshRefineStage(refineStages, 'ledger', 'failed', ledgerStarted, { error: ledgerError });
2446
+ }
2447
+
2448
+ const finalBranchConvergenceState = {
2449
+ branch: baseBranch,
2450
+ mergedBranch: branch,
2451
+ baseBranch,
2452
+ merged: true,
2453
+ removed: removeResult?.success !== false,
2454
+ validation: 'passed',
2455
+ patchEquivalence: 'passed',
2456
+ status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
2457
+ };
2458
+
2459
+ if (removeResult?.success === false) {
2460
+ return {
2461
+ success: false,
2462
+ code: 'cleanup_failed',
2463
+ error: 'Refinery merge completed but worktree cleanup failed; manual cleanup/retry is required.',
2464
+ merged: true,
2465
+ branch,
2466
+ into: baseBranch,
2467
+ removeResult,
2468
+ validationSummary,
2469
+ patchEquivalence,
2470
+ mergeResult,
2471
+ refineStages,
2472
+ ...(ledgerError ? { ledgerError } : {}),
2473
+ finalBranchConvergenceState,
2474
+ };
2475
+ }
2476
+
2477
+ return {
2478
+ success: true,
2479
+ merged: true,
2480
+ branch,
2481
+ into: baseBranch,
2482
+ removeResult,
2483
+ validationSummary,
2484
+ patchEquivalence,
2485
+ mergeResult,
2486
+ refineStages,
2487
+ ...(ledgerError ? { ledgerError } : {}),
2488
+ finalBranchConvergenceState,
2489
+ };
2490
+ } catch (e: any) {
2491
+ return { success: false, error: e.message, refineStages };
2492
+ }
2493
+ }
2494
+
2495
+ private async finishMeshRefineJob(handle: MeshRefineJobHandle, args: any): Promise<void> {
2496
+ const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
2497
+ let result: Record<string, unknown>;
2498
+ try {
2499
+ result = await this.executeMeshRefineNodeSynchronously(handle.meshId, handle.targetNodeId, args) as Record<string, unknown>;
2500
+ } catch (e: any) {
2501
+ result = { success: false, error: e?.message || String(e) };
2502
+ }
2503
+ const completedAt = new Date().toISOString();
2504
+ const terminalHandle = this.buildRefineJobHandle({
2505
+ meshId: handle.meshId,
2506
+ nodeId: handle.targetNodeId,
2507
+ status: result.success === true ? 'completed' : 'failed',
2508
+ startedAt: handle.startedAt,
2509
+ completedAt,
2510
+ jobId: handle.jobId,
2511
+ interactionId: handle.interactionId,
2512
+ node: { daemonId: handle.targetDaemonId, workspace: handle.workspace },
2513
+ });
2514
+ const terminal: MeshRefineTerminalJob = { ...terminalHandle, result };
2515
+ this.terminalRefineJobs.set(key, terminal);
2516
+ this.runningRefineJobs.delete(key);
2517
+ this.invalidateAggregateMeshStatus(handle.meshId);
2518
+ await this.appendRefineJobLedger(result.success === true ? 'task_completed' : 'task_failed', terminalHandle, result);
2519
+ this.queueRefineJobEvent(result.success === true ? 'refine:completed' : 'refine:failed', terminalHandle, result);
2520
+ }
2521
+
2522
+ private async startMeshRefineJob(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
2523
+ const key = this.buildRefineJobKey(meshId, nodeId);
2524
+ const running = this.runningRefineJobs.get(key);
2525
+ if (running) return { ...running, duplicate: true };
2526
+ const terminal = this.terminalRefineJobs.get(key);
2527
+ if (terminal) return { ...terminal, duplicate: true };
2528
+
2529
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2530
+ const mesh = meshRecord?.mesh;
2531
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2532
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
2533
+ if (!node.isLocalWorktree || !node.workspace) return { success: false, error: `Refinery requires a local worktree node` };
2534
+
2535
+ const handle = this.buildRefineJobHandle({ meshId, nodeId, node });
2536
+ this.runningRefineJobs.set(key, handle);
2537
+ await this.appendRefineJobLedger('task_dispatched', handle);
2538
+ this.queueRefineJobEvent('refine:accepted', handle);
2539
+
2540
+ setImmediate(() => {
2541
+ void this.finishMeshRefineJob(handle, args);
2542
+ });
2543
+
2544
+ return handle;
2545
+ }
2546
+
2144
2547
  // ─── Daemon-level command core ───────────────────
2145
2548
 
2146
2549
  /**
@@ -3359,228 +3762,7 @@ export class DaemonCommandRouter {
3359
3762
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3360
3763
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
3361
3764
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
3362
- const refineStages: Array<Record<string, unknown>> = [];
3363
- try {
3364
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
3365
- const mesh = meshRecord?.mesh;
3366
- const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
3367
- if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh`, refineStages };
3368
-
3369
- if (!node.isLocalWorktree || !node.workspace) {
3370
- return { success: false, error: `Refinery requires a local worktree node`, refineStages };
3371
- }
3372
-
3373
- const sourceNode = node.clonedFromNodeId
3374
- ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
3375
- : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
3376
- const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
3377
- if (!repoRoot) return { success: false, error: 'Source node repoRoot not found', refineStages };
3378
-
3379
- const { execFile } = await import('node:child_process');
3380
- const { promisify } = await import('node:util');
3381
- const execFileAsync = promisify(execFile);
3382
-
3383
- const resolveStarted = Date.now();
3384
- const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
3385
- const branch = branchStdout.trim();
3386
- if (!branch) return { success: false, error: 'Could not determine branch of the worktree node', refineStages };
3387
-
3388
- const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
3389
- const baseBranch = baseBranchStdout.trim();
3390
- const { stdout: baseHeadStdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
3391
- const { stdout: branchHeadStdout } = await execFileAsync('git', ['rev-parse', branch], { cwd: node.workspace, encoding: 'utf8' });
3392
- const baseHead = baseHeadStdout.trim();
3393
- const branchHead = branchHeadStdout.trim();
3394
- recordMeshRefineStage(refineStages, 'resolve_refs', 'passed', resolveStarted, { branch, baseBranch, baseHead, branchHead });
3395
-
3396
- const validationStarted = Date.now();
3397
- const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
3398
- recordMeshRefineStage(
3399
- refineStages,
3400
- 'validation',
3401
- validationSummary.status === 'passed' ? 'passed' : validationSummary.status === 'failed' ? 'failed' : 'skipped',
3402
- validationStarted,
3403
- { validationStatus: validationSummary.status, commandsRun: validationSummary.commandsRun.length },
3404
- );
3405
- if (validationSummary.status === 'failed') {
3406
- return {
3407
- success: false,
3408
- code: 'validation_failed',
3409
- convergenceStatus: 'blocked_review',
3410
- error: 'Refinery validation gate failed; merge/refine was not attempted.',
3411
- branch,
3412
- into: baseBranch,
3413
- validationSummary,
3414
- refineStages,
3415
- finalBranchConvergenceState: {
3416
- branch,
3417
- baseBranch,
3418
- merged: false,
3419
- removed: false,
3420
- validation: 'failed',
3421
- status: 'blocked_review',
3422
- },
3423
- };
3424
- }
3425
- if (validationSummary.status === 'skipped') {
3426
- return {
3427
- success: false,
3428
- code: 'validation_unavailable',
3429
- convergenceStatus: 'blocked_review',
3430
- error: 'Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.',
3431
- branch,
3432
- into: baseBranch,
3433
- validationSummary,
3434
- refineStages,
3435
- finalBranchConvergenceState: {
3436
- branch,
3437
- baseBranch,
3438
- merged: false,
3439
- removed: false,
3440
- validation: 'unavailable',
3441
- status: 'blocked_review',
3442
- },
3443
- };
3444
- }
3445
-
3446
- const patchEquivalenceStarted = Date.now();
3447
- const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
3448
- recordMeshRefineStage(refineStages, 'patch_equivalence', patchEquivalence.status, patchEquivalenceStarted, {
3449
- equivalent: patchEquivalence.equivalent,
3450
- expectedPatchId: patchEquivalence.expectedPatchId,
3451
- actualPatchId: patchEquivalence.actualPatchId,
3452
- error: patchEquivalence.error,
3453
- });
3454
- if (!patchEquivalence.equivalent) {
3455
- return {
3456
- success: false,
3457
- code: 'patch_equivalence_failed',
3458
- convergenceStatus: 'blocked_review',
3459
- error: 'Refinery patch-equivalence preflight failed; merge/refine was not attempted.',
3460
- branch,
3461
- into: baseBranch,
3462
- validationSummary,
3463
- patchEquivalence,
3464
- refineStages,
3465
- finalBranchConvergenceState: {
3466
- branch,
3467
- baseBranch,
3468
- merged: false,
3469
- removed: false,
3470
- validation: 'passed',
3471
- patchEquivalence: 'failed',
3472
- status: 'blocked_review',
3473
- },
3474
- };
3475
- }
3476
-
3477
- let mergeResult: Record<string, unknown> | undefined;
3478
- const mergeStarted = Date.now();
3479
- try {
3480
- const result = await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
3481
- mergeResult = {
3482
- stdout: truncateValidationOutput(result.stdout),
3483
- stderr: truncateValidationOutput(result.stderr),
3484
- durationMs: Date.now() - mergeStarted,
3485
- };
3486
- recordMeshRefineStage(refineStages, 'merge', 'passed', mergeStarted, mergeResult);
3487
- } catch (e: any) {
3488
- recordMeshRefineStage(refineStages, 'merge', 'failed', mergeStarted, {
3489
- error: e?.message || String(e),
3490
- stdout: truncateValidationOutput(e?.stdout),
3491
- stderr: truncateValidationOutput(e?.stderr),
3492
- });
3493
- return {
3494
- success: false,
3495
- error: `Merge failed (conflicts?): ${e.message}`,
3496
- validationSummary,
3497
- patchEquivalence,
3498
- refineStages,
3499
- finalBranchConvergenceState: {
3500
- branch,
3501
- baseBranch,
3502
- merged: false,
3503
- removed: false,
3504
- validation: 'passed',
3505
- patchEquivalence: 'passed',
3506
- status: 'not_mergeable',
3507
- },
3508
- };
3509
- }
3510
-
3511
- const cleanupStarted = Date.now();
3512
- const removeResult = await this.execute('remove_mesh_node', {
3513
- meshId,
3514
- nodeId,
3515
- sessionCleanupMode: 'preserve',
3516
- inlineMesh: args?.inlineMesh,
3517
- });
3518
- recordMeshRefineStage(refineStages, 'cleanup', removeResult?.success === false ? 'failed' : 'passed', cleanupStarted, {
3519
- removed: removeResult?.removed,
3520
- code: removeResult?.code,
3521
- error: removeResult?.error,
3522
- });
3523
-
3524
- let ledgerError: string | undefined;
3525
- const ledgerStarted = Date.now();
3526
- try {
3527
- const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
3528
- appendLedgerEntry(meshId, {
3529
- kind: 'node_removed',
3530
- nodeId,
3531
- payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence },
3532
- });
3533
- recordMeshRefineStage(refineStages, 'ledger', 'passed', ledgerStarted);
3534
- } catch (e: any) {
3535
- ledgerError = e?.message || String(e);
3536
- recordMeshRefineStage(refineStages, 'ledger', 'failed', ledgerStarted, { error: ledgerError });
3537
- }
3538
-
3539
- const finalBranchConvergenceState = {
3540
- branch: baseBranch,
3541
- mergedBranch: branch,
3542
- baseBranch,
3543
- merged: true,
3544
- removed: removeResult?.success !== false,
3545
- validation: 'passed',
3546
- patchEquivalence: 'passed',
3547
- status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
3548
- };
3549
-
3550
- if (removeResult?.success === false) {
3551
- return {
3552
- success: false,
3553
- code: 'cleanup_failed',
3554
- error: 'Refinery merge completed but worktree cleanup failed; manual cleanup/retry is required.',
3555
- merged: true,
3556
- branch,
3557
- into: baseBranch,
3558
- removeResult,
3559
- validationSummary,
3560
- patchEquivalence,
3561
- mergeResult,
3562
- refineStages,
3563
- ...(ledgerError ? { ledgerError } : {}),
3564
- finalBranchConvergenceState,
3565
- };
3566
- }
3567
-
3568
- return {
3569
- success: true,
3570
- merged: true,
3571
- branch,
3572
- into: baseBranch,
3573
- removeResult,
3574
- validationSummary,
3575
- patchEquivalence,
3576
- mergeResult,
3577
- refineStages,
3578
- ...(ledgerError ? { ledgerError } : {}),
3579
- finalBranchConvergenceState,
3580
- };
3581
- } catch (e: any) {
3582
- return { success: false, error: e.message, refineStages };
3583
- }
3765
+ return this.startMeshRefineJob(meshId, nodeId, args);
3584
3766
  }
3585
3767
 
3586
3768
  case 'remove_mesh_node': {