@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/commands/router-refine.d.ts +86 -5
- package/dist/commands/router.d.ts +9 -0
- package/dist/git/git-status.d.ts +14 -1
- package/dist/index.js +641 -159
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +641 -159
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-queue-assignment.d.ts +13 -0
- package/dist/mesh/mesh-refine-gates.d.ts +5 -0
- package/dist/mesh/refine-config.d.ts +36 -0
- package/package.json +3 -3
- package/src/commands/router-refine.ts +756 -164
- package/src/commands/router.ts +9 -0
- package/src/git/git-status.ts +38 -6
- package/src/mesh/mesh-queue-assignment.ts +64 -2
- package/src/mesh/mesh-reconcile-loop.ts +18 -1
- package/src/mesh/mesh-refine-gates.ts +49 -1
- package/src/mesh/refine-config.ts +44 -0
package/dist/index.mjs
CHANGED
|
@@ -414,10 +414,10 @@ function readInjected(value) {
|
|
|
414
414
|
}
|
|
415
415
|
function getDaemonBuildInfo() {
|
|
416
416
|
if (cached) return cached;
|
|
417
|
-
const commit = readInjected(true ? "
|
|
418
|
-
const commitShort = readInjected(true ? "
|
|
419
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
420
|
-
const builtAt = readInjected(true ? "2026-07-
|
|
417
|
+
const commit = readInjected(true ? "91ed5e0752d70b62ebcc2cddcea4d502756cff0a" : void 0) ?? "unknown";
|
|
418
|
+
const commitShort = readInjected(true ? "91ed5e07" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
419
|
+
const version = readInjected(true ? "0.9.82-rc.524" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
420
|
+
const builtAt = readInjected(true ? "2026-07-14T07:23:02.573Z" : void 0);
|
|
421
421
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
422
422
|
return cached;
|
|
423
423
|
}
|
|
@@ -790,6 +790,9 @@ function isNonRuntimeRootFile(file, policy) {
|
|
|
790
790
|
}
|
|
791
791
|
return false;
|
|
792
792
|
}
|
|
793
|
+
function deriveChangeArea(isDaemonAffecting, affectedPackages) {
|
|
794
|
+
return isDaemonAffecting ? "daemon" : affectedPackages.length > 0 ? "web" : "none";
|
|
795
|
+
}
|
|
793
796
|
function classifyChangedFileList(files, policy) {
|
|
794
797
|
if (files.length === 0) {
|
|
795
798
|
return { isDaemonAffecting: true, affectedPackages: [], ambiguousNonPackageFiles: [] };
|
|
@@ -812,9 +815,10 @@ async function classifyDaemonBuildChange(repoPath, buildCommit, options, policy)
|
|
|
812
815
|
try {
|
|
813
816
|
const diff = await runGit(repoPath, ["diff", "--name-only", `${buildCommit}..HEAD`], options);
|
|
814
817
|
const files = diff.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
815
|
-
|
|
818
|
+
const { isDaemonAffecting, affectedPackages } = classifyChangedFileList(files, policy);
|
|
819
|
+
return { isDaemonAffecting, affectedPackages, changeArea: deriveChangeArea(isDaemonAffecting, affectedPackages) };
|
|
816
820
|
} catch {
|
|
817
|
-
return { isDaemonAffecting: true, affectedPackages: [] };
|
|
821
|
+
return { isDaemonAffecting: true, affectedPackages: [], changeArea: "daemon" };
|
|
818
822
|
}
|
|
819
823
|
}
|
|
820
824
|
async function classifyChangedPackages(repoPath, fromRef, toRef, options = {}) {
|
|
@@ -827,7 +831,7 @@ async function classifyChangedPackages(repoPath, fromRef, toRef, options = {}) {
|
|
|
827
831
|
return refineVerdictThroughSubmodules(repoPath, fromRef, toRef, options, policy, rootVerdict);
|
|
828
832
|
}
|
|
829
833
|
async function refineVerdictThroughSubmodules(repoPath, fromRef, toRef, options, policy, rootVerdict) {
|
|
830
|
-
const strip = ({ isDaemonAffecting, affectedPackages }) => ({ isDaemonAffecting, affectedPackages });
|
|
834
|
+
const strip = ({ isDaemonAffecting, affectedPackages: affectedPackages2 }) => ({ isDaemonAffecting, affectedPackages: affectedPackages2, changeArea: deriveChangeArea(isDaemonAffecting, affectedPackages2) });
|
|
831
835
|
const ambiguous = rootVerdict.ambiguousNonPackageFiles;
|
|
832
836
|
if (ambiguous.length === 0) return strip(rootVerdict);
|
|
833
837
|
let submodulePaths;
|
|
@@ -859,9 +863,11 @@ async function refineVerdictThroughSubmodules(repoPath, fromRef, toRef, options,
|
|
|
859
863
|
return strip(rootVerdict);
|
|
860
864
|
}
|
|
861
865
|
if (subVerdict.isDaemonAffecting) {
|
|
866
|
+
const affectedPackages2 = [.../* @__PURE__ */ new Set([...rootVerdict.affectedPackages, ...subVerdict.affectedPackages])].sort();
|
|
862
867
|
return {
|
|
863
868
|
isDaemonAffecting: true,
|
|
864
|
-
affectedPackages:
|
|
869
|
+
affectedPackages: affectedPackages2,
|
|
870
|
+
changeArea: deriveChangeArea(true, affectedPackages2)
|
|
865
871
|
};
|
|
866
872
|
}
|
|
867
873
|
submoduleAffectedPackages.push(...subVerdict.affectedPackages);
|
|
@@ -869,9 +875,11 @@ async function refineVerdictThroughSubmodules(repoPath, fromRef, toRef, options,
|
|
|
869
875
|
const rootPackagesBenign = rootVerdict.affectedPackages.every(
|
|
870
876
|
(p) => policy.webOnlyPackages.has(p) && !policy.daemonRuntimePackages.has(p)
|
|
871
877
|
);
|
|
878
|
+
const affectedPackages = [.../* @__PURE__ */ new Set([...rootVerdict.affectedPackages, ...submoduleAffectedPackages])].sort();
|
|
872
879
|
return {
|
|
873
880
|
isDaemonAffecting: !rootPackagesBenign,
|
|
874
|
-
affectedPackages
|
|
881
|
+
affectedPackages,
|
|
882
|
+
changeArea: deriveChangeArea(!rootPackagesBenign, affectedPackages)
|
|
875
883
|
};
|
|
876
884
|
}
|
|
877
885
|
async function listSubmodulePaths(repoPath, options) {
|
|
@@ -10749,6 +10757,14 @@ function normalizeMeshCommandConfig(entry, source) {
|
|
|
10749
10757
|
if (entry.env !== void 0 && (!isMeshConfigRecord(entry.env) || !Object.values(entry.env).every((value) => typeof value === "string"))) {
|
|
10750
10758
|
return { rejected: { source, command: commandText, reason: "env must be an object of string values" } };
|
|
10751
10759
|
}
|
|
10760
|
+
let scopes;
|
|
10761
|
+
if (entry.scopes !== void 0) {
|
|
10762
|
+
if (!Array.isArray(entry.scopes) || !entry.scopes.every((s2) => MESH_REFINE_VALIDATION_SCOPES.includes(s2))) {
|
|
10763
|
+
return { rejected: { source, command: commandText, reason: `scopes must be an array of ${MESH_REFINE_VALIDATION_SCOPES.join(" | ")}` } };
|
|
10764
|
+
}
|
|
10765
|
+
const deduped = [...new Set(entry.scopes)];
|
|
10766
|
+
scopes = deduped.length ? deduped : void 0;
|
|
10767
|
+
}
|
|
10752
10768
|
return {
|
|
10753
10769
|
command: {
|
|
10754
10770
|
command,
|
|
@@ -10759,7 +10775,8 @@ function normalizeMeshCommandConfig(entry, source) {
|
|
|
10759
10775
|
...typeof entry.cwd === "string" && entry.cwd.trim() ? { cwd: entry.cwd.trim() } : {},
|
|
10760
10776
|
...typeof entry.timeoutMs === "number" ? { timeoutMs: entry.timeoutMs } : {},
|
|
10761
10777
|
...typeof entry.outputLimitBytes === "number" ? { outputLimitBytes: entry.outputLimitBytes } : {},
|
|
10762
|
-
...isMeshConfigRecord(entry.env) ? { env: entry.env } : {}
|
|
10778
|
+
...isMeshConfigRecord(entry.env) ? { env: entry.env } : {},
|
|
10779
|
+
...scopes ? { scopes } : {}
|
|
10763
10780
|
}
|
|
10764
10781
|
};
|
|
10765
10782
|
}
|
|
@@ -10916,11 +10933,12 @@ function resolveMeshRefineValidationPlan(mesh, workspace) {
|
|
|
10916
10933
|
unavailableReason: validation.commands.length ? void 0 : "validation_unavailable: repo mesh/refine config has no validation.commands"
|
|
10917
10934
|
};
|
|
10918
10935
|
}
|
|
10919
|
-
var MESH_REFINE_VALIDATION_CATEGORIES, MESH_REFINE_CONFIG_LOCATIONS, MESH_REFINE_CONFIG_SCHEMA, SHELL_METACHAR_RE, SAFE_TOKEN_RE, SAFE_WIN32_EXEC_TOKEN_RE, isRecord2;
|
|
10936
|
+
var 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;
|
|
10920
10937
|
var init_refine_config = __esm({
|
|
10921
10938
|
"src/mesh/refine-config.ts"() {
|
|
10922
10939
|
"use strict";
|
|
10923
10940
|
MESH_REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
|
|
10941
|
+
MESH_REFINE_VALIDATION_SCOPES = ["none", "web", "daemon"];
|
|
10924
10942
|
MESH_REFINE_CONFIG_LOCATIONS = [
|
|
10925
10943
|
".adhdev/refine.json",
|
|
10926
10944
|
".adhdev/refine.yaml",
|
|
@@ -10970,7 +10988,12 @@ var init_refine_config = __esm({
|
|
|
10970
10988
|
cwd: { type: "string" },
|
|
10971
10989
|
timeoutMs: { type: "number", minimum: 1e3, maximum: 6e5 },
|
|
10972
10990
|
outputLimitBytes: { type: "number", minimum: 1024, maximum: 1048576 },
|
|
10973
|
-
env: { type: "object", additionalProperties: { type: "string" } }
|
|
10991
|
+
env: { type: "object", additionalProperties: { type: "string" } },
|
|
10992
|
+
scopes: {
|
|
10993
|
+
type: "array",
|
|
10994
|
+
items: { enum: [...MESH_REFINE_VALIDATION_SCOPES] },
|
|
10995
|
+
description: "DOCS-ROOT: change-impact scopes this command runs in ('none'=docs-only, 'web', 'daemon'). Omitted/empty \u2192 runs in every area."
|
|
10996
|
+
}
|
|
10974
10997
|
}
|
|
10975
10998
|
}
|
|
10976
10999
|
},
|
|
@@ -10989,7 +11012,11 @@ var init_refine_config = __esm({
|
|
|
10989
11012
|
cwd: { type: "string" },
|
|
10990
11013
|
timeoutMs: { type: "number", minimum: 1e3, maximum: 6e5 },
|
|
10991
11014
|
outputLimitBytes: { type: "number", minimum: 1024, maximum: 1048576 },
|
|
10992
|
-
env: { type: "object", additionalProperties: { type: "string" } }
|
|
11015
|
+
env: { type: "object", additionalProperties: { type: "string" } },
|
|
11016
|
+
scopes: {
|
|
11017
|
+
type: "array",
|
|
11018
|
+
items: { enum: [...MESH_REFINE_VALIDATION_SCOPES] }
|
|
11019
|
+
}
|
|
10993
11020
|
}
|
|
10994
11021
|
}
|
|
10995
11022
|
}
|
|
@@ -17219,6 +17246,56 @@ async function runContinuousAutoFastForwardScan(components, mesh) {
|
|
|
17219
17246
|
await delegateRemoteAutoFastForward(components, { meshId, nodeId, node, daemonId, workspace, policy, trigger: "reconcile_auto" });
|
|
17220
17247
|
}
|
|
17221
17248
|
}
|
|
17249
|
+
async function runPendingCoordinatorCatchupScan(components, mesh) {
|
|
17250
|
+
const meshId = readNonEmptyString2(mesh?.id);
|
|
17251
|
+
if (!meshId) return;
|
|
17252
|
+
const localIds = expandDaemonIdForms([
|
|
17253
|
+
readNonEmptyString2(components.statusInstanceId),
|
|
17254
|
+
readNonEmptyString2(loadConfig().machineId)
|
|
17255
|
+
]);
|
|
17256
|
+
let markers = [];
|
|
17257
|
+
try {
|
|
17258
|
+
markers = drainPendingMeshCoordinatorEvents(
|
|
17259
|
+
meshId,
|
|
17260
|
+
localIds.length > 0 ? localIds : void 0,
|
|
17261
|
+
{ onlyEvents: /* @__PURE__ */ new Set(["coordinator_catchup"]) }
|
|
17262
|
+
);
|
|
17263
|
+
} catch (e) {
|
|
17264
|
+
LOG.warn("MeshReconcile", `Coordinator-catchup drain failed for mesh ${meshId}: ${e?.message || e}`);
|
|
17265
|
+
return;
|
|
17266
|
+
}
|
|
17267
|
+
if (markers.length === 0) return;
|
|
17268
|
+
const nodes = Array.isArray(mesh?.nodes) ? mesh.nodes : [];
|
|
17269
|
+
for (const marker of markers) {
|
|
17270
|
+
const meta = marker.metadataEvent || {};
|
|
17271
|
+
const nodeId = readNonEmptyString2(marker.nodeId) || readNonEmptyString2(meta.nodeId);
|
|
17272
|
+
const workspace = readNonEmptyString2(marker.workspace) || readNonEmptyString2(meta.workspace);
|
|
17273
|
+
const baseBranch = readNonEmptyString2(meta.baseBranch);
|
|
17274
|
+
if (!workspace) continue;
|
|
17275
|
+
if (nodeId && nodeHasActiveMeshWork(components, meshId, nodeId)) {
|
|
17276
|
+
try {
|
|
17277
|
+
queuePendingMeshCoordinatorEvent(marker);
|
|
17278
|
+
} catch {
|
|
17279
|
+
}
|
|
17280
|
+
continue;
|
|
17281
|
+
}
|
|
17282
|
+
try {
|
|
17283
|
+
const ff = await fastForwardMeshNode({
|
|
17284
|
+
meshId,
|
|
17285
|
+
...nodeId ? { nodeId } : {},
|
|
17286
|
+
workspace,
|
|
17287
|
+
...baseBranch ? { branch: baseBranch } : {},
|
|
17288
|
+
mode: "merge",
|
|
17289
|
+
execute: true,
|
|
17290
|
+
trigger: "refine_post_push_catchup",
|
|
17291
|
+
allowAutoPublishSubmoduleMainCommits: mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true
|
|
17292
|
+
});
|
|
17293
|
+
LOG.info("MeshReconcile", `Coordinator catch-up ff for ${meshId}/${nodeId || workspace}: ${ff.code} (executed=${ff.executed})`);
|
|
17294
|
+
} catch (e) {
|
|
17295
|
+
LOG.warn("MeshReconcile", `Coordinator catch-up ff failed for ${meshId}/${nodeId || workspace}: ${e?.message || e}`);
|
|
17296
|
+
}
|
|
17297
|
+
}
|
|
17298
|
+
}
|
|
17222
17299
|
function runIdleMaintenanceThenAssignQueue(components, args) {
|
|
17223
17300
|
setImmediate(() => {
|
|
17224
17301
|
maybeAutoFastForwardIdleNode(components, args).finally(() => {
|
|
@@ -22214,6 +22291,15 @@ async function runMeshReconcileTick(components) {
|
|
|
22214
22291
|
}
|
|
22215
22292
|
}
|
|
22216
22293
|
}
|
|
22294
|
+
for (const mesh of listMeshes()) {
|
|
22295
|
+
const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
|
|
22296
|
+
if (!daemonHostsMesh(mesh, selfIds)) continue;
|
|
22297
|
+
try {
|
|
22298
|
+
await runPendingCoordinatorCatchupScan(components, mesh);
|
|
22299
|
+
} catch (e) {
|
|
22300
|
+
LOG.warn("MeshReconcile", `Coordinator catch-up scan failed for mesh ${mesh.id}: ${e?.message || e}`);
|
|
22301
|
+
}
|
|
22302
|
+
}
|
|
22217
22303
|
if (dispatchMeshCommand) {
|
|
22218
22304
|
for (const mesh of listMeshes()) {
|
|
22219
22305
|
const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
|
|
@@ -58891,6 +58977,8 @@ init_logger();
|
|
|
58891
58977
|
init_debug_trace();
|
|
58892
58978
|
init_dist();
|
|
58893
58979
|
init_mesh_events();
|
|
58980
|
+
init_mesh_reconcile_identity();
|
|
58981
|
+
init_mesh_fast_forward();
|
|
58894
58982
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
58895
58983
|
|
|
58896
58984
|
// src/mesh/mesh-refine-batch.ts
|
|
@@ -59821,7 +59909,10 @@ function buildMeshRefineValidationPlan(mesh, workspace) {
|
|
|
59821
59909
|
category: command.category,
|
|
59822
59910
|
source: command.source,
|
|
59823
59911
|
cwd: command.cwd,
|
|
59824
|
-
timeoutMs: command.timeoutMs
|
|
59912
|
+
timeoutMs: command.timeoutMs,
|
|
59913
|
+
// DOCS-ROOT: surface the change-impact scopes so `mesh_refine_config` shows which
|
|
59914
|
+
// area(s) each command runs in (absent → every area).
|
|
59915
|
+
...command.scopes ? { scopes: command.scopes } : {}
|
|
59825
59916
|
});
|
|
59826
59917
|
return {
|
|
59827
59918
|
source: plan.source,
|
|
@@ -59934,9 +60025,33 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
|
|
|
59934
60025
|
return /\bdaemon-core\b|\bdaemon-cloud\b|\btest:daemon\b|check-vendor-drift/.test(haystack);
|
|
59935
60026
|
};
|
|
59936
60027
|
const scopeUnaffectedDaemon = opts?.changeImpact?.isDaemonAffecting === false;
|
|
60028
|
+
const changeArea = opts?.changeImpact?.changeArea;
|
|
60029
|
+
const commandRunsInArea = (candidate) => {
|
|
60030
|
+
if (!changeArea) return true;
|
|
60031
|
+
const scopes = candidate.scopes;
|
|
60032
|
+
if (scopes && scopes.length) return scopes.includes(changeArea);
|
|
60033
|
+
return changeArea !== "none";
|
|
60034
|
+
};
|
|
59937
60035
|
const skippedDaemonCommands = [];
|
|
60036
|
+
const skippedScopeCommands = [];
|
|
59938
60037
|
const commandsToRun = [];
|
|
59939
60038
|
for (const candidate of selection.commands) {
|
|
60039
|
+
if (!commandRunsInArea(candidate)) {
|
|
60040
|
+
skippedScopeCommands.push(candidate.displayCommand);
|
|
60041
|
+
summary.commandsRun.push({
|
|
60042
|
+
command: candidate.command,
|
|
60043
|
+
args: candidate.args,
|
|
60044
|
+
displayCommand: candidate.displayCommand,
|
|
60045
|
+
category: candidate.category,
|
|
60046
|
+
source: candidate.source,
|
|
60047
|
+
passed: true,
|
|
60048
|
+
skipped: true,
|
|
60049
|
+
skipReason: "unaffected_change_scope",
|
|
60050
|
+
changeArea,
|
|
60051
|
+
...candidate.scopes ? { scopes: candidate.scopes } : {}
|
|
60052
|
+
});
|
|
60053
|
+
continue;
|
|
60054
|
+
}
|
|
59940
60055
|
if (scopeUnaffectedDaemon && isDaemonScopedCommand(candidate)) {
|
|
59941
60056
|
skippedDaemonCommands.push(candidate.displayCommand);
|
|
59942
60057
|
summary.commandsRun.push({
|
|
@@ -59957,7 +60072,9 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
|
|
|
59957
60072
|
summary.changeImpact = {
|
|
59958
60073
|
isDaemonAffecting: opts.changeImpact.isDaemonAffecting,
|
|
59959
60074
|
affectedPackages: opts.changeImpact.affectedPackages,
|
|
59960
|
-
...
|
|
60075
|
+
...changeArea ? { changeArea } : {},
|
|
60076
|
+
...skippedDaemonCommands.length ? { skippedDaemonCommands } : {},
|
|
60077
|
+
...skippedScopeCommands.length ? { skippedScopeCommands } : {}
|
|
59961
60078
|
};
|
|
59962
60079
|
}
|
|
59963
60080
|
if (runLegacyBootstrapCommands) {
|
|
@@ -60083,6 +60200,24 @@ function buildRefineJobHandle(self, args) {
|
|
|
60083
60200
|
}
|
|
60084
60201
|
};
|
|
60085
60202
|
}
|
|
60203
|
+
function extractValidationFailureDiagnostics(validationSummary) {
|
|
60204
|
+
if (!validationSummary || typeof validationSummary !== "object") return void 0;
|
|
60205
|
+
const commandsRun = Array.isArray(validationSummary.commandsRun) ? validationSummary.commandsRun : [];
|
|
60206
|
+
const failed = commandsRun.find((c) => c.passed === false);
|
|
60207
|
+
const summaryFailureKind = validationSummary.failureKind;
|
|
60208
|
+
if (!failed) {
|
|
60209
|
+
return summaryFailureKind !== void 0 ? { failureKind: summaryFailureKind } : void 0;
|
|
60210
|
+
}
|
|
60211
|
+
const firstFailedCommand = typeof failed.displayCommand === "string" ? failed.displayCommand : typeof failed.command === "string" ? [failed.command, ...Array.isArray(failed.args) ? failed.args : []].join(" ").trim() : void 0;
|
|
60212
|
+
const rawOutput = [failed.stderr, failed.stdout, failed.output].filter((s2) => typeof s2 === "string" && s2.length > 0).join("\n");
|
|
60213
|
+
const outputTail = rawOutput.length > 600 ? rawOutput.slice(-600) : rawOutput;
|
|
60214
|
+
return {
|
|
60215
|
+
...firstFailedCommand ? { firstFailedCommand } : {},
|
|
60216
|
+
...failed.exitCode !== void 0 ? { exitCode: failed.exitCode } : {},
|
|
60217
|
+
...failed.failureKind !== void 0 ? { failureKind: failed.failureKind } : summaryFailureKind !== void 0 ? { failureKind: summaryFailureKind } : {},
|
|
60218
|
+
...outputTail ? { outputTail } : {}
|
|
60219
|
+
};
|
|
60220
|
+
}
|
|
60086
60221
|
function slimRefineEventResult(result) {
|
|
60087
60222
|
const slim = {};
|
|
60088
60223
|
for (const key2 of [
|
|
@@ -60095,7 +60230,12 @@ function slimRefineEventResult(result) {
|
|
|
60095
60230
|
"into",
|
|
60096
60231
|
"terminalKind",
|
|
60097
60232
|
"nextStep",
|
|
60098
|
-
"finalBranchConvergenceState"
|
|
60233
|
+
"finalBranchConvergenceState",
|
|
60234
|
+
// QW4: merge conflict paths; QW5: cleanup branch-ref / residue warnings.
|
|
60235
|
+
"conflictPaths",
|
|
60236
|
+
"branchRefWarning",
|
|
60237
|
+
"residueWarning",
|
|
60238
|
+
"branchRefDeleted"
|
|
60099
60239
|
]) {
|
|
60100
60240
|
if (result[key2] !== void 0) slim[key2] = result[key2];
|
|
60101
60241
|
}
|
|
@@ -60104,12 +60244,15 @@ function slimRefineEventResult(result) {
|
|
|
60104
60244
|
}
|
|
60105
60245
|
if (result.validationSummary && typeof result.validationSummary === "object") {
|
|
60106
60246
|
const vs = result.validationSummary;
|
|
60247
|
+
const diagnostics = vs.status === "failed" ? extractValidationFailureDiagnostics(vs) : void 0;
|
|
60107
60248
|
slim.validationSummary = {
|
|
60108
60249
|
status: vs.status,
|
|
60109
60250
|
failureCode: vs.failureCode,
|
|
60251
|
+
failureKind: vs.failureKind,
|
|
60110
60252
|
configSource: vs.configSource,
|
|
60111
60253
|
configSourceType: vs.configSourceType,
|
|
60112
|
-
commandsRunCount: Array.isArray(vs.commandsRun) ? vs.commandsRun.length : void 0
|
|
60254
|
+
commandsRunCount: Array.isArray(vs.commandsRun) ? vs.commandsRun.length : void 0,
|
|
60255
|
+
...diagnostics ? { failure: diagnostics } : {}
|
|
60113
60256
|
};
|
|
60114
60257
|
}
|
|
60115
60258
|
if (result.patchEquivalence && typeof result.patchEquivalence === "object") {
|
|
@@ -60250,6 +60393,8 @@ async function executeMeshRefineNodeSynchronously(self, meshId, nodeId, args) {
|
|
|
60250
60393
|
const resolved = await refineResolveRefsStage(self, meshId, nodeId, args, refineStages);
|
|
60251
60394
|
if (resolved.kind === "terminal") return resolved.result;
|
|
60252
60395
|
const ctx = resolved.ctx;
|
|
60396
|
+
const syncBase = await refineSyncBaseStage(self, ctx);
|
|
60397
|
+
if (syncBase.kind === "terminal") return syncBase.result;
|
|
60253
60398
|
const validation = await refineValidationStage(self, ctx);
|
|
60254
60399
|
if (validation.kind === "terminal") return validation.result;
|
|
60255
60400
|
const patchEquivalence = await refinePatchEquivalenceStage(self, ctx);
|
|
@@ -60338,6 +60483,152 @@ async function refineResolveRefsStage(self, meshId, nodeId, args, refineStages)
|
|
|
60338
60483
|
}
|
|
60339
60484
|
};
|
|
60340
60485
|
}
|
|
60486
|
+
async function computeBranchBaseDivergence(execFileAsync4, cwd, baseHead, branchHead) {
|
|
60487
|
+
let mergeBase;
|
|
60488
|
+
try {
|
|
60489
|
+
const { stdout } = await execFileAsync4("git", ["merge-base", baseHead, branchHead], { cwd, encoding: "utf8" });
|
|
60490
|
+
mergeBase = stdout.trim() || void 0;
|
|
60491
|
+
} catch {
|
|
60492
|
+
}
|
|
60493
|
+
let ahead = 0;
|
|
60494
|
+
let behind = 0;
|
|
60495
|
+
try {
|
|
60496
|
+
const { stdout } = await execFileAsync4("git", ["rev-list", "--left-right", "--count", `${baseHead}...${branchHead}`], { cwd, encoding: "utf8" });
|
|
60497
|
+
const [left, right] = stdout.trim().split(/\s+/).map((n) => Number.parseInt(n, 10));
|
|
60498
|
+
behind = Number.isFinite(left) ? left : 0;
|
|
60499
|
+
ahead = Number.isFinite(right) ? right : 0;
|
|
60500
|
+
} catch {
|
|
60501
|
+
}
|
|
60502
|
+
return {
|
|
60503
|
+
mergeBase,
|
|
60504
|
+
ahead,
|
|
60505
|
+
behind,
|
|
60506
|
+
diverged: ahead > 0 && behind > 0,
|
|
60507
|
+
// Strictly behind = base is a descendant of branch (branch is an ancestor of base):
|
|
60508
|
+
// behind>0 with ahead===0.
|
|
60509
|
+
isStrictlyBehind: behind > 0 && ahead === 0
|
|
60510
|
+
};
|
|
60511
|
+
}
|
|
60512
|
+
async function refineSyncBaseStage(self, ctx) {
|
|
60513
|
+
const { repoRoot, baseHead, node, branch, baseBranch, refineStages, execFileAsync: execFileAsync4 } = ctx;
|
|
60514
|
+
let branchHead = ctx.branchHead;
|
|
60515
|
+
const syncStarted = Date.now();
|
|
60516
|
+
const divergence = await computeBranchBaseDivergence(execFileAsync4, node.workspace, baseHead, branchHead);
|
|
60517
|
+
if (divergence.behind === 0) {
|
|
60518
|
+
recordMeshRefineStage(refineStages, "sync_base", "passed", syncStarted, {
|
|
60519
|
+
ahead: divergence.ahead,
|
|
60520
|
+
behind: divergence.behind,
|
|
60521
|
+
rebased: false,
|
|
60522
|
+
reason: "branch_up_to_date_with_base"
|
|
60523
|
+
});
|
|
60524
|
+
return { kind: "continue", ctx };
|
|
60525
|
+
}
|
|
60526
|
+
try {
|
|
60527
|
+
const preRebasePe = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
|
|
60528
|
+
const alreadyMerged = !preRebasePe.actualPatchId && !!preRebasePe.expectedPatchId;
|
|
60529
|
+
const submoduleConflict = preRebasePe.actionableHint?.kind === "submodule_conflict";
|
|
60530
|
+
if (alreadyMerged || submoduleConflict) {
|
|
60531
|
+
recordMeshRefineStage(refineStages, "sync_base", "passed", syncStarted, {
|
|
60532
|
+
ahead: divergence.ahead,
|
|
60533
|
+
behind: divergence.behind,
|
|
60534
|
+
rebased: false,
|
|
60535
|
+
reason: alreadyMerged ? "already_merged_via_other_path_skip_rebase" : "submodule_conflict_defer_to_patch_equivalence"
|
|
60536
|
+
});
|
|
60537
|
+
return { kind: "continue", ctx };
|
|
60538
|
+
}
|
|
60539
|
+
} catch {
|
|
60540
|
+
}
|
|
60541
|
+
const rebaseStarted = Date.now();
|
|
60542
|
+
try {
|
|
60543
|
+
execFileSync8("git", ["rebase", baseHead], { cwd: node.workspace, stdio: ["ignore", "pipe", "pipe"] });
|
|
60544
|
+
} catch (rebaseErr) {
|
|
60545
|
+
try {
|
|
60546
|
+
execFileSync8("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
|
|
60547
|
+
} catch {
|
|
60548
|
+
}
|
|
60549
|
+
let submoduleHintPatchEquivalence;
|
|
60550
|
+
try {
|
|
60551
|
+
submoduleHintPatchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, ctx.branchHead);
|
|
60552
|
+
} catch {
|
|
60553
|
+
}
|
|
60554
|
+
const submoduleConflict = submoduleHintPatchEquivalence?.actionableHint?.kind === "submodule_conflict";
|
|
60555
|
+
recordMeshRefineStage(refineStages, "sync_base", "failed", syncStarted, {
|
|
60556
|
+
ahead: divergence.ahead,
|
|
60557
|
+
behind: divergence.behind,
|
|
60558
|
+
diverged: divergence.diverged,
|
|
60559
|
+
error: rebaseErr?.message || String(rebaseErr),
|
|
60560
|
+
...submoduleConflict ? { submoduleConflict: true } : {}
|
|
60561
|
+
});
|
|
60562
|
+
if (submoduleConflict && submoduleHintPatchEquivalence) {
|
|
60563
|
+
recordMeshRefineStage(refineStages, "patch_equivalence", "failed", rebaseStarted, {
|
|
60564
|
+
equivalent: submoduleHintPatchEquivalence.equivalent,
|
|
60565
|
+
expectedPatchId: submoduleHintPatchEquivalence.expectedPatchId,
|
|
60566
|
+
actualPatchId: submoduleHintPatchEquivalence.actualPatchId,
|
|
60567
|
+
error: submoduleHintPatchEquivalence.error,
|
|
60568
|
+
actionableHint: submoduleHintPatchEquivalence.actionableHint
|
|
60569
|
+
});
|
|
60570
|
+
return { kind: "terminal", result: {
|
|
60571
|
+
success: false,
|
|
60572
|
+
code: "patch_equivalence_failed",
|
|
60573
|
+
convergenceStatus: "blocked_review",
|
|
60574
|
+
error: "Refinery patch-equivalence preflight failed (submodule gitlink conflict); merge/refine was not attempted.",
|
|
60575
|
+
branch,
|
|
60576
|
+
into: baseBranch,
|
|
60577
|
+
patchEquivalence: submoduleHintPatchEquivalence,
|
|
60578
|
+
refineStages,
|
|
60579
|
+
finalBranchConvergenceState: {
|
|
60580
|
+
branch,
|
|
60581
|
+
baseBranch,
|
|
60582
|
+
merged: false,
|
|
60583
|
+
removed: false,
|
|
60584
|
+
patchEquivalence: "failed",
|
|
60585
|
+
status: "blocked_review"
|
|
60586
|
+
}
|
|
60587
|
+
} };
|
|
60588
|
+
}
|
|
60589
|
+
recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", "failed", rebaseStarted, {
|
|
60590
|
+
error: rebaseErr?.message || String(rebaseErr)
|
|
60591
|
+
});
|
|
60592
|
+
return { kind: "terminal", result: {
|
|
60593
|
+
success: false,
|
|
60594
|
+
code: "needs_rebase_with_conflicts",
|
|
60595
|
+
convergenceStatus: "blocked_review",
|
|
60596
|
+
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.`,
|
|
60597
|
+
branch,
|
|
60598
|
+
into: baseBranch,
|
|
60599
|
+
refineStages,
|
|
60600
|
+
finalBranchConvergenceState: {
|
|
60601
|
+
branch,
|
|
60602
|
+
baseBranch,
|
|
60603
|
+
merged: false,
|
|
60604
|
+
removed: false,
|
|
60605
|
+
status: "blocked_review"
|
|
60606
|
+
}
|
|
60607
|
+
} };
|
|
60608
|
+
}
|
|
60609
|
+
const { stdout: rebasedHeadStdout } = await execFileAsync4("git", ["rev-parse", "HEAD"], { cwd: node.workspace, encoding: "utf8" });
|
|
60610
|
+
branchHead = rebasedHeadStdout.trim();
|
|
60611
|
+
ctx.branchHead = branchHead;
|
|
60612
|
+
let changeImpact = ctx.changeImpact;
|
|
60613
|
+
try {
|
|
60614
|
+
changeImpact = await classifyChangedPackages(node.workspace, baseHead, branchHead);
|
|
60615
|
+
ctx.changeImpact = changeImpact;
|
|
60616
|
+
} catch {
|
|
60617
|
+
}
|
|
60618
|
+
recordMeshRefineStage(refineStages, "sync_base", "passed", syncStarted, {
|
|
60619
|
+
ahead: divergence.ahead,
|
|
60620
|
+
behind: divergence.behind,
|
|
60621
|
+
diverged: divergence.diverged,
|
|
60622
|
+
rebased: true,
|
|
60623
|
+
rebasedBranchHead: branchHead,
|
|
60624
|
+
...changeImpact ? { changeImpact } : {}
|
|
60625
|
+
});
|
|
60626
|
+
recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", "passed", rebaseStarted, {
|
|
60627
|
+
rebasedBranchHead: branchHead,
|
|
60628
|
+
rebasedOnto: baseHead
|
|
60629
|
+
});
|
|
60630
|
+
return { kind: "continue", ctx };
|
|
60631
|
+
}
|
|
60341
60632
|
async function refineValidationStage(self, ctx) {
|
|
60342
60633
|
const { mesh, node, branch, baseBranch, refineStages } = ctx;
|
|
60343
60634
|
const validationStarted = Date.now();
|
|
@@ -60362,7 +60653,7 @@ async function refineValidationStage(self, ctx) {
|
|
|
60362
60653
|
{ validationStatus: validationSummary.status, commandsRun: validationSummary.commandsRun.length }
|
|
60363
60654
|
);
|
|
60364
60655
|
if (validationSummary.status === "failed") {
|
|
60365
|
-
const firstFailedCmd = Array.isArray(validationSummary.commandsRun) ? validationSummary.commandsRun.find((c) => c.
|
|
60656
|
+
const firstFailedCmd = Array.isArray(validationSummary.commandsRun) ? validationSummary.commandsRun.find((c) => c.passed === false) : void 0;
|
|
60366
60657
|
const buildValidationFailedError = () => {
|
|
60367
60658
|
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.";
|
|
60368
60659
|
if (!firstFailedCmd) return base;
|
|
@@ -60418,10 +60709,10 @@ ${tail}` : ""
|
|
|
60418
60709
|
return { kind: "continue", ctx };
|
|
60419
60710
|
}
|
|
60420
60711
|
async function refinePatchEquivalenceStage(self, ctx) {
|
|
60421
|
-
const { meshId, nodeId, args, repoRoot, baseHead,
|
|
60422
|
-
|
|
60712
|
+
const { meshId, nodeId, args, repoRoot, baseHead, branch, baseBranch, validationSummary, refineStages } = ctx;
|
|
60713
|
+
const branchHead = ctx.branchHead;
|
|
60423
60714
|
const patchEquivalenceStarted = Date.now();
|
|
60424
|
-
|
|
60715
|
+
const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
|
|
60425
60716
|
recordMeshRefineStage(refineStages, "patch_equivalence", patchEquivalence.status, patchEquivalenceStarted, {
|
|
60426
60717
|
equivalent: patchEquivalence.equivalent,
|
|
60427
60718
|
expectedPatchId: patchEquivalence.expectedPatchId,
|
|
@@ -60430,90 +60721,8 @@ async function refinePatchEquivalenceStage(self, ctx) {
|
|
|
60430
60721
|
actionableHint: patchEquivalence.actionableHint
|
|
60431
60722
|
});
|
|
60432
60723
|
if (!patchEquivalence.equivalent) {
|
|
60433
|
-
let didAutoRebase = false;
|
|
60434
|
-
let isBehindBase = false;
|
|
60435
|
-
try {
|
|
60436
|
-
execFileSync8("git", ["merge-base", "--is-ancestor", branchHead, baseHead], {
|
|
60437
|
-
cwd: node.workspace,
|
|
60438
|
-
stdio: "ignore"
|
|
60439
|
-
});
|
|
60440
|
-
isBehindBase = true;
|
|
60441
|
-
} catch {
|
|
60442
|
-
}
|
|
60443
|
-
if (isBehindBase) {
|
|
60444
|
-
const autoRebaseStarted = Date.now();
|
|
60445
|
-
try {
|
|
60446
|
-
execFileSync8("git", ["rebase", baseHead], {
|
|
60447
|
-
cwd: node.workspace,
|
|
60448
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
60449
|
-
});
|
|
60450
|
-
const { stdout: rebasedHeadStdout } = await execFileAsync4("git", ["rev-parse", "HEAD"], { cwd: node.workspace, encoding: "utf8" });
|
|
60451
|
-
branchHead = rebasedHeadStdout.trim();
|
|
60452
|
-
const rebasedPatchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
|
|
60453
|
-
recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", rebasedPatchEquivalence.status, autoRebaseStarted, {
|
|
60454
|
-
equivalent: rebasedPatchEquivalence.equivalent,
|
|
60455
|
-
expectedPatchId: rebasedPatchEquivalence.expectedPatchId,
|
|
60456
|
-
actualPatchId: rebasedPatchEquivalence.actualPatchId,
|
|
60457
|
-
error: rebasedPatchEquivalence.error,
|
|
60458
|
-
rebasedBranchHead: branchHead
|
|
60459
|
-
});
|
|
60460
|
-
if (rebasedPatchEquivalence.equivalent) {
|
|
60461
|
-
patchEquivalence = rebasedPatchEquivalence;
|
|
60462
|
-
didAutoRebase = true;
|
|
60463
|
-
} else {
|
|
60464
|
-
return { kind: "terminal", result: {
|
|
60465
|
-
success: false,
|
|
60466
|
-
code: "needs_rebase",
|
|
60467
|
-
convergenceStatus: "blocked_review",
|
|
60468
|
-
error: "Branch was rebased onto base but patch equivalence still failed; manual intervention required.",
|
|
60469
|
-
branch,
|
|
60470
|
-
into: baseBranch,
|
|
60471
|
-
validationSummary,
|
|
60472
|
-
patchEquivalence: rebasedPatchEquivalence,
|
|
60473
|
-
refineStages,
|
|
60474
|
-
finalBranchConvergenceState: {
|
|
60475
|
-
branch,
|
|
60476
|
-
baseBranch,
|
|
60477
|
-
merged: false,
|
|
60478
|
-
removed: false,
|
|
60479
|
-
validation: "passed",
|
|
60480
|
-
patchEquivalence: "failed",
|
|
60481
|
-
status: "blocked_review"
|
|
60482
|
-
}
|
|
60483
|
-
} };
|
|
60484
|
-
}
|
|
60485
|
-
} catch (rebaseErr) {
|
|
60486
|
-
try {
|
|
60487
|
-
execFileSync8("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
|
|
60488
|
-
} catch {
|
|
60489
|
-
}
|
|
60490
|
-
recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", "failed", autoRebaseStarted, {
|
|
60491
|
-
error: rebaseErr?.message || String(rebaseErr)
|
|
60492
|
-
});
|
|
60493
|
-
return { kind: "terminal", result: {
|
|
60494
|
-
success: false,
|
|
60495
|
-
code: "needs_rebase_with_conflicts",
|
|
60496
|
-
convergenceStatus: "blocked_review",
|
|
60497
|
-
error: "Branch is behind base and auto-rebase failed due to conflicts; resolve conflicts manually and retry.",
|
|
60498
|
-
branch,
|
|
60499
|
-
into: baseBranch,
|
|
60500
|
-
validationSummary,
|
|
60501
|
-
patchEquivalence,
|
|
60502
|
-
refineStages,
|
|
60503
|
-
finalBranchConvergenceState: {
|
|
60504
|
-
branch,
|
|
60505
|
-
baseBranch,
|
|
60506
|
-
merged: false,
|
|
60507
|
-
removed: false,
|
|
60508
|
-
validation: "passed",
|
|
60509
|
-
patchEquivalence: "failed",
|
|
60510
|
-
status: "blocked_review"
|
|
60511
|
-
}
|
|
60512
|
-
} };
|
|
60513
|
-
}
|
|
60514
|
-
}
|
|
60515
60724
|
const alreadyMergedViaOtherPath = !patchEquivalence.actualPatchId && !!patchEquivalence.expectedPatchId;
|
|
60516
|
-
if (!
|
|
60725
|
+
if (!alreadyMergedViaOtherPath) {
|
|
60517
60726
|
return { kind: "terminal", result: {
|
|
60518
60727
|
success: false,
|
|
60519
60728
|
code: "patch_equivalence_failed",
|
|
@@ -60535,7 +60744,7 @@ async function refinePatchEquivalenceStage(self, ctx) {
|
|
|
60535
60744
|
}
|
|
60536
60745
|
} };
|
|
60537
60746
|
}
|
|
60538
|
-
|
|
60747
|
+
{
|
|
60539
60748
|
recordMeshRefineStage(refineStages, "merge", "skipped", Date.now(), {
|
|
60540
60749
|
reason: "already_merged_via_other_path",
|
|
60541
60750
|
note: "actualPatchId is empty; branch content is already present in base via a different commit path"
|
|
@@ -60743,8 +60952,145 @@ ${hintLines.join("\n")}` : "",
|
|
|
60743
60952
|
}
|
|
60744
60953
|
return { kind: "continue", ctx };
|
|
60745
60954
|
}
|
|
60955
|
+
async function requestCoordinatorLocalCatchup(self, params) {
|
|
60956
|
+
const { meshId, ctx, mesh, baseBranch, repoRoot } = params;
|
|
60957
|
+
const coordinatorDaemonId = typeof ctx.args?.coordinatorDaemonId === "string" && ctx.args.coordinatorDaemonId.trim() ? ctx.args.coordinatorDaemonId.trim() : self.deps.statusInstanceId || void 0;
|
|
60958
|
+
if (!coordinatorDaemonId) return void 0;
|
|
60959
|
+
if (!Array.isArray(mesh?.nodes)) return void 0;
|
|
60960
|
+
const coordinatorBaseNode = mesh.nodes.find((n) => !n?.isLocalWorktree && daemonIdListIncludes([coordinatorDaemonId], readStringValue(n?.daemonId)));
|
|
60961
|
+
if (!coordinatorBaseNode) return void 0;
|
|
60962
|
+
const coordinatorWorkspace = readStringValue(coordinatorBaseNode.repoRoot) || readStringValue(coordinatorBaseNode.workspace);
|
|
60963
|
+
if (!coordinatorWorkspace) return void 0;
|
|
60964
|
+
const drainIds = [self.deps.statusInstanceId].filter((v) => typeof v === "string" && v.length > 0);
|
|
60965
|
+
const selfIds = resolveCoordinatorSelfIds(mesh, drainIds);
|
|
60966
|
+
const coordinatorIsSelf = daemonIdListIncludes(selfIds, readStringValue(coordinatorBaseNode.daemonId));
|
|
60967
|
+
if (coordinatorIsSelf) {
|
|
60968
|
+
try {
|
|
60969
|
+
const ff = await fastForwardMeshNode({
|
|
60970
|
+
meshId,
|
|
60971
|
+
nodeId: readStringValue(coordinatorBaseNode.id),
|
|
60972
|
+
workspace: coordinatorWorkspace,
|
|
60973
|
+
branch: baseBranch,
|
|
60974
|
+
mode: "merge",
|
|
60975
|
+
execute: true,
|
|
60976
|
+
trigger: "refine_post_push_catchup",
|
|
60977
|
+
allowAutoPublishSubmoduleMainCommits: mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true
|
|
60978
|
+
});
|
|
60979
|
+
return {
|
|
60980
|
+
mode: "local_fast_forward",
|
|
60981
|
+
coordinatorWorkspace,
|
|
60982
|
+
sameAsRepoRoot: coordinatorWorkspace === repoRoot,
|
|
60983
|
+
code: ff.code,
|
|
60984
|
+
executed: ff.executed,
|
|
60985
|
+
success: ff.success,
|
|
60986
|
+
...ff.blockingReasons?.length ? { blockingReasons: ff.blockingReasons } : {}
|
|
60987
|
+
};
|
|
60988
|
+
} catch (e) {
|
|
60989
|
+
return { mode: "local_fast_forward", coordinatorWorkspace, error: e?.message || String(e) };
|
|
60990
|
+
}
|
|
60991
|
+
}
|
|
60992
|
+
try {
|
|
60993
|
+
queuePendingMeshCoordinatorEvent({
|
|
60994
|
+
event: "coordinator_catchup",
|
|
60995
|
+
meshId,
|
|
60996
|
+
nodeLabel: readStringValue(coordinatorBaseNode.id) || "coordinator-base",
|
|
60997
|
+
nodeId: readStringValue(coordinatorBaseNode.id),
|
|
60998
|
+
workspace: coordinatorWorkspace,
|
|
60999
|
+
metadataEvent: {
|
|
61000
|
+
source: "refine_post_push_coordinator_catchup",
|
|
61001
|
+
operation: "coordinator_catchup",
|
|
61002
|
+
baseBranch,
|
|
61003
|
+
coordinatorDaemonId,
|
|
61004
|
+
reason: "post_push_base_advanced"
|
|
61005
|
+
},
|
|
61006
|
+
queuedAt: Date.now(),
|
|
61007
|
+
targetCoordinatorDaemonId: coordinatorDaemonId
|
|
61008
|
+
});
|
|
61009
|
+
return { mode: "pending_marker_queued", coordinatorDaemonId, coordinatorWorkspace, baseBranch };
|
|
61010
|
+
} catch (e) {
|
|
61011
|
+
return { mode: "pending_marker_queued", coordinatorDaemonId, error: e?.message || String(e) };
|
|
61012
|
+
}
|
|
61013
|
+
}
|
|
60746
61014
|
async function refineMergeAndFinalizeStage(self, ctx) {
|
|
60747
61015
|
const { meshId, nodeId, args, repoRoot, baseHead, node, branch, baseBranch, sourceNode, validationSummary, patchEquivalence, submoduleReachability, mesh, refineStages, execFileAsync: execFileAsync4 } = ctx;
|
|
61016
|
+
const leaseKey = `${repoRoot}::${baseBranch}`;
|
|
61017
|
+
const leaseHolder = buildRefineJobKey(self, meshId, nodeId);
|
|
61018
|
+
if (self.refineBaseLeases.has(leaseKey) && self.refineBaseLeases.get(leaseKey) !== leaseHolder) {
|
|
61019
|
+
recordMeshRefineStage(refineStages, "base_lease", "skipped", Date.now(), {
|
|
61020
|
+
leaseKey,
|
|
61021
|
+
heldBy: self.refineBaseLeases.get(leaseKey),
|
|
61022
|
+
retryable: true
|
|
61023
|
+
});
|
|
61024
|
+
return { kind: "terminal", result: {
|
|
61025
|
+
success: false,
|
|
61026
|
+
code: "base_locked",
|
|
61027
|
+
convergenceStatus: "blocked_review",
|
|
61028
|
+
retryable: true,
|
|
61029
|
+
error: `Another refine holds the base lease for ${baseBranch} in this repo; retry after it completes.`,
|
|
61030
|
+
branch,
|
|
61031
|
+
into: baseBranch,
|
|
61032
|
+
validationSummary,
|
|
61033
|
+
patchEquivalence,
|
|
61034
|
+
submoduleReachability,
|
|
61035
|
+
refineStages,
|
|
61036
|
+
finalBranchConvergenceState: {
|
|
61037
|
+
branch,
|
|
61038
|
+
baseBranch,
|
|
61039
|
+
merged: false,
|
|
61040
|
+
removed: false,
|
|
61041
|
+
status: "blocked_review"
|
|
61042
|
+
}
|
|
61043
|
+
} };
|
|
61044
|
+
}
|
|
61045
|
+
self.refineBaseLeases.set(leaseKey, leaseHolder);
|
|
61046
|
+
try {
|
|
61047
|
+
return await runRefineMergeAndFinalizeLocked(self, ctx);
|
|
61048
|
+
} finally {
|
|
61049
|
+
if (self.refineBaseLeases.get(leaseKey) === leaseHolder) self.refineBaseLeases.delete(leaseKey);
|
|
61050
|
+
}
|
|
61051
|
+
}
|
|
61052
|
+
async function runRefineMergeAndFinalizeLocked(self, ctx) {
|
|
61053
|
+
const { meshId, nodeId, args, repoRoot, baseHead, node, branch, baseBranch, sourceNode, validationSummary, patchEquivalence, submoduleReachability, mesh, refineStages, execFileAsync: execFileAsync4 } = ctx;
|
|
61054
|
+
const casStarted = Date.now();
|
|
61055
|
+
let baseMoved = false;
|
|
61056
|
+
let liveBaseHead;
|
|
61057
|
+
try {
|
|
61058
|
+
await execFileAsync4("git", ["fetch", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
|
|
61059
|
+
const { stdout } = await execFileAsync4("git", ["rev-parse", `origin/${baseBranch}`], { cwd: repoRoot, encoding: "utf8" });
|
|
61060
|
+
liveBaseHead = stdout.trim();
|
|
61061
|
+
baseMoved = !!liveBaseHead && liveBaseHead !== baseHead;
|
|
61062
|
+
} catch {
|
|
61063
|
+
}
|
|
61064
|
+
if (baseMoved) {
|
|
61065
|
+
recordMeshRefineStage(refineStages, "base_cas", "failed", casStarted, {
|
|
61066
|
+
pinnedBaseHead: baseHead,
|
|
61067
|
+
liveBaseHead,
|
|
61068
|
+
retryable: true
|
|
61069
|
+
});
|
|
61070
|
+
return { kind: "terminal", result: {
|
|
61071
|
+
success: false,
|
|
61072
|
+
code: "base_moved",
|
|
61073
|
+
convergenceStatus: "blocked_review",
|
|
61074
|
+
retryable: true,
|
|
61075
|
+
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.`,
|
|
61076
|
+
branch,
|
|
61077
|
+
into: baseBranch,
|
|
61078
|
+
pinnedBaseHead: baseHead,
|
|
61079
|
+
liveBaseHead,
|
|
61080
|
+
validationSummary,
|
|
61081
|
+
patchEquivalence,
|
|
61082
|
+
submoduleReachability,
|
|
61083
|
+
refineStages,
|
|
61084
|
+
finalBranchConvergenceState: {
|
|
61085
|
+
branch,
|
|
61086
|
+
baseBranch,
|
|
61087
|
+
merged: false,
|
|
61088
|
+
removed: false,
|
|
61089
|
+
status: "blocked_review"
|
|
61090
|
+
}
|
|
61091
|
+
} };
|
|
61092
|
+
}
|
|
61093
|
+
recordMeshRefineStage(refineStages, "base_cas", "passed", casStarted, { pinnedBaseHead: baseHead });
|
|
60748
61094
|
let mergeResult;
|
|
60749
61095
|
const mergeStarted = Date.now();
|
|
60750
61096
|
try {
|
|
@@ -60756,16 +61102,33 @@ async function refineMergeAndFinalizeStage(self, ctx) {
|
|
|
60756
61102
|
};
|
|
60757
61103
|
recordMeshRefineStage(refineStages, "merge", "passed", mergeStarted, mergeResult);
|
|
60758
61104
|
} catch (e) {
|
|
61105
|
+
const mergeOutput = `${e?.stdout || ""}
|
|
61106
|
+
${e?.stderr || ""}`;
|
|
61107
|
+
const conflictPaths = [...mergeOutput.matchAll(/Merge conflict in (.+)/g)].map((m) => m[1].trim()).filter(Boolean);
|
|
61108
|
+
try {
|
|
61109
|
+
await execFileAsync4("git", ["merge", "--abort"], { cwd: repoRoot, encoding: "utf8" });
|
|
61110
|
+
} catch {
|
|
61111
|
+
}
|
|
60759
61112
|
recordMeshRefineStage(refineStages, "merge", "failed", mergeStarted, {
|
|
60760
61113
|
error: e?.message || String(e),
|
|
60761
61114
|
stdout: truncateValidationOutput(e?.stdout),
|
|
60762
|
-
stderr: truncateValidationOutput(e?.stderr)
|
|
61115
|
+
stderr: truncateValidationOutput(e?.stderr),
|
|
61116
|
+
...conflictPaths.length ? { conflictPaths } : {}
|
|
60763
61117
|
});
|
|
60764
61118
|
return { kind: "terminal", result: {
|
|
60765
61119
|
success: false,
|
|
60766
|
-
|
|
61120
|
+
code: "merge_failed",
|
|
61121
|
+
convergenceStatus: "not_mergeable",
|
|
61122
|
+
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)}`,
|
|
61123
|
+
branch,
|
|
61124
|
+
into: baseBranch,
|
|
61125
|
+
...conflictPaths.length ? { conflictPaths } : {},
|
|
60767
61126
|
validationSummary,
|
|
60768
61127
|
patchEquivalence,
|
|
61128
|
+
mergeResult: {
|
|
61129
|
+
stdout: truncateValidationOutput(e?.stdout),
|
|
61130
|
+
stderr: truncateValidationOutput(e?.stderr)
|
|
61131
|
+
},
|
|
60769
61132
|
refineStages,
|
|
60770
61133
|
finalBranchConvergenceState: {
|
|
60771
61134
|
branch,
|
|
@@ -60821,6 +61184,93 @@ async function refineMergeAndFinalizeStage(self, ctx) {
|
|
|
60821
61184
|
}
|
|
60822
61185
|
} };
|
|
60823
61186
|
}
|
|
61187
|
+
const requireApprovalForPush = mesh?.policy?.requireApprovalForPush ?? DEFAULT_MESH_POLICY.requireApprovalForPush;
|
|
61188
|
+
let pushResult;
|
|
61189
|
+
if (!requireApprovalForPush) {
|
|
61190
|
+
const pushStarted = Date.now();
|
|
61191
|
+
try {
|
|
61192
|
+
await execFileAsync4("git", ["push", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
|
|
61193
|
+
pushResult = { pushed: true, remote: "origin", branch: baseBranch, durationMs: Date.now() - pushStarted };
|
|
61194
|
+
recordMeshRefineStage(refineStages, "push", "passed", pushStarted, pushResult);
|
|
61195
|
+
} catch (e) {
|
|
61196
|
+
pushResult = {
|
|
61197
|
+
pushed: false,
|
|
61198
|
+
remote: "origin",
|
|
61199
|
+
branch: baseBranch,
|
|
61200
|
+
error: e?.message || String(e),
|
|
61201
|
+
stderr: e?.stderr,
|
|
61202
|
+
durationMs: Date.now() - pushStarted
|
|
61203
|
+
};
|
|
61204
|
+
recordMeshRefineStage(refineStages, "push", "failed", pushStarted, pushResult);
|
|
61205
|
+
return { kind: "terminal", result: {
|
|
61206
|
+
success: false,
|
|
61207
|
+
code: "push_failed",
|
|
61208
|
+
convergenceStatus: "blocked_review",
|
|
61209
|
+
retryable: true,
|
|
61210
|
+
merged: true,
|
|
61211
|
+
mergedLocal: true,
|
|
61212
|
+
pushed: false,
|
|
61213
|
+
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}`,
|
|
61214
|
+
branch,
|
|
61215
|
+
into: baseBranch,
|
|
61216
|
+
pushResult,
|
|
61217
|
+
pushCommand: `git push origin ${baseBranch}`,
|
|
61218
|
+
validationSummary,
|
|
61219
|
+
patchEquivalence,
|
|
61220
|
+
submoduleReachability,
|
|
61221
|
+
submoduleAlignment,
|
|
61222
|
+
mergeResult,
|
|
61223
|
+
refineStages,
|
|
61224
|
+
finalBranchConvergenceState: {
|
|
61225
|
+
branch: baseBranch,
|
|
61226
|
+
mergedBranch: branch,
|
|
61227
|
+
baseBranch,
|
|
61228
|
+
merged: true,
|
|
61229
|
+
pushed: false,
|
|
61230
|
+
removed: false,
|
|
61231
|
+
validation: "passed",
|
|
61232
|
+
patchEquivalence: "passed",
|
|
61233
|
+
submoduleAlignment: submoduleAlignment.status,
|
|
61234
|
+
status: "merged_push_failed",
|
|
61235
|
+
nextStep: `Retry the push (git -C ${repoRoot} push origin ${baseBranch}); then the worktree can be cleaned up.`
|
|
61236
|
+
}
|
|
61237
|
+
} };
|
|
61238
|
+
}
|
|
61239
|
+
} else {
|
|
61240
|
+
recordMeshRefineStage(refineStages, "push", "skipped", Date.now(), {
|
|
61241
|
+
reason: "require_approval_for_push"
|
|
61242
|
+
});
|
|
61243
|
+
return { kind: "terminal", result: {
|
|
61244
|
+
success: true,
|
|
61245
|
+
merged: true,
|
|
61246
|
+
mergedLocal: true,
|
|
61247
|
+
pushed: false,
|
|
61248
|
+
branch,
|
|
61249
|
+
into: baseBranch,
|
|
61250
|
+
validationSummary,
|
|
61251
|
+
patchEquivalence,
|
|
61252
|
+
submoduleReachability,
|
|
61253
|
+
submoduleAlignment,
|
|
61254
|
+
mergeResult,
|
|
61255
|
+
refineStages,
|
|
61256
|
+
pushReady: true,
|
|
61257
|
+
pushCommand: `git push origin ${baseBranch}`,
|
|
61258
|
+
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.",
|
|
61259
|
+
finalBranchConvergenceState: {
|
|
61260
|
+
branch: baseBranch,
|
|
61261
|
+
mergedBranch: branch,
|
|
61262
|
+
baseBranch,
|
|
61263
|
+
merged: true,
|
|
61264
|
+
pushed: false,
|
|
61265
|
+
removed: false,
|
|
61266
|
+
validation: "passed",
|
|
61267
|
+
patchEquivalence: "passed",
|
|
61268
|
+
submoduleAlignment: submoduleAlignment.status,
|
|
61269
|
+
status: "merged_local_pending_push",
|
|
61270
|
+
nextStep: `Approve and run the push (git -C ${repoRoot} push origin ${baseBranch}); the worktree is retained until then.`
|
|
61271
|
+
}
|
|
61272
|
+
} };
|
|
61273
|
+
}
|
|
60824
61274
|
const cleanupStarted = Date.now();
|
|
60825
61275
|
const refineSessionCleanupMode = self.normalizeMeshSessionCleanupMode(
|
|
60826
61276
|
mesh?.policy?.sessionCleanupOnNodeRemove
|
|
@@ -60848,10 +61298,10 @@ async function refineMergeAndFinalizeStage(self, ctx) {
|
|
|
60848
61298
|
sessionCleanupMode: refineSessionCleanupMode,
|
|
60849
61299
|
...refineSessionIds && refineSessionIds.length > 0 ? { sessionIds: refineSessionIds } : {},
|
|
60850
61300
|
inlineMesh: args?.inlineMesh,
|
|
60851
|
-
// REFINE-CLEANUP: refine reaches cleanup only AFTER a verified merge
|
|
60852
|
-
//
|
|
60853
|
-
// (e.g. a bootstrap lockfile rewrite) — never unmerged work.
|
|
60854
|
-
// sets requireClean=false so a plain-dirty worktree no longer aborts
|
|
61301
|
+
// REFINE-CLEANUP: refine reaches cleanup only AFTER a verified merge AND a
|
|
61302
|
+
// successful push (DS1), so any residual worktree dirtiness here is
|
|
61303
|
+
// incidental (e.g. a bootstrap lockfile rewrite) — never unmerged work.
|
|
61304
|
+
// `force` sets requireClean=false so a plain-dirty worktree no longer aborts
|
|
60855
61305
|
// removal with merged_cleanup_failed. Branch-ref deletion still keys off
|
|
60856
61306
|
// mergeConvergence (NOT the force flag), so no merged work can be lost.
|
|
60857
61307
|
force: true
|
|
@@ -60868,7 +61318,7 @@ async function refineMergeAndFinalizeStage(self, ctx) {
|
|
|
60868
61318
|
appendLedgerEntry2(meshId, {
|
|
60869
61319
|
kind: "node_removed",
|
|
60870
61320
|
nodeId,
|
|
60871
|
-
payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence, submoduleReachability, submoduleAlignment }
|
|
61321
|
+
payload: { refined: true, mergedBranch: branch, into: baseBranch, pushed: true, validationSummary, patchEquivalence, submoduleReachability, submoduleAlignment }
|
|
60872
61322
|
});
|
|
60873
61323
|
recordMeshRefineStage(refineStages, "ledger", "passed", ledgerStarted);
|
|
60874
61324
|
} catch (e) {
|
|
@@ -60880,21 +61330,24 @@ async function refineMergeAndFinalizeStage(self, ctx) {
|
|
|
60880
61330
|
mergedBranch: branch,
|
|
60881
61331
|
baseBranch,
|
|
60882
61332
|
merged: true,
|
|
61333
|
+
pushed: true,
|
|
60883
61334
|
removed: removeResult?.success !== false,
|
|
60884
61335
|
validation: "passed",
|
|
60885
61336
|
patchEquivalence: "passed",
|
|
60886
61337
|
submoduleAlignment: submoduleAlignment.status,
|
|
60887
|
-
status: removeResult?.success === false ? "merged_cleanup_failed" : "
|
|
61338
|
+
status: removeResult?.success === false ? "merged_cleanup_failed" : "merged_pushed"
|
|
60888
61339
|
};
|
|
60889
61340
|
if (removeResult?.success === false) {
|
|
60890
61341
|
return { kind: "terminal", result: {
|
|
60891
61342
|
success: false,
|
|
60892
61343
|
code: "cleanup_failed",
|
|
60893
|
-
error: "Refinery merge completed but worktree cleanup failed; manual cleanup/retry is required.",
|
|
61344
|
+
error: "Refinery merge + push completed but worktree cleanup failed; the change is on origin \u2014 manual worktree cleanup/retry is required.",
|
|
60894
61345
|
merged: true,
|
|
61346
|
+
pushed: true,
|
|
60895
61347
|
branch,
|
|
60896
61348
|
into: baseBranch,
|
|
60897
61349
|
removeResult,
|
|
61350
|
+
pushResult,
|
|
60898
61351
|
validationSummary,
|
|
60899
61352
|
patchEquivalence,
|
|
60900
61353
|
submoduleReachability,
|
|
@@ -60905,33 +61358,35 @@ async function refineMergeAndFinalizeStage(self, ctx) {
|
|
|
60905
61358
|
finalBranchConvergenceState
|
|
60906
61359
|
} };
|
|
60907
61360
|
}
|
|
60908
|
-
|
|
60909
|
-
|
|
60910
|
-
|
|
60911
|
-
|
|
60912
|
-
|
|
60913
|
-
|
|
60914
|
-
|
|
60915
|
-
|
|
60916
|
-
|
|
60917
|
-
|
|
60918
|
-
|
|
60919
|
-
pushed: false,
|
|
60920
|
-
remote: "origin",
|
|
60921
|
-
branch: baseBranch,
|
|
60922
|
-
error: e?.message || String(e),
|
|
60923
|
-
stderr: e?.stderr,
|
|
60924
|
-
durationMs: Date.now() - pushStarted
|
|
60925
|
-
};
|
|
60926
|
-
recordMeshRefineStage(refineStages, "push", "failed", pushStarted, pushResult);
|
|
61361
|
+
let coordinatorCatchup;
|
|
61362
|
+
try {
|
|
61363
|
+
coordinatorCatchup = await requestCoordinatorLocalCatchup(self, {
|
|
61364
|
+
meshId,
|
|
61365
|
+
ctx,
|
|
61366
|
+
mesh,
|
|
61367
|
+
baseBranch,
|
|
61368
|
+
repoRoot
|
|
61369
|
+
});
|
|
61370
|
+
if (coordinatorCatchup) {
|
|
61371
|
+
recordMeshRefineStage(refineStages, "coordinator_catchup", "passed", Date.now(), coordinatorCatchup);
|
|
60927
61372
|
}
|
|
61373
|
+
} catch {
|
|
60928
61374
|
}
|
|
61375
|
+
const cleanupBranchRefWarning = typeof removeResult?.branchRefWarning === "string" ? removeResult.branchRefWarning : void 0;
|
|
61376
|
+
const cleanupResidueWarning = typeof removeResult?.residueWarning === "string" ? removeResult.residueWarning : void 0;
|
|
61377
|
+
const cleanupBranchRefDeleted = typeof removeResult?.worktreeCleanup?.branchRefDeleted === "boolean" ? removeResult.worktreeCleanup.branchRefDeleted : void 0;
|
|
60929
61378
|
return { kind: "terminal", result: {
|
|
60930
61379
|
success: true,
|
|
60931
61380
|
merged: true,
|
|
61381
|
+
pushed: true,
|
|
60932
61382
|
branch,
|
|
60933
61383
|
into: baseBranch,
|
|
60934
61384
|
removeResult,
|
|
61385
|
+
pushResult,
|
|
61386
|
+
...coordinatorCatchup ? { coordinatorCatchup } : {},
|
|
61387
|
+
...cleanupBranchRefWarning ? { branchRefWarning: cleanupBranchRefWarning } : {},
|
|
61388
|
+
...cleanupResidueWarning ? { residueWarning: cleanupResidueWarning } : {},
|
|
61389
|
+
...cleanupBranchRefDeleted !== void 0 ? { branchRefDeleted: cleanupBranchRefDeleted } : {},
|
|
60935
61390
|
validationSummary,
|
|
60936
61391
|
patchEquivalence,
|
|
60937
61392
|
submoduleReachability,
|
|
@@ -60939,13 +61394,7 @@ async function refineMergeAndFinalizeStage(self, ctx) {
|
|
|
60939
61394
|
mergeResult,
|
|
60940
61395
|
refineStages,
|
|
60941
61396
|
...ledgerError ? { ledgerError } : {},
|
|
60942
|
-
finalBranchConvergenceState
|
|
60943
|
-
// Push outcome or readiness info for coordinator.
|
|
60944
|
-
...pushResult ? { pushResult } : {
|
|
60945
|
-
pushReady: true,
|
|
60946
|
-
pushCommand: `git push origin ${baseBranch}`,
|
|
60947
|
-
pushNote: "requireApprovalForPush is enabled \u2014 run the push command or obtain user approval before pushing."
|
|
60948
|
-
}
|
|
61397
|
+
finalBranchConvergenceState
|
|
60949
61398
|
} };
|
|
60950
61399
|
}
|
|
60951
61400
|
async function batchRefineMeshNodes(self, meshId, requestedNodeIds, args) {
|
|
@@ -61100,29 +61549,34 @@ async function batchRefineMeshNodes(self, meshId, requestedNodeIds, args) {
|
|
|
61100
61549
|
}
|
|
61101
61550
|
return runMeshRefineBatchConvergence(self, meshId, orderedNodes, ordering, args);
|
|
61102
61551
|
}
|
|
61552
|
+
var RETRYABLE_BASE_MOVEMENT_CODES = /* @__PURE__ */ new Set(["base_moved", "base_locked"]);
|
|
61553
|
+
function classifyBatchNodeConvergence(result) {
|
|
61554
|
+
const code = typeof result.code === "string" ? result.code : "";
|
|
61555
|
+
const stage = Array.isArray(result.refineStages) ? result.refineStages.filter((s2) => s2.status === "failed").map((s2) => s2.stage).filter(Boolean).pop() : void 0;
|
|
61556
|
+
let convergence;
|
|
61557
|
+
if (code === "already_merged" && result.alreadyMergedViaOtherPath) {
|
|
61558
|
+
convergence = "skipped_patch_equivalent";
|
|
61559
|
+
} else if (result.success === true) {
|
|
61560
|
+
convergence = "merged_to_main";
|
|
61561
|
+
} else if (code === "merge_failed" || stage === "merge") {
|
|
61562
|
+
convergence = "not_mergeable";
|
|
61563
|
+
} else {
|
|
61564
|
+
convergence = "blocked_review";
|
|
61565
|
+
}
|
|
61566
|
+
const retryable = convergence === "blocked_review" && (result.retryable === true || RETRYABLE_BASE_MOVEMENT_CODES.has(code));
|
|
61567
|
+
return { convergence, code, retryable, ...stage ? { stage } : {} };
|
|
61568
|
+
}
|
|
61103
61569
|
async function runMeshRefineBatchConvergence(self, meshId, orderedNodes, ordering, args) {
|
|
61104
|
-
const
|
|
61105
|
-
for (const node of orderedNodes) {
|
|
61570
|
+
const refineOne = async (node) => {
|
|
61106
61571
|
let result;
|
|
61107
61572
|
try {
|
|
61108
61573
|
result = await executeMeshRefineNodeSynchronously(self, meshId, node.id, args);
|
|
61109
61574
|
} catch (e) {
|
|
61110
61575
|
result = { success: false, error: e?.message || String(e) };
|
|
61111
61576
|
}
|
|
61112
|
-
const
|
|
61113
|
-
let convergence;
|
|
61114
|
-
if (code === "already_merged" && result.alreadyMergedViaOtherPath) {
|
|
61115
|
-
convergence = "skipped_patch_equivalent";
|
|
61116
|
-
} else if (result.success === true) {
|
|
61117
|
-
convergence = "merged_to_main";
|
|
61118
|
-
} else if (code === "merge_failed") {
|
|
61119
|
-
convergence = "not_mergeable";
|
|
61120
|
-
} else {
|
|
61121
|
-
convergence = "blocked_review";
|
|
61122
|
-
}
|
|
61577
|
+
const { convergence, code, stage, retryable } = classifyBatchNodeConvergence(result);
|
|
61123
61578
|
const fbcs = result.finalBranchConvergenceState && typeof result.finalBranchConvergenceState === "object" ? result.finalBranchConvergenceState : void 0;
|
|
61124
|
-
|
|
61125
|
-
results.push({
|
|
61579
|
+
return {
|
|
61126
61580
|
nodeId: node.id,
|
|
61127
61581
|
workspace: node.workspace,
|
|
61128
61582
|
convergence,
|
|
@@ -61130,14 +61584,30 @@ async function runMeshRefineBatchConvergence(self, meshId, orderedNodes, orderin
|
|
|
61130
61584
|
...typeof result.blockedReason === "string" ? { reason: result.blockedReason } : {},
|
|
61131
61585
|
...stage ? { stage } : {},
|
|
61132
61586
|
...typeof result.error === "string" ? { error: result.error } : {},
|
|
61587
|
+
...retryable ? { retryable: true } : {},
|
|
61133
61588
|
...fbcs ? { finalBranchConvergenceState: fbcs } : {}
|
|
61134
|
-
}
|
|
61589
|
+
};
|
|
61590
|
+
};
|
|
61591
|
+
const results = [];
|
|
61592
|
+
const retryQueue = [];
|
|
61593
|
+
for (const node of orderedNodes) {
|
|
61594
|
+
const outcome = await refineOne(node);
|
|
61595
|
+
results.push(outcome);
|
|
61596
|
+
if (outcome.retryable) retryQueue.push(node);
|
|
61597
|
+
}
|
|
61598
|
+
for (const node of retryQueue) {
|
|
61599
|
+
const idx = results.findIndex((r) => r.nodeId === node.id);
|
|
61600
|
+
const retried = await refineOne(node);
|
|
61601
|
+
retried.retried = true;
|
|
61602
|
+
if (idx >= 0) results[idx] = retried;
|
|
61603
|
+
else results.push(retried);
|
|
61135
61604
|
}
|
|
61136
61605
|
const summary = {
|
|
61137
61606
|
merged: results.filter((r) => r.convergence === "merged_to_main").length,
|
|
61138
61607
|
skipped: results.filter((r) => r.convergence === "skipped_patch_equivalent").length,
|
|
61139
61608
|
blocked: results.filter((r) => r.convergence === "blocked_review").length,
|
|
61140
|
-
notMergeable: results.filter((r) => r.convergence === "not_mergeable").length
|
|
61609
|
+
notMergeable: results.filter((r) => r.convergence === "not_mergeable").length,
|
|
61610
|
+
...retryQueue.length ? { retried: retryQueue.length } : {}
|
|
61141
61611
|
};
|
|
61142
61612
|
const allConverged = summary.blocked === 0 && summary.notMergeable === 0;
|
|
61143
61613
|
return {
|
|
@@ -61350,7 +61820,7 @@ async function finishMeshRefineJob(self, handle, args) {
|
|
|
61350
61820
|
}
|
|
61351
61821
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
61352
61822
|
const refineCode = typeof result.code === "string" ? result.code : "";
|
|
61353
|
-
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";
|
|
61823
|
+
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";
|
|
61354
61824
|
const isTerminalSuccess = refineTerminalKind === "completed";
|
|
61355
61825
|
const blockerContext = isTerminalSuccess ? void 0 : (() => {
|
|
61356
61826
|
const code = typeof result.code === "string" ? result.code : refineTerminalKind;
|
|
@@ -61381,9 +61851,12 @@ async function finishMeshRefineJob(self, handle, args) {
|
|
|
61381
61851
|
}
|
|
61382
61852
|
if (stage === "validation" && result.validationSummary) {
|
|
61383
61853
|
const vs = result.validationSummary;
|
|
61854
|
+
const diagnostics = extractValidationFailureDiagnostics(vs);
|
|
61384
61855
|
ctx.details = {
|
|
61385
61856
|
failureCode: vs.failureCode,
|
|
61386
|
-
|
|
61857
|
+
failureKind: vs.failureKind,
|
|
61858
|
+
commandsRun: Array.isArray(vs.commandsRun) ? vs.commandsRun.length : void 0,
|
|
61859
|
+
...diagnostics ? { failure: diagnostics } : {}
|
|
61387
61860
|
};
|
|
61388
61861
|
}
|
|
61389
61862
|
return ctx;
|
|
@@ -62418,6 +62891,15 @@ var DaemonCommandRouter = class {
|
|
|
62418
62891
|
runningRefineBatchJobs = /* @__PURE__ */ new Map();
|
|
62419
62892
|
/** Terminal async batch Refinery jobs preserve the last batch outcome for late readers. */
|
|
62420
62893
|
terminalRefineBatchJobs = /* @__PURE__ */ new Map();
|
|
62894
|
+
/**
|
|
62895
|
+
* DS2: in-process refinement leases keyed by `${repoRoot}::${baseBranch}`. Serialize
|
|
62896
|
+
* the base-mutating window (candidate-SHA pin → merge → push) of concurrent single-node
|
|
62897
|
+
* refines that target the SAME base branch in the same repo, so two refines cannot both
|
|
62898
|
+
* validate against one baseHead and then race their merges (the base-movement race). The
|
|
62899
|
+
* batch path is already sequential, so this only matters for overlapping single-node
|
|
62900
|
+
* async jobs. Value = the meshId:nodeId job key holding the lease (for diagnostics).
|
|
62901
|
+
*/
|
|
62902
|
+
refineBaseLeases = /* @__PURE__ */ new Map();
|
|
62421
62903
|
constructor(deps) {
|
|
62422
62904
|
this.deps = deps;
|
|
62423
62905
|
}
|