@adhdev/daemon-core 0.9.82-rc.522 → 0.9.82-rc.524

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/dist/index.js CHANGED
@@ -419,10 +419,10 @@ function readInjected(value) {
419
419
  }
420
420
  function getDaemonBuildInfo() {
421
421
  if (cached) return cached;
422
- const commit = readInjected(true ? "d3a44d29416fef77ac6cd36f4798637571260dfb" : void 0) ?? "unknown";
423
- const commitShort = readInjected(true ? "d3a44d29" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
424
- const version = readInjected(true ? "0.9.82-rc.522" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
425
- const builtAt = readInjected(true ? "2026-07-14T03:03:55.067Z" : void 0);
422
+ const commit = readInjected(true ? "91ed5e0752d70b62ebcc2cddcea4d502756cff0a" : void 0) ?? "unknown";
423
+ const commitShort = readInjected(true ? "91ed5e07" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
424
+ const version = readInjected(true ? "0.9.82-rc.524" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
425
+ const builtAt = readInjected(true ? "2026-07-14T07:23:02.573Z" : void 0);
426
426
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
427
427
  return cached;
428
428
  }
@@ -794,6 +794,9 @@ function isNonRuntimeRootFile(file, policy) {
794
794
  }
795
795
  return false;
796
796
  }
797
+ function deriveChangeArea(isDaemonAffecting, affectedPackages) {
798
+ return isDaemonAffecting ? "daemon" : affectedPackages.length > 0 ? "web" : "none";
799
+ }
797
800
  function classifyChangedFileList(files, policy) {
798
801
  if (files.length === 0) {
799
802
  return { isDaemonAffecting: true, affectedPackages: [], ambiguousNonPackageFiles: [] };
@@ -816,9 +819,10 @@ async function classifyDaemonBuildChange(repoPath, buildCommit, options, policy)
816
819
  try {
817
820
  const diff = await runGit(repoPath, ["diff", "--name-only", `${buildCommit}..HEAD`], options);
818
821
  const files = diff.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
819
- return classifyChangedFileList(files, policy);
822
+ const { isDaemonAffecting, affectedPackages } = classifyChangedFileList(files, policy);
823
+ return { isDaemonAffecting, affectedPackages, changeArea: deriveChangeArea(isDaemonAffecting, affectedPackages) };
820
824
  } catch {
821
- return { isDaemonAffecting: true, affectedPackages: [] };
825
+ return { isDaemonAffecting: true, affectedPackages: [], changeArea: "daemon" };
822
826
  }
823
827
  }
824
828
  async function classifyChangedPackages(repoPath, fromRef, toRef, options = {}) {
@@ -831,7 +835,7 @@ async function classifyChangedPackages(repoPath, fromRef, toRef, options = {}) {
831
835
  return refineVerdictThroughSubmodules(repoPath, fromRef, toRef, options, policy, rootVerdict);
832
836
  }
833
837
  async function refineVerdictThroughSubmodules(repoPath, fromRef, toRef, options, policy, rootVerdict) {
834
- const strip = ({ isDaemonAffecting, affectedPackages }) => ({ isDaemonAffecting, affectedPackages });
838
+ const strip = ({ isDaemonAffecting, affectedPackages: affectedPackages2 }) => ({ isDaemonAffecting, affectedPackages: affectedPackages2, changeArea: deriveChangeArea(isDaemonAffecting, affectedPackages2) });
835
839
  const ambiguous = rootVerdict.ambiguousNonPackageFiles;
836
840
  if (ambiguous.length === 0) return strip(rootVerdict);
837
841
  let submodulePaths;
@@ -863,9 +867,11 @@ async function refineVerdictThroughSubmodules(repoPath, fromRef, toRef, options,
863
867
  return strip(rootVerdict);
864
868
  }
865
869
  if (subVerdict.isDaemonAffecting) {
870
+ const affectedPackages2 = [.../* @__PURE__ */ new Set([...rootVerdict.affectedPackages, ...subVerdict.affectedPackages])].sort();
866
871
  return {
867
872
  isDaemonAffecting: true,
868
- affectedPackages: [.../* @__PURE__ */ new Set([...rootVerdict.affectedPackages, ...subVerdict.affectedPackages])].sort()
873
+ affectedPackages: affectedPackages2,
874
+ changeArea: deriveChangeArea(true, affectedPackages2)
869
875
  };
870
876
  }
871
877
  submoduleAffectedPackages.push(...subVerdict.affectedPackages);
@@ -873,9 +879,11 @@ async function refineVerdictThroughSubmodules(repoPath, fromRef, toRef, options,
873
879
  const rootPackagesBenign = rootVerdict.affectedPackages.every(
874
880
  (p) => policy.webOnlyPackages.has(p) && !policy.daemonRuntimePackages.has(p)
875
881
  );
882
+ const affectedPackages = [.../* @__PURE__ */ new Set([...rootVerdict.affectedPackages, ...submoduleAffectedPackages])].sort();
876
883
  return {
877
884
  isDaemonAffecting: !rootPackagesBenign,
878
- affectedPackages: [.../* @__PURE__ */ new Set([...rootVerdict.affectedPackages, ...submoduleAffectedPackages])].sort()
885
+ affectedPackages,
886
+ changeArea: deriveChangeArea(!rootPackagesBenign, affectedPackages)
879
887
  };
880
888
  }
881
889
  async function listSubmodulePaths(repoPath, options) {
@@ -10753,6 +10761,14 @@ function normalizeMeshCommandConfig(entry, source) {
10753
10761
  if (entry.env !== void 0 && (!isMeshConfigRecord(entry.env) || !Object.values(entry.env).every((value) => typeof value === "string"))) {
10754
10762
  return { rejected: { source, command: commandText, reason: "env must be an object of string values" } };
10755
10763
  }
10764
+ let scopes;
10765
+ if (entry.scopes !== void 0) {
10766
+ if (!Array.isArray(entry.scopes) || !entry.scopes.every((s2) => MESH_REFINE_VALIDATION_SCOPES.includes(s2))) {
10767
+ return { rejected: { source, command: commandText, reason: `scopes must be an array of ${MESH_REFINE_VALIDATION_SCOPES.join(" | ")}` } };
10768
+ }
10769
+ const deduped = [...new Set(entry.scopes)];
10770
+ scopes = deduped.length ? deduped : void 0;
10771
+ }
10756
10772
  return {
10757
10773
  command: {
10758
10774
  command,
@@ -10763,7 +10779,8 @@ function normalizeMeshCommandConfig(entry, source) {
10763
10779
  ...typeof entry.cwd === "string" && entry.cwd.trim() ? { cwd: entry.cwd.trim() } : {},
10764
10780
  ...typeof entry.timeoutMs === "number" ? { timeoutMs: entry.timeoutMs } : {},
10765
10781
  ...typeof entry.outputLimitBytes === "number" ? { outputLimitBytes: entry.outputLimitBytes } : {},
10766
- ...isMeshConfigRecord(entry.env) ? { env: entry.env } : {}
10782
+ ...isMeshConfigRecord(entry.env) ? { env: entry.env } : {},
10783
+ ...scopes ? { scopes } : {}
10767
10784
  }
10768
10785
  };
10769
10786
  }
@@ -10920,7 +10937,7 @@ function resolveMeshRefineValidationPlan(mesh, workspace) {
10920
10937
  unavailableReason: validation.commands.length ? void 0 : "validation_unavailable: repo mesh/refine config has no validation.commands"
10921
10938
  };
10922
10939
  }
10923
- var import_fs8, import_path8, yaml2, MESH_REFINE_VALIDATION_CATEGORIES, MESH_REFINE_CONFIG_LOCATIONS, MESH_REFINE_CONFIG_SCHEMA, SHELL_METACHAR_RE, SAFE_TOKEN_RE, SAFE_WIN32_EXEC_TOKEN_RE, isRecord2;
10940
+ var import_fs8, import_path8, yaml2, MESH_REFINE_VALIDATION_CATEGORIES, MESH_REFINE_VALIDATION_SCOPES, MESH_REFINE_CONFIG_LOCATIONS, MESH_REFINE_CONFIG_SCHEMA, SHELL_METACHAR_RE, SAFE_TOKEN_RE, SAFE_WIN32_EXEC_TOKEN_RE, isRecord2;
10924
10941
  var init_refine_config = __esm({
10925
10942
  "src/mesh/refine-config.ts"() {
10926
10943
  "use strict";
@@ -10928,6 +10945,7 @@ var init_refine_config = __esm({
10928
10945
  import_path8 = require("path");
10929
10946
  yaml2 = __toESM(require("js-yaml"));
10930
10947
  MESH_REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
10948
+ MESH_REFINE_VALIDATION_SCOPES = ["none", "web", "daemon"];
10931
10949
  MESH_REFINE_CONFIG_LOCATIONS = [
10932
10950
  ".adhdev/refine.json",
10933
10951
  ".adhdev/refine.yaml",
@@ -10977,7 +10995,12 @@ var init_refine_config = __esm({
10977
10995
  cwd: { type: "string" },
10978
10996
  timeoutMs: { type: "number", minimum: 1e3, maximum: 6e5 },
10979
10997
  outputLimitBytes: { type: "number", minimum: 1024, maximum: 1048576 },
10980
- env: { type: "object", additionalProperties: { type: "string" } }
10998
+ env: { type: "object", additionalProperties: { type: "string" } },
10999
+ scopes: {
11000
+ type: "array",
11001
+ items: { enum: [...MESH_REFINE_VALIDATION_SCOPES] },
11002
+ description: "DOCS-ROOT: change-impact scopes this command runs in ('none'=docs-only, 'web', 'daemon'). Omitted/empty \u2192 runs in every area."
11003
+ }
10981
11004
  }
10982
11005
  }
10983
11006
  },
@@ -10996,7 +11019,11 @@ var init_refine_config = __esm({
10996
11019
  cwd: { type: "string" },
10997
11020
  timeoutMs: { type: "number", minimum: 1e3, maximum: 6e5 },
10998
11021
  outputLimitBytes: { type: "number", minimum: 1024, maximum: 1048576 },
10999
- env: { type: "object", additionalProperties: { type: "string" } }
11022
+ env: { type: "object", additionalProperties: { type: "string" } },
11023
+ scopes: {
11024
+ type: "array",
11025
+ items: { enum: [...MESH_REFINE_VALIDATION_SCOPES] }
11026
+ }
11000
11027
  }
11001
11028
  }
11002
11029
  }
@@ -17216,6 +17243,56 @@ async function runContinuousAutoFastForwardScan(components, mesh) {
17216
17243
  await delegateRemoteAutoFastForward(components, { meshId, nodeId, node, daemonId, workspace, policy, trigger: "reconcile_auto" });
17217
17244
  }
17218
17245
  }
17246
+ async function runPendingCoordinatorCatchupScan(components, mesh) {
17247
+ const meshId = readNonEmptyString2(mesh?.id);
17248
+ if (!meshId) return;
17249
+ const localIds = expandDaemonIdForms([
17250
+ readNonEmptyString2(components.statusInstanceId),
17251
+ readNonEmptyString2(loadConfig().machineId)
17252
+ ]);
17253
+ let markers = [];
17254
+ try {
17255
+ markers = drainPendingMeshCoordinatorEvents(
17256
+ meshId,
17257
+ localIds.length > 0 ? localIds : void 0,
17258
+ { onlyEvents: /* @__PURE__ */ new Set(["coordinator_catchup"]) }
17259
+ );
17260
+ } catch (e) {
17261
+ LOG.warn("MeshReconcile", `Coordinator-catchup drain failed for mesh ${meshId}: ${e?.message || e}`);
17262
+ return;
17263
+ }
17264
+ if (markers.length === 0) return;
17265
+ const nodes = Array.isArray(mesh?.nodes) ? mesh.nodes : [];
17266
+ for (const marker of markers) {
17267
+ const meta = marker.metadataEvent || {};
17268
+ const nodeId = readNonEmptyString2(marker.nodeId) || readNonEmptyString2(meta.nodeId);
17269
+ const workspace = readNonEmptyString2(marker.workspace) || readNonEmptyString2(meta.workspace);
17270
+ const baseBranch = readNonEmptyString2(meta.baseBranch);
17271
+ if (!workspace) continue;
17272
+ if (nodeId && nodeHasActiveMeshWork(components, meshId, nodeId)) {
17273
+ try {
17274
+ queuePendingMeshCoordinatorEvent(marker);
17275
+ } catch {
17276
+ }
17277
+ continue;
17278
+ }
17279
+ try {
17280
+ const ff = await fastForwardMeshNode({
17281
+ meshId,
17282
+ ...nodeId ? { nodeId } : {},
17283
+ workspace,
17284
+ ...baseBranch ? { branch: baseBranch } : {},
17285
+ mode: "merge",
17286
+ execute: true,
17287
+ trigger: "refine_post_push_catchup",
17288
+ allowAutoPublishSubmoduleMainCommits: mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true
17289
+ });
17290
+ LOG.info("MeshReconcile", `Coordinator catch-up ff for ${meshId}/${nodeId || workspace}: ${ff.code} (executed=${ff.executed})`);
17291
+ } catch (e) {
17292
+ LOG.warn("MeshReconcile", `Coordinator catch-up ff failed for ${meshId}/${nodeId || workspace}: ${e?.message || e}`);
17293
+ }
17294
+ }
17295
+ }
17219
17296
  function runIdleMaintenanceThenAssignQueue(components, args) {
17220
17297
  setImmediate(() => {
17221
17298
  maybeAutoFastForwardIdleNode(components, args).finally(() => {
@@ -22212,6 +22289,15 @@ async function runMeshReconcileTick(components) {
22212
22289
  }
22213
22290
  }
22214
22291
  }
22292
+ for (const mesh of listMeshes()) {
22293
+ const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
22294
+ if (!daemonHostsMesh(mesh, selfIds)) continue;
22295
+ try {
22296
+ await runPendingCoordinatorCatchupScan(components, mesh);
22297
+ } catch (e) {
22298
+ LOG.warn("MeshReconcile", `Coordinator catch-up scan failed for mesh ${mesh.id}: ${e?.message || e}`);
22299
+ }
22300
+ }
22215
22301
  if (dispatchMeshCommand) {
22216
22302
  for (const mesh of listMeshes()) {
22217
22303
  const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
@@ -59315,6 +59401,8 @@ init_logger();
59315
59401
  init_debug_trace();
59316
59402
  init_dist();
59317
59403
  init_mesh_events();
59404
+ init_mesh_reconcile_identity();
59405
+ init_mesh_fast_forward();
59318
59406
 
59319
59407
  // src/mesh/mesh-refine-batch.ts
59320
59408
  var import_node_child_process5 = require("child_process");
@@ -60244,7 +60332,10 @@ function buildMeshRefineValidationPlan(mesh, workspace) {
60244
60332
  category: command.category,
60245
60333
  source: command.source,
60246
60334
  cwd: command.cwd,
60247
- timeoutMs: command.timeoutMs
60335
+ timeoutMs: command.timeoutMs,
60336
+ // DOCS-ROOT: surface the change-impact scopes so `mesh_refine_config` shows which
60337
+ // area(s) each command runs in (absent → every area).
60338
+ ...command.scopes ? { scopes: command.scopes } : {}
60248
60339
  });
60249
60340
  return {
60250
60341
  source: plan.source,
@@ -60357,9 +60448,33 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
60357
60448
  return /\bdaemon-core\b|\bdaemon-cloud\b|\btest:daemon\b|check-vendor-drift/.test(haystack);
60358
60449
  };
60359
60450
  const scopeUnaffectedDaemon = opts?.changeImpact?.isDaemonAffecting === false;
60451
+ const changeArea = opts?.changeImpact?.changeArea;
60452
+ const commandRunsInArea = (candidate) => {
60453
+ if (!changeArea) return true;
60454
+ const scopes = candidate.scopes;
60455
+ if (scopes && scopes.length) return scopes.includes(changeArea);
60456
+ return changeArea !== "none";
60457
+ };
60360
60458
  const skippedDaemonCommands = [];
60459
+ const skippedScopeCommands = [];
60361
60460
  const commandsToRun = [];
60362
60461
  for (const candidate of selection.commands) {
60462
+ if (!commandRunsInArea(candidate)) {
60463
+ skippedScopeCommands.push(candidate.displayCommand);
60464
+ summary.commandsRun.push({
60465
+ command: candidate.command,
60466
+ args: candidate.args,
60467
+ displayCommand: candidate.displayCommand,
60468
+ category: candidate.category,
60469
+ source: candidate.source,
60470
+ passed: true,
60471
+ skipped: true,
60472
+ skipReason: "unaffected_change_scope",
60473
+ changeArea,
60474
+ ...candidate.scopes ? { scopes: candidate.scopes } : {}
60475
+ });
60476
+ continue;
60477
+ }
60363
60478
  if (scopeUnaffectedDaemon && isDaemonScopedCommand(candidate)) {
60364
60479
  skippedDaemonCommands.push(candidate.displayCommand);
60365
60480
  summary.commandsRun.push({
@@ -60380,7 +60495,9 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
60380
60495
  summary.changeImpact = {
60381
60496
  isDaemonAffecting: opts.changeImpact.isDaemonAffecting,
60382
60497
  affectedPackages: opts.changeImpact.affectedPackages,
60383
- ...skippedDaemonCommands.length ? { skippedDaemonCommands } : {}
60498
+ ...changeArea ? { changeArea } : {},
60499
+ ...skippedDaemonCommands.length ? { skippedDaemonCommands } : {},
60500
+ ...skippedScopeCommands.length ? { skippedScopeCommands } : {}
60384
60501
  };
60385
60502
  }
60386
60503
  if (runLegacyBootstrapCommands) {
@@ -60506,6 +60623,24 @@ function buildRefineJobHandle(self, args) {
60506
60623
  }
60507
60624
  };
60508
60625
  }
60626
+ function extractValidationFailureDiagnostics(validationSummary) {
60627
+ if (!validationSummary || typeof validationSummary !== "object") return void 0;
60628
+ const commandsRun = Array.isArray(validationSummary.commandsRun) ? validationSummary.commandsRun : [];
60629
+ const failed = commandsRun.find((c) => c.passed === false);
60630
+ const summaryFailureKind = validationSummary.failureKind;
60631
+ if (!failed) {
60632
+ return summaryFailureKind !== void 0 ? { failureKind: summaryFailureKind } : void 0;
60633
+ }
60634
+ const firstFailedCommand = typeof failed.displayCommand === "string" ? failed.displayCommand : typeof failed.command === "string" ? [failed.command, ...Array.isArray(failed.args) ? failed.args : []].join(" ").trim() : void 0;
60635
+ const rawOutput = [failed.stderr, failed.stdout, failed.output].filter((s2) => typeof s2 === "string" && s2.length > 0).join("\n");
60636
+ const outputTail = rawOutput.length > 600 ? rawOutput.slice(-600) : rawOutput;
60637
+ return {
60638
+ ...firstFailedCommand ? { firstFailedCommand } : {},
60639
+ ...failed.exitCode !== void 0 ? { exitCode: failed.exitCode } : {},
60640
+ ...failed.failureKind !== void 0 ? { failureKind: failed.failureKind } : summaryFailureKind !== void 0 ? { failureKind: summaryFailureKind } : {},
60641
+ ...outputTail ? { outputTail } : {}
60642
+ };
60643
+ }
60509
60644
  function slimRefineEventResult(result) {
60510
60645
  const slim = {};
60511
60646
  for (const key2 of [
@@ -60518,7 +60653,12 @@ function slimRefineEventResult(result) {
60518
60653
  "into",
60519
60654
  "terminalKind",
60520
60655
  "nextStep",
60521
- "finalBranchConvergenceState"
60656
+ "finalBranchConvergenceState",
60657
+ // QW4: merge conflict paths; QW5: cleanup branch-ref / residue warnings.
60658
+ "conflictPaths",
60659
+ "branchRefWarning",
60660
+ "residueWarning",
60661
+ "branchRefDeleted"
60522
60662
  ]) {
60523
60663
  if (result[key2] !== void 0) slim[key2] = result[key2];
60524
60664
  }
@@ -60527,12 +60667,15 @@ function slimRefineEventResult(result) {
60527
60667
  }
60528
60668
  if (result.validationSummary && typeof result.validationSummary === "object") {
60529
60669
  const vs = result.validationSummary;
60670
+ const diagnostics = vs.status === "failed" ? extractValidationFailureDiagnostics(vs) : void 0;
60530
60671
  slim.validationSummary = {
60531
60672
  status: vs.status,
60532
60673
  failureCode: vs.failureCode,
60674
+ failureKind: vs.failureKind,
60533
60675
  configSource: vs.configSource,
60534
60676
  configSourceType: vs.configSourceType,
60535
- commandsRunCount: Array.isArray(vs.commandsRun) ? vs.commandsRun.length : void 0
60677
+ commandsRunCount: Array.isArray(vs.commandsRun) ? vs.commandsRun.length : void 0,
60678
+ ...diagnostics ? { failure: diagnostics } : {}
60536
60679
  };
60537
60680
  }
60538
60681
  if (result.patchEquivalence && typeof result.patchEquivalence === "object") {
@@ -60673,6 +60816,8 @@ async function executeMeshRefineNodeSynchronously(self, meshId, nodeId, args) {
60673
60816
  const resolved = await refineResolveRefsStage(self, meshId, nodeId, args, refineStages);
60674
60817
  if (resolved.kind === "terminal") return resolved.result;
60675
60818
  const ctx = resolved.ctx;
60819
+ const syncBase = await refineSyncBaseStage(self, ctx);
60820
+ if (syncBase.kind === "terminal") return syncBase.result;
60676
60821
  const validation = await refineValidationStage(self, ctx);
60677
60822
  if (validation.kind === "terminal") return validation.result;
60678
60823
  const patchEquivalence = await refinePatchEquivalenceStage(self, ctx);
@@ -60761,6 +60906,152 @@ async function refineResolveRefsStage(self, meshId, nodeId, args, refineStages)
60761
60906
  }
60762
60907
  };
60763
60908
  }
60909
+ async function computeBranchBaseDivergence(execFileAsync4, cwd, baseHead, branchHead) {
60910
+ let mergeBase;
60911
+ try {
60912
+ const { stdout } = await execFileAsync4("git", ["merge-base", baseHead, branchHead], { cwd, encoding: "utf8" });
60913
+ mergeBase = stdout.trim() || void 0;
60914
+ } catch {
60915
+ }
60916
+ let ahead = 0;
60917
+ let behind = 0;
60918
+ try {
60919
+ const { stdout } = await execFileAsync4("git", ["rev-list", "--left-right", "--count", `${baseHead}...${branchHead}`], { cwd, encoding: "utf8" });
60920
+ const [left, right] = stdout.trim().split(/\s+/).map((n) => Number.parseInt(n, 10));
60921
+ behind = Number.isFinite(left) ? left : 0;
60922
+ ahead = Number.isFinite(right) ? right : 0;
60923
+ } catch {
60924
+ }
60925
+ return {
60926
+ mergeBase,
60927
+ ahead,
60928
+ behind,
60929
+ diverged: ahead > 0 && behind > 0,
60930
+ // Strictly behind = base is a descendant of branch (branch is an ancestor of base):
60931
+ // behind>0 with ahead===0.
60932
+ isStrictlyBehind: behind > 0 && ahead === 0
60933
+ };
60934
+ }
60935
+ async function refineSyncBaseStage(self, ctx) {
60936
+ const { repoRoot, baseHead, node, branch, baseBranch, refineStages, execFileAsync: execFileAsync4 } = ctx;
60937
+ let branchHead = ctx.branchHead;
60938
+ const syncStarted = Date.now();
60939
+ const divergence = await computeBranchBaseDivergence(execFileAsync4, node.workspace, baseHead, branchHead);
60940
+ if (divergence.behind === 0) {
60941
+ recordMeshRefineStage(refineStages, "sync_base", "passed", syncStarted, {
60942
+ ahead: divergence.ahead,
60943
+ behind: divergence.behind,
60944
+ rebased: false,
60945
+ reason: "branch_up_to_date_with_base"
60946
+ });
60947
+ return { kind: "continue", ctx };
60948
+ }
60949
+ try {
60950
+ const preRebasePe = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
60951
+ const alreadyMerged = !preRebasePe.actualPatchId && !!preRebasePe.expectedPatchId;
60952
+ const submoduleConflict = preRebasePe.actionableHint?.kind === "submodule_conflict";
60953
+ if (alreadyMerged || submoduleConflict) {
60954
+ recordMeshRefineStage(refineStages, "sync_base", "passed", syncStarted, {
60955
+ ahead: divergence.ahead,
60956
+ behind: divergence.behind,
60957
+ rebased: false,
60958
+ reason: alreadyMerged ? "already_merged_via_other_path_skip_rebase" : "submodule_conflict_defer_to_patch_equivalence"
60959
+ });
60960
+ return { kind: "continue", ctx };
60961
+ }
60962
+ } catch {
60963
+ }
60964
+ const rebaseStarted = Date.now();
60965
+ try {
60966
+ (0, import_node_child_process7.execFileSync)("git", ["rebase", baseHead], { cwd: node.workspace, stdio: ["ignore", "pipe", "pipe"] });
60967
+ } catch (rebaseErr) {
60968
+ try {
60969
+ (0, import_node_child_process7.execFileSync)("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
60970
+ } catch {
60971
+ }
60972
+ let submoduleHintPatchEquivalence;
60973
+ try {
60974
+ submoduleHintPatchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, ctx.branchHead);
60975
+ } catch {
60976
+ }
60977
+ const submoduleConflict = submoduleHintPatchEquivalence?.actionableHint?.kind === "submodule_conflict";
60978
+ recordMeshRefineStage(refineStages, "sync_base", "failed", syncStarted, {
60979
+ ahead: divergence.ahead,
60980
+ behind: divergence.behind,
60981
+ diverged: divergence.diverged,
60982
+ error: rebaseErr?.message || String(rebaseErr),
60983
+ ...submoduleConflict ? { submoduleConflict: true } : {}
60984
+ });
60985
+ if (submoduleConflict && submoduleHintPatchEquivalence) {
60986
+ recordMeshRefineStage(refineStages, "patch_equivalence", "failed", rebaseStarted, {
60987
+ equivalent: submoduleHintPatchEquivalence.equivalent,
60988
+ expectedPatchId: submoduleHintPatchEquivalence.expectedPatchId,
60989
+ actualPatchId: submoduleHintPatchEquivalence.actualPatchId,
60990
+ error: submoduleHintPatchEquivalence.error,
60991
+ actionableHint: submoduleHintPatchEquivalence.actionableHint
60992
+ });
60993
+ return { kind: "terminal", result: {
60994
+ success: false,
60995
+ code: "patch_equivalence_failed",
60996
+ convergenceStatus: "blocked_review",
60997
+ error: "Refinery patch-equivalence preflight failed (submodule gitlink conflict); merge/refine was not attempted.",
60998
+ branch,
60999
+ into: baseBranch,
61000
+ patchEquivalence: submoduleHintPatchEquivalence,
61001
+ refineStages,
61002
+ finalBranchConvergenceState: {
61003
+ branch,
61004
+ baseBranch,
61005
+ merged: false,
61006
+ removed: false,
61007
+ patchEquivalence: "failed",
61008
+ status: "blocked_review"
61009
+ }
61010
+ } };
61011
+ }
61012
+ recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", "failed", rebaseStarted, {
61013
+ error: rebaseErr?.message || String(rebaseErr)
61014
+ });
61015
+ return { kind: "terminal", result: {
61016
+ success: false,
61017
+ code: "needs_rebase_with_conflicts",
61018
+ convergenceStatus: "blocked_review",
61019
+ error: divergence.diverged ? `Branch has diverged from ${baseBranch} (ahead ${divergence.ahead}, behind ${divergence.behind}) and auto-rebase onto the fetched base hit conflicts; resolve conflicts manually and retry.` : `Branch is behind ${baseBranch} and auto-rebase failed due to conflicts; resolve conflicts manually and retry.`,
61020
+ branch,
61021
+ into: baseBranch,
61022
+ refineStages,
61023
+ finalBranchConvergenceState: {
61024
+ branch,
61025
+ baseBranch,
61026
+ merged: false,
61027
+ removed: false,
61028
+ status: "blocked_review"
61029
+ }
61030
+ } };
61031
+ }
61032
+ const { stdout: rebasedHeadStdout } = await execFileAsync4("git", ["rev-parse", "HEAD"], { cwd: node.workspace, encoding: "utf8" });
61033
+ branchHead = rebasedHeadStdout.trim();
61034
+ ctx.branchHead = branchHead;
61035
+ let changeImpact = ctx.changeImpact;
61036
+ try {
61037
+ changeImpact = await classifyChangedPackages(node.workspace, baseHead, branchHead);
61038
+ ctx.changeImpact = changeImpact;
61039
+ } catch {
61040
+ }
61041
+ recordMeshRefineStage(refineStages, "sync_base", "passed", syncStarted, {
61042
+ ahead: divergence.ahead,
61043
+ behind: divergence.behind,
61044
+ diverged: divergence.diverged,
61045
+ rebased: true,
61046
+ rebasedBranchHead: branchHead,
61047
+ ...changeImpact ? { changeImpact } : {}
61048
+ });
61049
+ recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", "passed", rebaseStarted, {
61050
+ rebasedBranchHead: branchHead,
61051
+ rebasedOnto: baseHead
61052
+ });
61053
+ return { kind: "continue", ctx };
61054
+ }
60764
61055
  async function refineValidationStage(self, ctx) {
60765
61056
  const { mesh, node, branch, baseBranch, refineStages } = ctx;
60766
61057
  const validationStarted = Date.now();
@@ -60785,7 +61076,7 @@ async function refineValidationStage(self, ctx) {
60785
61076
  { validationStatus: validationSummary.status, commandsRun: validationSummary.commandsRun.length }
60786
61077
  );
60787
61078
  if (validationSummary.status === "failed") {
60788
- const firstFailedCmd = Array.isArray(validationSummary.commandsRun) ? validationSummary.commandsRun.find((c) => c.success === false) : void 0;
61079
+ const firstFailedCmd = Array.isArray(validationSummary.commandsRun) ? validationSummary.commandsRun.find((c) => c.passed === false) : void 0;
60789
61080
  const buildValidationFailedError = () => {
60790
61081
  const base = validationSummary.failureCode === "missing_dependencies" ? "Refinery validation dependencies are missing for a change-affected package; merge/refine was not attempted. To make this self-service, either (1) configure .adhdev/worktree_bootstrap.json (or validation.bootstrapCommands in .adhdev/refine.json) so Refinery installs deps before validation, or (2) converge the branch via the documented manual fast-forward-only bypass (rebase onto the fetched base, verify strict ancestry, then push ff-only) instead of the refine gate." : validationSummary.failureCode === "dependency_bootstrap_failed" ? "Refinery dependency/bootstrap command failed; merge/refine was not attempted." : validationSummary.failureCode === "spawn_resolution_failed" ? validationSummary.spawnResolutionError || "Refinery validation command could not be spawned (executable not found); merge/refine was not attempted." : "Refinery validation gate failed; merge/refine was not attempted.";
60791
61082
  if (!firstFailedCmd) return base;
@@ -60841,10 +61132,10 @@ ${tail}` : ""
60841
61132
  return { kind: "continue", ctx };
60842
61133
  }
60843
61134
  async function refinePatchEquivalenceStage(self, ctx) {
60844
- const { meshId, nodeId, args, repoRoot, baseHead, node, branch, baseBranch, validationSummary, refineStages, execFileAsync: execFileAsync4 } = ctx;
60845
- let branchHead = ctx.branchHead;
61135
+ const { meshId, nodeId, args, repoRoot, baseHead, branch, baseBranch, validationSummary, refineStages } = ctx;
61136
+ const branchHead = ctx.branchHead;
60846
61137
  const patchEquivalenceStarted = Date.now();
60847
- let patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
61138
+ const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
60848
61139
  recordMeshRefineStage(refineStages, "patch_equivalence", patchEquivalence.status, patchEquivalenceStarted, {
60849
61140
  equivalent: patchEquivalence.equivalent,
60850
61141
  expectedPatchId: patchEquivalence.expectedPatchId,
@@ -60853,90 +61144,8 @@ async function refinePatchEquivalenceStage(self, ctx) {
60853
61144
  actionableHint: patchEquivalence.actionableHint
60854
61145
  });
60855
61146
  if (!patchEquivalence.equivalent) {
60856
- let didAutoRebase = false;
60857
- let isBehindBase = false;
60858
- try {
60859
- (0, import_node_child_process7.execFileSync)("git", ["merge-base", "--is-ancestor", branchHead, baseHead], {
60860
- cwd: node.workspace,
60861
- stdio: "ignore"
60862
- });
60863
- isBehindBase = true;
60864
- } catch {
60865
- }
60866
- if (isBehindBase) {
60867
- const autoRebaseStarted = Date.now();
60868
- try {
60869
- (0, import_node_child_process7.execFileSync)("git", ["rebase", baseHead], {
60870
- cwd: node.workspace,
60871
- stdio: ["ignore", "pipe", "pipe"]
60872
- });
60873
- const { stdout: rebasedHeadStdout } = await execFileAsync4("git", ["rev-parse", "HEAD"], { cwd: node.workspace, encoding: "utf8" });
60874
- branchHead = rebasedHeadStdout.trim();
60875
- const rebasedPatchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
60876
- recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", rebasedPatchEquivalence.status, autoRebaseStarted, {
60877
- equivalent: rebasedPatchEquivalence.equivalent,
60878
- expectedPatchId: rebasedPatchEquivalence.expectedPatchId,
60879
- actualPatchId: rebasedPatchEquivalence.actualPatchId,
60880
- error: rebasedPatchEquivalence.error,
60881
- rebasedBranchHead: branchHead
60882
- });
60883
- if (rebasedPatchEquivalence.equivalent) {
60884
- patchEquivalence = rebasedPatchEquivalence;
60885
- didAutoRebase = true;
60886
- } else {
60887
- return { kind: "terminal", result: {
60888
- success: false,
60889
- code: "needs_rebase",
60890
- convergenceStatus: "blocked_review",
60891
- error: "Branch was rebased onto base but patch equivalence still failed; manual intervention required.",
60892
- branch,
60893
- into: baseBranch,
60894
- validationSummary,
60895
- patchEquivalence: rebasedPatchEquivalence,
60896
- refineStages,
60897
- finalBranchConvergenceState: {
60898
- branch,
60899
- baseBranch,
60900
- merged: false,
60901
- removed: false,
60902
- validation: "passed",
60903
- patchEquivalence: "failed",
60904
- status: "blocked_review"
60905
- }
60906
- } };
60907
- }
60908
- } catch (rebaseErr) {
60909
- try {
60910
- (0, import_node_child_process7.execFileSync)("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
60911
- } catch {
60912
- }
60913
- recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", "failed", autoRebaseStarted, {
60914
- error: rebaseErr?.message || String(rebaseErr)
60915
- });
60916
- return { kind: "terminal", result: {
60917
- success: false,
60918
- code: "needs_rebase_with_conflicts",
60919
- convergenceStatus: "blocked_review",
60920
- error: "Branch is behind base and auto-rebase failed due to conflicts; resolve conflicts manually and retry.",
60921
- branch,
60922
- into: baseBranch,
60923
- validationSummary,
60924
- patchEquivalence,
60925
- refineStages,
60926
- finalBranchConvergenceState: {
60927
- branch,
60928
- baseBranch,
60929
- merged: false,
60930
- removed: false,
60931
- validation: "passed",
60932
- patchEquivalence: "failed",
60933
- status: "blocked_review"
60934
- }
60935
- } };
60936
- }
60937
- }
60938
61147
  const alreadyMergedViaOtherPath = !patchEquivalence.actualPatchId && !!patchEquivalence.expectedPatchId;
60939
- if (!didAutoRebase && !alreadyMergedViaOtherPath) {
61148
+ if (!alreadyMergedViaOtherPath) {
60940
61149
  return { kind: "terminal", result: {
60941
61150
  success: false,
60942
61151
  code: "patch_equivalence_failed",
@@ -60958,7 +61167,7 @@ async function refinePatchEquivalenceStage(self, ctx) {
60958
61167
  }
60959
61168
  } };
60960
61169
  }
60961
- if (!didAutoRebase && alreadyMergedViaOtherPath) {
61170
+ {
60962
61171
  recordMeshRefineStage(refineStages, "merge", "skipped", Date.now(), {
60963
61172
  reason: "already_merged_via_other_path",
60964
61173
  note: "actualPatchId is empty; branch content is already present in base via a different commit path"
@@ -61166,8 +61375,145 @@ ${hintLines.join("\n")}` : "",
61166
61375
  }
61167
61376
  return { kind: "continue", ctx };
61168
61377
  }
61378
+ async function requestCoordinatorLocalCatchup(self, params) {
61379
+ const { meshId, ctx, mesh, baseBranch, repoRoot } = params;
61380
+ const coordinatorDaemonId = typeof ctx.args?.coordinatorDaemonId === "string" && ctx.args.coordinatorDaemonId.trim() ? ctx.args.coordinatorDaemonId.trim() : self.deps.statusInstanceId || void 0;
61381
+ if (!coordinatorDaemonId) return void 0;
61382
+ if (!Array.isArray(mesh?.nodes)) return void 0;
61383
+ const coordinatorBaseNode = mesh.nodes.find((n) => !n?.isLocalWorktree && daemonIdListIncludes([coordinatorDaemonId], readStringValue(n?.daemonId)));
61384
+ if (!coordinatorBaseNode) return void 0;
61385
+ const coordinatorWorkspace = readStringValue(coordinatorBaseNode.repoRoot) || readStringValue(coordinatorBaseNode.workspace);
61386
+ if (!coordinatorWorkspace) return void 0;
61387
+ const drainIds = [self.deps.statusInstanceId].filter((v) => typeof v === "string" && v.length > 0);
61388
+ const selfIds = resolveCoordinatorSelfIds(mesh, drainIds);
61389
+ const coordinatorIsSelf = daemonIdListIncludes(selfIds, readStringValue(coordinatorBaseNode.daemonId));
61390
+ if (coordinatorIsSelf) {
61391
+ try {
61392
+ const ff = await fastForwardMeshNode({
61393
+ meshId,
61394
+ nodeId: readStringValue(coordinatorBaseNode.id),
61395
+ workspace: coordinatorWorkspace,
61396
+ branch: baseBranch,
61397
+ mode: "merge",
61398
+ execute: true,
61399
+ trigger: "refine_post_push_catchup",
61400
+ allowAutoPublishSubmoduleMainCommits: mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true
61401
+ });
61402
+ return {
61403
+ mode: "local_fast_forward",
61404
+ coordinatorWorkspace,
61405
+ sameAsRepoRoot: coordinatorWorkspace === repoRoot,
61406
+ code: ff.code,
61407
+ executed: ff.executed,
61408
+ success: ff.success,
61409
+ ...ff.blockingReasons?.length ? { blockingReasons: ff.blockingReasons } : {}
61410
+ };
61411
+ } catch (e) {
61412
+ return { mode: "local_fast_forward", coordinatorWorkspace, error: e?.message || String(e) };
61413
+ }
61414
+ }
61415
+ try {
61416
+ queuePendingMeshCoordinatorEvent({
61417
+ event: "coordinator_catchup",
61418
+ meshId,
61419
+ nodeLabel: readStringValue(coordinatorBaseNode.id) || "coordinator-base",
61420
+ nodeId: readStringValue(coordinatorBaseNode.id),
61421
+ workspace: coordinatorWorkspace,
61422
+ metadataEvent: {
61423
+ source: "refine_post_push_coordinator_catchup",
61424
+ operation: "coordinator_catchup",
61425
+ baseBranch,
61426
+ coordinatorDaemonId,
61427
+ reason: "post_push_base_advanced"
61428
+ },
61429
+ queuedAt: Date.now(),
61430
+ targetCoordinatorDaemonId: coordinatorDaemonId
61431
+ });
61432
+ return { mode: "pending_marker_queued", coordinatorDaemonId, coordinatorWorkspace, baseBranch };
61433
+ } catch (e) {
61434
+ return { mode: "pending_marker_queued", coordinatorDaemonId, error: e?.message || String(e) };
61435
+ }
61436
+ }
61169
61437
  async function refineMergeAndFinalizeStage(self, ctx) {
61170
61438
  const { meshId, nodeId, args, repoRoot, baseHead, node, branch, baseBranch, sourceNode, validationSummary, patchEquivalence, submoduleReachability, mesh, refineStages, execFileAsync: execFileAsync4 } = ctx;
61439
+ const leaseKey = `${repoRoot}::${baseBranch}`;
61440
+ const leaseHolder = buildRefineJobKey(self, meshId, nodeId);
61441
+ if (self.refineBaseLeases.has(leaseKey) && self.refineBaseLeases.get(leaseKey) !== leaseHolder) {
61442
+ recordMeshRefineStage(refineStages, "base_lease", "skipped", Date.now(), {
61443
+ leaseKey,
61444
+ heldBy: self.refineBaseLeases.get(leaseKey),
61445
+ retryable: true
61446
+ });
61447
+ return { kind: "terminal", result: {
61448
+ success: false,
61449
+ code: "base_locked",
61450
+ convergenceStatus: "blocked_review",
61451
+ retryable: true,
61452
+ error: `Another refine holds the base lease for ${baseBranch} in this repo; retry after it completes.`,
61453
+ branch,
61454
+ into: baseBranch,
61455
+ validationSummary,
61456
+ patchEquivalence,
61457
+ submoduleReachability,
61458
+ refineStages,
61459
+ finalBranchConvergenceState: {
61460
+ branch,
61461
+ baseBranch,
61462
+ merged: false,
61463
+ removed: false,
61464
+ status: "blocked_review"
61465
+ }
61466
+ } };
61467
+ }
61468
+ self.refineBaseLeases.set(leaseKey, leaseHolder);
61469
+ try {
61470
+ return await runRefineMergeAndFinalizeLocked(self, ctx);
61471
+ } finally {
61472
+ if (self.refineBaseLeases.get(leaseKey) === leaseHolder) self.refineBaseLeases.delete(leaseKey);
61473
+ }
61474
+ }
61475
+ async function runRefineMergeAndFinalizeLocked(self, ctx) {
61476
+ const { meshId, nodeId, args, repoRoot, baseHead, node, branch, baseBranch, sourceNode, validationSummary, patchEquivalence, submoduleReachability, mesh, refineStages, execFileAsync: execFileAsync4 } = ctx;
61477
+ const casStarted = Date.now();
61478
+ let baseMoved = false;
61479
+ let liveBaseHead;
61480
+ try {
61481
+ await execFileAsync4("git", ["fetch", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
61482
+ const { stdout } = await execFileAsync4("git", ["rev-parse", `origin/${baseBranch}`], { cwd: repoRoot, encoding: "utf8" });
61483
+ liveBaseHead = stdout.trim();
61484
+ baseMoved = !!liveBaseHead && liveBaseHead !== baseHead;
61485
+ } catch {
61486
+ }
61487
+ if (baseMoved) {
61488
+ recordMeshRefineStage(refineStages, "base_cas", "failed", casStarted, {
61489
+ pinnedBaseHead: baseHead,
61490
+ liveBaseHead,
61491
+ retryable: true
61492
+ });
61493
+ return { kind: "terminal", result: {
61494
+ success: false,
61495
+ code: "base_moved",
61496
+ convergenceStatus: "blocked_review",
61497
+ retryable: true,
61498
+ error: `Base ${baseBranch} advanced from ${baseHead.slice(0, 7)} to ${(liveBaseHead || "").slice(0, 7)} after this node was validated; re-run refine to rebase onto and re-validate the new base.`,
61499
+ branch,
61500
+ into: baseBranch,
61501
+ pinnedBaseHead: baseHead,
61502
+ liveBaseHead,
61503
+ validationSummary,
61504
+ patchEquivalence,
61505
+ submoduleReachability,
61506
+ refineStages,
61507
+ finalBranchConvergenceState: {
61508
+ branch,
61509
+ baseBranch,
61510
+ merged: false,
61511
+ removed: false,
61512
+ status: "blocked_review"
61513
+ }
61514
+ } };
61515
+ }
61516
+ recordMeshRefineStage(refineStages, "base_cas", "passed", casStarted, { pinnedBaseHead: baseHead });
61171
61517
  let mergeResult;
61172
61518
  const mergeStarted = Date.now();
61173
61519
  try {
@@ -61179,16 +61525,33 @@ async function refineMergeAndFinalizeStage(self, ctx) {
61179
61525
  };
61180
61526
  recordMeshRefineStage(refineStages, "merge", "passed", mergeStarted, mergeResult);
61181
61527
  } catch (e) {
61528
+ const mergeOutput = `${e?.stdout || ""}
61529
+ ${e?.stderr || ""}`;
61530
+ const conflictPaths = [...mergeOutput.matchAll(/Merge conflict in (.+)/g)].map((m) => m[1].trim()).filter(Boolean);
61531
+ try {
61532
+ await execFileAsync4("git", ["merge", "--abort"], { cwd: repoRoot, encoding: "utf8" });
61533
+ } catch {
61534
+ }
61182
61535
  recordMeshRefineStage(refineStages, "merge", "failed", mergeStarted, {
61183
61536
  error: e?.message || String(e),
61184
61537
  stdout: truncateValidationOutput(e?.stdout),
61185
- stderr: truncateValidationOutput(e?.stderr)
61538
+ stderr: truncateValidationOutput(e?.stderr),
61539
+ ...conflictPaths.length ? { conflictPaths } : {}
61186
61540
  });
61187
61541
  return { kind: "terminal", result: {
61188
61542
  success: false,
61189
- error: `Merge failed (conflicts?): ${e.message}`,
61543
+ code: "merge_failed",
61544
+ convergenceStatus: "not_mergeable",
61545
+ error: conflictPaths.length ? `Merge failed \u2014 conflicts in ${conflictPaths.length} path(s): ${conflictPaths.join(", ")}. The branch cannot fast-forward-merge onto ${baseBranch}; resolve conflicts (rebase the branch onto the fetched base) and retry.` : `Merge failed (conflicts?): ${e?.message || String(e)}`,
61546
+ branch,
61547
+ into: baseBranch,
61548
+ ...conflictPaths.length ? { conflictPaths } : {},
61190
61549
  validationSummary,
61191
61550
  patchEquivalence,
61551
+ mergeResult: {
61552
+ stdout: truncateValidationOutput(e?.stdout),
61553
+ stderr: truncateValidationOutput(e?.stderr)
61554
+ },
61192
61555
  refineStages,
61193
61556
  finalBranchConvergenceState: {
61194
61557
  branch,
@@ -61244,6 +61607,93 @@ async function refineMergeAndFinalizeStage(self, ctx) {
61244
61607
  }
61245
61608
  } };
61246
61609
  }
61610
+ const requireApprovalForPush = mesh?.policy?.requireApprovalForPush ?? DEFAULT_MESH_POLICY.requireApprovalForPush;
61611
+ let pushResult;
61612
+ if (!requireApprovalForPush) {
61613
+ const pushStarted = Date.now();
61614
+ try {
61615
+ await execFileAsync4("git", ["push", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
61616
+ pushResult = { pushed: true, remote: "origin", branch: baseBranch, durationMs: Date.now() - pushStarted };
61617
+ recordMeshRefineStage(refineStages, "push", "passed", pushStarted, pushResult);
61618
+ } catch (e) {
61619
+ pushResult = {
61620
+ pushed: false,
61621
+ remote: "origin",
61622
+ branch: baseBranch,
61623
+ error: e?.message || String(e),
61624
+ stderr: e?.stderr,
61625
+ durationMs: Date.now() - pushStarted
61626
+ };
61627
+ recordMeshRefineStage(refineStages, "push", "failed", pushStarted, pushResult);
61628
+ return { kind: "terminal", result: {
61629
+ success: false,
61630
+ code: "push_failed",
61631
+ convergenceStatus: "blocked_review",
61632
+ retryable: true,
61633
+ merged: true,
61634
+ mergedLocal: true,
61635
+ pushed: false,
61636
+ error: `Refinery merged '${branch}' into local ${baseBranch} but the push to origin failed; the worktree and branch ref were preserved (NOT cleaned up) so the push can be retried. Run: git -C ${repoRoot} push origin ${baseBranch}`,
61637
+ branch,
61638
+ into: baseBranch,
61639
+ pushResult,
61640
+ pushCommand: `git push origin ${baseBranch}`,
61641
+ validationSummary,
61642
+ patchEquivalence,
61643
+ submoduleReachability,
61644
+ submoduleAlignment,
61645
+ mergeResult,
61646
+ refineStages,
61647
+ finalBranchConvergenceState: {
61648
+ branch: baseBranch,
61649
+ mergedBranch: branch,
61650
+ baseBranch,
61651
+ merged: true,
61652
+ pushed: false,
61653
+ removed: false,
61654
+ validation: "passed",
61655
+ patchEquivalence: "passed",
61656
+ submoduleAlignment: submoduleAlignment.status,
61657
+ status: "merged_push_failed",
61658
+ nextStep: `Retry the push (git -C ${repoRoot} push origin ${baseBranch}); then the worktree can be cleaned up.`
61659
+ }
61660
+ } };
61661
+ }
61662
+ } else {
61663
+ recordMeshRefineStage(refineStages, "push", "skipped", Date.now(), {
61664
+ reason: "require_approval_for_push"
61665
+ });
61666
+ return { kind: "terminal", result: {
61667
+ success: true,
61668
+ merged: true,
61669
+ mergedLocal: true,
61670
+ pushed: false,
61671
+ branch,
61672
+ into: baseBranch,
61673
+ validationSummary,
61674
+ patchEquivalence,
61675
+ submoduleReachability,
61676
+ submoduleAlignment,
61677
+ mergeResult,
61678
+ refineStages,
61679
+ pushReady: true,
61680
+ pushCommand: `git push origin ${baseBranch}`,
61681
+ pushNote: "requireApprovalForPush is enabled \u2014 the merge landed on the local base but was NOT pushed and the worktree was NOT cleaned up. Run the push (or approve it), then re-run refine/cleanup to remove the worktree.",
61682
+ finalBranchConvergenceState: {
61683
+ branch: baseBranch,
61684
+ mergedBranch: branch,
61685
+ baseBranch,
61686
+ merged: true,
61687
+ pushed: false,
61688
+ removed: false,
61689
+ validation: "passed",
61690
+ patchEquivalence: "passed",
61691
+ submoduleAlignment: submoduleAlignment.status,
61692
+ status: "merged_local_pending_push",
61693
+ nextStep: `Approve and run the push (git -C ${repoRoot} push origin ${baseBranch}); the worktree is retained until then.`
61694
+ }
61695
+ } };
61696
+ }
61247
61697
  const cleanupStarted = Date.now();
61248
61698
  const refineSessionCleanupMode = self.normalizeMeshSessionCleanupMode(
61249
61699
  mesh?.policy?.sessionCleanupOnNodeRemove
@@ -61271,10 +61721,10 @@ async function refineMergeAndFinalizeStage(self, ctx) {
61271
61721
  sessionCleanupMode: refineSessionCleanupMode,
61272
61722
  ...refineSessionIds && refineSessionIds.length > 0 ? { sessionIds: refineSessionIds } : {},
61273
61723
  inlineMesh: args?.inlineMesh,
61274
- // REFINE-CLEANUP: refine reaches cleanup only AFTER a verified merge
61275
- // convergence, so any residual worktree dirtiness here is incidental
61276
- // (e.g. a bootstrap lockfile rewrite) — never unmerged work. `force`
61277
- // sets requireClean=false so a plain-dirty worktree no longer aborts
61724
+ // REFINE-CLEANUP: refine reaches cleanup only AFTER a verified merge AND a
61725
+ // successful push (DS1), so any residual worktree dirtiness here is
61726
+ // incidental (e.g. a bootstrap lockfile rewrite) — never unmerged work.
61727
+ // `force` sets requireClean=false so a plain-dirty worktree no longer aborts
61278
61728
  // removal with merged_cleanup_failed. Branch-ref deletion still keys off
61279
61729
  // mergeConvergence (NOT the force flag), so no merged work can be lost.
61280
61730
  force: true
@@ -61291,7 +61741,7 @@ async function refineMergeAndFinalizeStage(self, ctx) {
61291
61741
  appendLedgerEntry2(meshId, {
61292
61742
  kind: "node_removed",
61293
61743
  nodeId,
61294
- payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence, submoduleReachability, submoduleAlignment }
61744
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, pushed: true, validationSummary, patchEquivalence, submoduleReachability, submoduleAlignment }
61295
61745
  });
61296
61746
  recordMeshRefineStage(refineStages, "ledger", "passed", ledgerStarted);
61297
61747
  } catch (e) {
@@ -61303,21 +61753,24 @@ async function refineMergeAndFinalizeStage(self, ctx) {
61303
61753
  mergedBranch: branch,
61304
61754
  baseBranch,
61305
61755
  merged: true,
61756
+ pushed: true,
61306
61757
  removed: removeResult?.success !== false,
61307
61758
  validation: "passed",
61308
61759
  patchEquivalence: "passed",
61309
61760
  submoduleAlignment: submoduleAlignment.status,
61310
- status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
61761
+ status: removeResult?.success === false ? "merged_cleanup_failed" : "merged_pushed"
61311
61762
  };
61312
61763
  if (removeResult?.success === false) {
61313
61764
  return { kind: "terminal", result: {
61314
61765
  success: false,
61315
61766
  code: "cleanup_failed",
61316
- error: "Refinery merge completed but worktree cleanup failed; manual cleanup/retry is required.",
61767
+ error: "Refinery merge + push completed but worktree cleanup failed; the change is on origin \u2014 manual worktree cleanup/retry is required.",
61317
61768
  merged: true,
61769
+ pushed: true,
61318
61770
  branch,
61319
61771
  into: baseBranch,
61320
61772
  removeResult,
61773
+ pushResult,
61321
61774
  validationSummary,
61322
61775
  patchEquivalence,
61323
61776
  submoduleReachability,
@@ -61328,33 +61781,35 @@ async function refineMergeAndFinalizeStage(self, ctx) {
61328
61781
  finalBranchConvergenceState
61329
61782
  } };
61330
61783
  }
61331
- const requireApprovalForPush = mesh?.policy?.requireApprovalForPush ?? DEFAULT_MESH_POLICY.requireApprovalForPush;
61332
- let pushResult;
61333
- if (!requireApprovalForPush) {
61334
- const pushStarted = Date.now();
61335
- try {
61336
- await execFileAsync4("git", ["push", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
61337
- pushResult = { pushed: true, remote: "origin", branch: baseBranch, durationMs: Date.now() - pushStarted };
61338
- recordMeshRefineStage(refineStages, "push", "passed", pushStarted, pushResult);
61339
- finalBranchConvergenceState.status = "merged_pushed";
61340
- } catch (e) {
61341
- pushResult = {
61342
- pushed: false,
61343
- remote: "origin",
61344
- branch: baseBranch,
61345
- error: e?.message || String(e),
61346
- stderr: e?.stderr,
61347
- durationMs: Date.now() - pushStarted
61348
- };
61349
- recordMeshRefineStage(refineStages, "push", "failed", pushStarted, pushResult);
61784
+ let coordinatorCatchup;
61785
+ try {
61786
+ coordinatorCatchup = await requestCoordinatorLocalCatchup(self, {
61787
+ meshId,
61788
+ ctx,
61789
+ mesh,
61790
+ baseBranch,
61791
+ repoRoot
61792
+ });
61793
+ if (coordinatorCatchup) {
61794
+ recordMeshRefineStage(refineStages, "coordinator_catchup", "passed", Date.now(), coordinatorCatchup);
61350
61795
  }
61796
+ } catch {
61351
61797
  }
61798
+ const cleanupBranchRefWarning = typeof removeResult?.branchRefWarning === "string" ? removeResult.branchRefWarning : void 0;
61799
+ const cleanupResidueWarning = typeof removeResult?.residueWarning === "string" ? removeResult.residueWarning : void 0;
61800
+ const cleanupBranchRefDeleted = typeof removeResult?.worktreeCleanup?.branchRefDeleted === "boolean" ? removeResult.worktreeCleanup.branchRefDeleted : void 0;
61352
61801
  return { kind: "terminal", result: {
61353
61802
  success: true,
61354
61803
  merged: true,
61804
+ pushed: true,
61355
61805
  branch,
61356
61806
  into: baseBranch,
61357
61807
  removeResult,
61808
+ pushResult,
61809
+ ...coordinatorCatchup ? { coordinatorCatchup } : {},
61810
+ ...cleanupBranchRefWarning ? { branchRefWarning: cleanupBranchRefWarning } : {},
61811
+ ...cleanupResidueWarning ? { residueWarning: cleanupResidueWarning } : {},
61812
+ ...cleanupBranchRefDeleted !== void 0 ? { branchRefDeleted: cleanupBranchRefDeleted } : {},
61358
61813
  validationSummary,
61359
61814
  patchEquivalence,
61360
61815
  submoduleReachability,
@@ -61362,13 +61817,7 @@ async function refineMergeAndFinalizeStage(self, ctx) {
61362
61817
  mergeResult,
61363
61818
  refineStages,
61364
61819
  ...ledgerError ? { ledgerError } : {},
61365
- finalBranchConvergenceState,
61366
- // Push outcome or readiness info for coordinator.
61367
- ...pushResult ? { pushResult } : {
61368
- pushReady: true,
61369
- pushCommand: `git push origin ${baseBranch}`,
61370
- pushNote: "requireApprovalForPush is enabled \u2014 run the push command or obtain user approval before pushing."
61371
- }
61820
+ finalBranchConvergenceState
61372
61821
  } };
61373
61822
  }
61374
61823
  async function batchRefineMeshNodes(self, meshId, requestedNodeIds, args) {
@@ -61523,29 +61972,34 @@ async function batchRefineMeshNodes(self, meshId, requestedNodeIds, args) {
61523
61972
  }
61524
61973
  return runMeshRefineBatchConvergence(self, meshId, orderedNodes, ordering, args);
61525
61974
  }
61975
+ var RETRYABLE_BASE_MOVEMENT_CODES = /* @__PURE__ */ new Set(["base_moved", "base_locked"]);
61976
+ function classifyBatchNodeConvergence(result) {
61977
+ const code = typeof result.code === "string" ? result.code : "";
61978
+ const stage = Array.isArray(result.refineStages) ? result.refineStages.filter((s2) => s2.status === "failed").map((s2) => s2.stage).filter(Boolean).pop() : void 0;
61979
+ let convergence;
61980
+ if (code === "already_merged" && result.alreadyMergedViaOtherPath) {
61981
+ convergence = "skipped_patch_equivalent";
61982
+ } else if (result.success === true) {
61983
+ convergence = "merged_to_main";
61984
+ } else if (code === "merge_failed" || stage === "merge") {
61985
+ convergence = "not_mergeable";
61986
+ } else {
61987
+ convergence = "blocked_review";
61988
+ }
61989
+ const retryable = convergence === "blocked_review" && (result.retryable === true || RETRYABLE_BASE_MOVEMENT_CODES.has(code));
61990
+ return { convergence, code, retryable, ...stage ? { stage } : {} };
61991
+ }
61526
61992
  async function runMeshRefineBatchConvergence(self, meshId, orderedNodes, ordering, args) {
61527
- const results = [];
61528
- for (const node of orderedNodes) {
61993
+ const refineOne = async (node) => {
61529
61994
  let result;
61530
61995
  try {
61531
61996
  result = await executeMeshRefineNodeSynchronously(self, meshId, node.id, args);
61532
61997
  } catch (e) {
61533
61998
  result = { success: false, error: e?.message || String(e) };
61534
61999
  }
61535
- const code = typeof result.code === "string" ? result.code : "";
61536
- let convergence;
61537
- if (code === "already_merged" && result.alreadyMergedViaOtherPath) {
61538
- convergence = "skipped_patch_equivalent";
61539
- } else if (result.success === true) {
61540
- convergence = "merged_to_main";
61541
- } else if (code === "merge_failed") {
61542
- convergence = "not_mergeable";
61543
- } else {
61544
- convergence = "blocked_review";
61545
- }
62000
+ const { convergence, code, stage, retryable } = classifyBatchNodeConvergence(result);
61546
62001
  const fbcs = result.finalBranchConvergenceState && typeof result.finalBranchConvergenceState === "object" ? result.finalBranchConvergenceState : void 0;
61547
- const stage = Array.isArray(result.refineStages) ? result.refineStages.filter((s2) => s2.status === "failed").map((s2) => s2.stage).filter(Boolean).pop() : void 0;
61548
- results.push({
62002
+ return {
61549
62003
  nodeId: node.id,
61550
62004
  workspace: node.workspace,
61551
62005
  convergence,
@@ -61553,14 +62007,30 @@ async function runMeshRefineBatchConvergence(self, meshId, orderedNodes, orderin
61553
62007
  ...typeof result.blockedReason === "string" ? { reason: result.blockedReason } : {},
61554
62008
  ...stage ? { stage } : {},
61555
62009
  ...typeof result.error === "string" ? { error: result.error } : {},
62010
+ ...retryable ? { retryable: true } : {},
61556
62011
  ...fbcs ? { finalBranchConvergenceState: fbcs } : {}
61557
- });
62012
+ };
62013
+ };
62014
+ const results = [];
62015
+ const retryQueue = [];
62016
+ for (const node of orderedNodes) {
62017
+ const outcome = await refineOne(node);
62018
+ results.push(outcome);
62019
+ if (outcome.retryable) retryQueue.push(node);
62020
+ }
62021
+ for (const node of retryQueue) {
62022
+ const idx = results.findIndex((r) => r.nodeId === node.id);
62023
+ const retried = await refineOne(node);
62024
+ retried.retried = true;
62025
+ if (idx >= 0) results[idx] = retried;
62026
+ else results.push(retried);
61558
62027
  }
61559
62028
  const summary = {
61560
62029
  merged: results.filter((r) => r.convergence === "merged_to_main").length,
61561
62030
  skipped: results.filter((r) => r.convergence === "skipped_patch_equivalent").length,
61562
62031
  blocked: results.filter((r) => r.convergence === "blocked_review").length,
61563
- notMergeable: results.filter((r) => r.convergence === "not_mergeable").length
62032
+ notMergeable: results.filter((r) => r.convergence === "not_mergeable").length,
62033
+ ...retryQueue.length ? { retried: retryQueue.length } : {}
61564
62034
  };
61565
62035
  const allConverged = summary.blocked === 0 && summary.notMergeable === 0;
61566
62036
  return {
@@ -61773,7 +62243,7 @@ async function finishMeshRefineJob(self, handle, args) {
61773
62243
  }
61774
62244
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
61775
62245
  const refineCode = typeof result.code === "string" ? result.code : "";
61776
- const refineTerminalKind = result.success === true ? "completed" : refineCode === "blocked_review" ? "blocked_review" : refineCode === "validation_failed" || refineCode === "validation_dependencies_missing" ? "validation_failed" : refineCode === "submodule_reachability_failed" ? "submodule_reachability_failed" : refineCode === "merge_failed" || refineCode === "patch_equivalence_failed" || refineCode === "needs_rebase" || refineCode === "needs_rebase_with_conflicts" ? "merge_failed" : refineCode === "cleanup_failed" ? "cleanup_failed" : "merge_failed";
62246
+ const refineTerminalKind = result.success === true ? "completed" : refineCode === "blocked_review" ? "blocked_review" : refineCode === "validation_failed" || refineCode === "validation_dependencies_missing" || refineCode === "missing_dependencies" || refineCode === "dependency_bootstrap_failed" || refineCode === "spawn_resolution_failed" || refineCode === "validation_unavailable" ? "validation_failed" : refineCode === "submodule_reachability_failed" ? "submodule_reachability_failed" : refineCode === "merge_failed" || refineCode === "patch_equivalence_failed" || refineCode === "needs_rebase" || refineCode === "needs_rebase_with_conflicts" ? "merge_failed" : refineCode === "cleanup_failed" ? "cleanup_failed" : "merge_failed";
61777
62247
  const isTerminalSuccess = refineTerminalKind === "completed";
61778
62248
  const blockerContext = isTerminalSuccess ? void 0 : (() => {
61779
62249
  const code = typeof result.code === "string" ? result.code : refineTerminalKind;
@@ -61804,9 +62274,12 @@ async function finishMeshRefineJob(self, handle, args) {
61804
62274
  }
61805
62275
  if (stage === "validation" && result.validationSummary) {
61806
62276
  const vs = result.validationSummary;
62277
+ const diagnostics = extractValidationFailureDiagnostics(vs);
61807
62278
  ctx.details = {
61808
62279
  failureCode: vs.failureCode,
61809
- commandsRun: Array.isArray(vs.commandsRun) ? vs.commandsRun.length : void 0
62280
+ failureKind: vs.failureKind,
62281
+ commandsRun: Array.isArray(vs.commandsRun) ? vs.commandsRun.length : void 0,
62282
+ ...diagnostics ? { failure: diagnostics } : {}
61810
62283
  };
61811
62284
  }
61812
62285
  return ctx;
@@ -62841,6 +63314,15 @@ var DaemonCommandRouter = class {
62841
63314
  runningRefineBatchJobs = /* @__PURE__ */ new Map();
62842
63315
  /** Terminal async batch Refinery jobs preserve the last batch outcome for late readers. */
62843
63316
  terminalRefineBatchJobs = /* @__PURE__ */ new Map();
63317
+ /**
63318
+ * DS2: in-process refinement leases keyed by `${repoRoot}::${baseBranch}`. Serialize
63319
+ * the base-mutating window (candidate-SHA pin → merge → push) of concurrent single-node
63320
+ * refines that target the SAME base branch in the same repo, so two refines cannot both
63321
+ * validate against one baseHead and then race their merges (the base-movement race). The
63322
+ * batch path is already sequential, so this only matters for overlapping single-node
63323
+ * async jobs. Value = the meshId:nodeId job key holding the lease (for diagnostics).
63324
+ */
63325
+ refineBaseLeases = /* @__PURE__ */ new Map();
62844
63326
  constructor(deps) {
62845
63327
  this.deps = deps;
62846
63328
  }