@adhdev/daemon-core 0.9.82-rc.419 → 0.9.82-rc.420

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.mjs CHANGED
@@ -384,10 +384,10 @@ function readInjected(value) {
384
384
  }
385
385
  function getDaemonBuildInfo() {
386
386
  if (cached) return cached;
387
- const commit = readInjected(true ? "00b0b1bba6930ac68b6c43e87795cbe63e59b323" : void 0) ?? "unknown";
388
- const commitShort = readInjected(true ? "00b0b1bb" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
389
- const version = readInjected(true ? "0.9.82-rc.419" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
390
- const builtAt = readInjected(true ? "2026-06-28T18:13:37.481Z" : void 0);
387
+ const commit = readInjected(true ? "bd19cb0fbe1e16db1d8d2fcd8c2c871aae3ce33b" : void 0) ?? "unknown";
388
+ const commitShort = readInjected(true ? "bd19cb0f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
389
+ const version = readInjected(true ? "0.9.82-rc.420" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
390
+ const builtAt = readInjected(true ? "2026-06-28T19:41:31.696Z" : void 0);
391
391
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
392
392
  return cached;
393
393
  }
@@ -10909,6 +10909,35 @@ function drainPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId, opts) {
10909
10909
  if (merged.length === 0) return [];
10910
10910
  return reconcilePendingMeshCoordinatorEvents(meshId, merged);
10911
10911
  }
10912
+ function retractPendingDispatchBlockedEvent(meshId, taskId, coordinatorDaemonId) {
10913
+ if (!meshId || !taskId) return 0;
10914
+ let removed = 0;
10915
+ const matchesTask = (event) => {
10916
+ if (!event || event.event !== "mesh:dispatch_blocked") return false;
10917
+ const rowTaskId = readNonEmptyString2(event.metadataEvent?.taskId);
10918
+ return rowTaskId === taskId;
10919
+ };
10920
+ try {
10921
+ const store = MeshRuntimeStore.getInstance();
10922
+ const ids = [];
10923
+ for (const row of store.peekPendingEvents(meshId)) {
10924
+ if (row.event !== "mesh:dispatch_blocked") continue;
10925
+ if (matchesTask(row.payload)) ids.push(row.id);
10926
+ }
10927
+ if (ids.length) removed += store.deletePendingEventsById(ids);
10928
+ } catch {
10929
+ }
10930
+ const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
10931
+ const primaryDaemonId = daemonIds[0];
10932
+ const paths = primaryDaemonId ? [getPendingEventsPath(meshId, primaryDaemonId), getPendingEventsPath(meshId)] : [getPendingEventsPath(meshId)];
10933
+ for (const path43 of paths) {
10934
+ try {
10935
+ removed += selectiveDrainFile(path43, matchesTask).length;
10936
+ } catch {
10937
+ }
10938
+ }
10939
+ return removed;
10940
+ }
10912
10941
  function getPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId) {
10913
10942
  if (!meshId) return [];
10914
10943
  const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
@@ -11960,6 +11989,41 @@ var init_mesh_warmup_deadline = __esm({
11960
11989
  }
11961
11990
  });
11962
11991
 
11992
+ // src/mesh/mesh-clone-grace.ts
11993
+ function normalizeNodeIdKey(nodeId) {
11994
+ return normalizeMeshNodeId({ id: nodeId ?? void 0 }) ?? "";
11995
+ }
11996
+ function noteRecentlyClonedNode(nodeId, nowMs = Date.now()) {
11997
+ const key2 = normalizeNodeIdKey(nodeId);
11998
+ if (!key2) return;
11999
+ recentlyClonedNodeExpiry.set(key2, nowMs + CLONE_BOOTSTRAP_GRACE_MS);
12000
+ if (recentlyClonedNodeExpiry.size > MAX_TRACKED_CLONED_NODES) {
12001
+ const oldest = recentlyClonedNodeExpiry.keys().next().value;
12002
+ if (oldest !== void 0) recentlyClonedNodeExpiry.delete(oldest);
12003
+ }
12004
+ }
12005
+ function isWithinCloneBootstrapGrace(nodeId, nowMs = Date.now()) {
12006
+ const key2 = normalizeNodeIdKey(nodeId);
12007
+ if (!key2) return false;
12008
+ const expiry = recentlyClonedNodeExpiry.get(key2);
12009
+ if (expiry === void 0) return false;
12010
+ if (nowMs >= expiry) {
12011
+ recentlyClonedNodeExpiry.delete(key2);
12012
+ return false;
12013
+ }
12014
+ return true;
12015
+ }
12016
+ var CLONE_BOOTSTRAP_GRACE_MS, recentlyClonedNodeExpiry, MAX_TRACKED_CLONED_NODES;
12017
+ var init_mesh_clone_grace = __esm({
12018
+ "src/mesh/mesh-clone-grace.ts"() {
12019
+ "use strict";
12020
+ init_dist();
12021
+ CLONE_BOOTSTRAP_GRACE_MS = 10 * 60 * 1e3;
12022
+ recentlyClonedNodeExpiry = /* @__PURE__ */ new Map();
12023
+ MAX_TRACKED_CLONED_NODES = 512;
12024
+ }
12025
+ });
12026
+
11963
12027
  // src/mesh/mesh-queue-assignment.ts
11964
12028
  import { existsSync as existsSync17 } from "fs";
11965
12029
  function localCoordinatorDaemonId() {
@@ -12124,6 +12188,7 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
12124
12188
  return false;
12125
12189
  }
12126
12190
  LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
12191
+ retractActionableSkipIfPreviouslyNotified(meshId, task.id);
12127
12192
  beginTaskDispatchInFlight(meshId, task.id);
12128
12193
  if (node?.daemonId && components.dispatchMeshCommand) {
12129
12194
  const isLocalNode = components.cliManager.adapters.has(sessionId);
@@ -12213,6 +12278,28 @@ function isActionableSkipReason(reason) {
12213
12278
  if (!reason) return false;
12214
12279
  return ACTIONABLE_SKIP_REASON_PREFIXES.some((prefix) => reason === prefix || reason.startsWith(prefix));
12215
12280
  }
12281
+ function isTargetNodeTransientlyUnresolved(mesh, task) {
12282
+ const targetNodeId = readNonEmptyString2(task.targetNodeId);
12283
+ if (!targetNodeId) return false;
12284
+ const node = Array.isArray(mesh?.nodes) ? mesh.nodes.find((n) => meshNodeIdMatches(n, targetNodeId)) : void 0;
12285
+ if (node && node.worktreeBootstrap?.status === "running" && !isWorktreeBootstrapStaleRunning(node)) {
12286
+ return true;
12287
+ }
12288
+ return isWithinCloneBootstrapGrace(targetNodeId);
12289
+ }
12290
+ function retractActionableSkipIfPreviouslyNotified(meshId, taskId) {
12291
+ const dedupKey = `${meshId}:${taskId}`;
12292
+ if (!lastActionableSkipNotified.delete(dedupKey)) return;
12293
+ try {
12294
+ const coordinatorDaemonId = readNonEmptyString2(loadConfig().machineId) || void 0;
12295
+ const removed = retractPendingDispatchBlockedEvent(meshId, taskId, coordinatorDaemonId);
12296
+ if (removed > 0) {
12297
+ LOG.info("MeshQueue", `Retracted ${removed} stale dispatch-blocked event(s) for task ${taskId} (mesh ${meshId}) \u2014 its blocker resolved`);
12298
+ }
12299
+ } catch (e) {
12300
+ LOG.warn("MeshQueue", `Failed to retract stale dispatch-blocked event for task ${taskId} (mesh ${meshId}): ${e?.message || e}`);
12301
+ }
12302
+ }
12216
12303
  function actionableSkipGuidance(reason) {
12217
12304
  if (reason === "target_node_id_unmatched") return {
12218
12305
  summary: "it is pinned to a target node id that matches no node in the mesh (the node may have been removed, or its id form does not resolve)",
@@ -12245,6 +12332,7 @@ function actionableSkipGuidance(reason) {
12245
12332
  }
12246
12333
  function notifyCoordinatorOfActionableSkip(meshId, taskId, reason, nodeId) {
12247
12334
  if (!isActionableSkipReason(reason)) return;
12335
+ if (reason === "target_node_id_unmatched" && isWithinCloneBootstrapGrace(readNonEmptyString2(nodeId))) return;
12248
12336
  const dedupKey = `${meshId}:${taskId}`;
12249
12337
  if (lastActionableSkipNotified.get(dedupKey) === reason) return;
12250
12338
  lastActionableSkipNotified.set(dedupKey, reason);
@@ -12482,9 +12570,13 @@ function markAutoLaunch(meshId, taskId, args) {
12482
12570
  error: args.error
12483
12571
  });
12484
12572
  if (args.status === "skipped") {
12485
- notifyCoordinatorOfActionableSkip(meshId, taskId, args.reason, args.nodeId);
12573
+ if (isActionableSkipReason(args.reason)) {
12574
+ notifyCoordinatorOfActionableSkip(meshId, taskId, args.reason, args.nodeId);
12575
+ } else if (args.reason === TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON) {
12576
+ retractActionableSkipIfPreviouslyNotified(meshId, taskId);
12577
+ }
12486
12578
  } else {
12487
- lastActionableSkipNotified.delete(`${meshId}:${taskId}`);
12579
+ retractActionableSkipIfPreviouslyNotified(meshId, taskId);
12488
12580
  }
12489
12581
  }
12490
12582
  async function resolveUsableProvider(components, nodeId, node, requiredTags) {
@@ -12572,9 +12664,10 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
12572
12664
  const matched = mesh.nodes.filter((n) => !task.targetNodeId || meshNodeIdMatches(n, task.targetNodeId));
12573
12665
  return matched.length > 0 && matched.every((n) => n?.isLocalWorktree === true);
12574
12666
  })();
12667
+ const targetTransientlyUnresolved = targetPinUnmatched && isTargetNodeTransientlyUnresolved(mesh, task);
12575
12668
  markAutoLaunch(meshId, task.id, {
12576
12669
  status: "skipped",
12577
- reason: convergenceOntoWorktree ? "mesh_convergence_target_is_worktree" : targetPinUnmatched ? "target_node_id_unmatched" : "no_node_satisfies_required_tags",
12670
+ reason: convergenceOntoWorktree ? "mesh_convergence_target_is_worktree" : targetTransientlyUnresolved ? TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON : targetPinUnmatched ? "target_node_id_unmatched" : "no_node_satisfies_required_tags",
12578
12671
  nodeId: task.targetNodeId
12579
12672
  });
12580
12673
  continue;
@@ -12927,7 +13020,7 @@ function runIdleMaintenanceThenAssignQueue(components, args) {
12927
13020
  });
12928
13021
  });
12929
13022
  }
12930
- var IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, DISPATCH_CONFIRM_TIMEOUT_MS, DISPATCH_CONNECT_TIMEOUT_MS, dispatchWarmupGetterMissingWarned, LOCAL_LAUNCH_READY_TIMEOUT_MS, LOCAL_LAUNCH_READY_POLL_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, AUTO_LAUNCH_AWAIT_CLAIM_MS, lastAutoLaunchLedgerKey, AUTO_LAUNCH_LEDGER_DEDUP_MAX, ACTIONABLE_SKIP_REASON_PREFIXES, lastActionableSkipNotified;
13023
+ var IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, DISPATCH_CONFIRM_TIMEOUT_MS, DISPATCH_CONNECT_TIMEOUT_MS, dispatchWarmupGetterMissingWarned, LOCAL_LAUNCH_READY_TIMEOUT_MS, LOCAL_LAUNCH_READY_POLL_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, AUTO_LAUNCH_AWAIT_CLAIM_MS, lastAutoLaunchLedgerKey, AUTO_LAUNCH_LEDGER_DEDUP_MAX, ACTIONABLE_SKIP_REASON_PREFIXES, TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON, lastActionableSkipNotified;
12931
13024
  var init_mesh_queue_assignment = __esm({
12932
13025
  "src/mesh/mesh-queue-assignment.ts"() {
12933
13026
  "use strict";
@@ -12949,6 +13042,7 @@ var init_mesh_queue_assignment = __esm({
12949
13042
  init_mesh_events_utils();
12950
13043
  init_mesh_events_pending();
12951
13044
  init_worktree_bootstrap_config();
13045
+ init_mesh_clone_grace();
12952
13046
  init_mesh_task_inflight();
12953
13047
  IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
12954
13048
  idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
@@ -12975,6 +13069,7 @@ var init_mesh_queue_assignment = __esm({
12975
13069
  "provider_unusable",
12976
13070
  "dirty_workspace"
12977
13071
  ];
13072
+ TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON = "target_node_bootstrap_pending";
12978
13073
  lastActionableSkipNotified = /* @__PURE__ */ new Map();
12979
13074
  }
12980
13075
  });
@@ -48596,6 +48691,7 @@ init_dist();
48596
48691
  init_mesh_host_ownership();
48597
48692
  init_worktree_bootstrap_config();
48598
48693
  init_mesh_events();
48694
+ init_mesh_clone_grace();
48599
48695
  init_config();
48600
48696
  async function decideOssCloneSync(ossCtx, worktreeOssSha, sourceSha, rg) {
48601
48697
  if (!worktreeOssSha || !sourceSha || worktreeOssSha === sourceSha) return "noop";
@@ -48877,6 +48973,26 @@ var meshCrudHandlers = {
48877
48973
  explicitCleanupMode ?? (node?.isLocalWorktree === true ? "stop_and_delete" : void 0) ?? mesh?.policy?.sessionCleanupOnNodeRemove
48878
48974
  );
48879
48975
  const explicitSessionIds = Array.isArray(args?.sessionIds) ? args.sessionIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
48976
+ if (node?.isLocalWorktree) {
48977
+ const nodeDaemonId = typeof node.daemonId === "string" ? node.daemonId.trim() : void 0;
48978
+ const isRemoteWorktree = nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, ctx.deps.statusInstanceId) && ctx.deps.dispatchMeshCommand && !args?._meshDirectDispatch;
48979
+ if (!isRemoteWorktree) {
48980
+ const precheck = await ctx.precheckLocalWorktreeRemovable({ mesh, node, nodeId, force: args?.force === true });
48981
+ if (precheck.ok === false) {
48982
+ return {
48983
+ success: false,
48984
+ removed: false,
48985
+ code: precheck.code,
48986
+ error: precheck.error,
48987
+ recoveryHint: precheck.recoveryHint,
48988
+ // No sessionCleanup key: the session was deliberately NOT
48989
+ // touched. worktreeCleanup mirrors the destructive path's
48990
+ // refusal shape so existing callers see the same code.
48991
+ worktreeCleanup: { success: false, code: precheck.code, error: precheck.error, recoveryHint: precheck.recoveryHint }
48992
+ };
48993
+ }
48994
+ }
48995
+ }
48880
48996
  let sessionCleanup;
48881
48997
  if (node && sessionCleanupMode !== "preserve") {
48882
48998
  sessionCleanup = await ctx.cleanupMeshSessions({
@@ -48987,6 +49103,8 @@ var meshCrudHandlers = {
48987
49103
  ...typeof args === "object" && args !== null ? args : {},
48988
49104
  _meshDirectDispatch: true
48989
49105
  });
49106
+ const forwardedNodeId = forwarded?.node?.id;
49107
+ if (typeof forwardedNodeId === "string" && forwardedNodeId) noteRecentlyClonedNode(forwardedNodeId);
48990
49108
  return forwarded ?? { success: false, error: "no response from remote node" };
48991
49109
  }
48992
49110
  const repoRoot = sourceNode.repoRoot || sourceNode.workspace;
@@ -49036,6 +49154,7 @@ var meshCrudHandlers = {
49036
49154
  if (inlineForReconcile) ctx.updateInlineMeshNode(meshId, inlineForReconcile, node);
49037
49155
  ctx.invalidateAggregateMeshStatus(meshId);
49038
49156
  }
49157
+ if (typeof node?.id === "string" && node.id) noteRecentlyClonedNode(node.id);
49039
49158
  const persistWorktreeSetupState = async (bootstrapState2) => {
49040
49159
  node.worktreeBootstrap = bootstrapState2;
49041
49160
  if (meshRecord.inline) {
@@ -54061,6 +54180,7 @@ var DaemonCommandRouter = class {
54061
54180
  normalizeMeshSessionCleanupMode: this.normalizeMeshSessionCleanupMode.bind(this),
54062
54181
  cleanupMeshSessions: this.cleanupMeshSessions.bind(this),
54063
54182
  cleanupLocalWorktreeNode: this.cleanupLocalWorktreeNode.bind(this),
54183
+ precheckLocalWorktreeRemovable: this.precheckLocalWorktreeRemovable.bind(this),
54064
54184
  startMeshRefineJob: this.startMeshRefineJob.bind(this),
54065
54185
  batchRefineMeshNodes: this.batchRefineMeshNodes.bind(this),
54066
54186
  startMeshRefineBatchJob: this.startMeshRefineBatchJob.bind(this),
@@ -54277,6 +54397,111 @@ var DaemonCommandRouter = class {
54277
54397
  }
54278
54398
  return fs32.existsSync(dir) ? { removed: false, residue: true, error: String(lastErr?.message || lastErr || "unknown rm error") } : { removed: true, residue: false };
54279
54399
  }
54400
+ /**
54401
+ * Non-destructive precheck mirroring every REFUSAL condition in
54402
+ * {@link cleanupLocalWorktreeNode} — missing workspace / source-repo / branch
54403
+ * metadata, unexpected (non-managed) path, branch mismatch — PLUS the
54404
+ * dirty-worktree guard that `removeWorktree(requireClean)` enforces
54405
+ * (`git status --porcelain`). It performs ZERO destructive actions: no
54406
+ * `git worktree remove`, no `git worktree prune`, no directory deletion.
54407
+ *
54408
+ * remove_mesh_node calls this BEFORE any session cleanup so that a refusal
54409
+ * (the common one being a dirty worktree) does not first stop/delete the
54410
+ * delegated session and orphan it — the original ordering bug. Success/skip
54411
+ * cases that the real cleanup handles idempotently (worktree path already
54412
+ * gone, git-de-registered residue) are NOT refusals and return `{ ok: true }`.
54413
+ *
54414
+ * `force:true` skips the dirty guard, preserving `removeWorktree`'s
54415
+ * `requireClean: !force` semantics. This is a read-only superset check; the
54416
+ * authoritative `requireClean` guard inside `removeWorktree` is intentionally
54417
+ * kept as a second line of defense against a precheck→execute race.
54418
+ */
54419
+ async precheckLocalWorktreeRemovable(args) {
54420
+ const sessionPreservedNote = " The delegated session was left running (not stopped) \u2014 resolve the issue and retry mesh_remove_node.";
54421
+ const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
54422
+ if (!workspace) {
54423
+ return {
54424
+ ok: false,
54425
+ code: "mesh_worktree_cleanup_missing_workspace",
54426
+ error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
54427
+ recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains." + sessionPreservedNote
54428
+ };
54429
+ }
54430
+ if (!fs32.existsSync(workspace)) return { ok: true };
54431
+ const sourceNode = args.node?.clonedFromNodeId ? args.mesh?.nodes?.find((n) => meshNodeIdMatches(n, args.node.clonedFromNodeId)) : args.mesh?.nodes?.find((n) => !n.isLocalWorktree);
54432
+ const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
54433
+ if (!repoRoot || !fs32.existsSync(repoRoot)) {
54434
+ return {
54435
+ ok: false,
54436
+ code: "mesh_worktree_cleanup_missing_source_repo",
54437
+ error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
54438
+ recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying." + sessionPreservedNote
54439
+ };
54440
+ }
54441
+ if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
54442
+ return {
54443
+ ok: false,
54444
+ code: "mesh_worktree_cleanup_missing_branch",
54445
+ error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
54446
+ recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata." + sessionPreservedNote
54447
+ };
54448
+ }
54449
+ const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
54450
+ const normalizePath = (value) => {
54451
+ const resolved = pathResolve4(value);
54452
+ try {
54453
+ return fs32.realpathSync(resolved);
54454
+ } catch {
54455
+ return resolved;
54456
+ }
54457
+ };
54458
+ const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
54459
+ const actualPath = normalizePath(workspace);
54460
+ if (actualPath !== expectedPath) {
54461
+ return {
54462
+ ok: false,
54463
+ code: "mesh_worktree_cleanup_unexpected_path",
54464
+ error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
54465
+ recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree." + sessionPreservedNote
54466
+ };
54467
+ }
54468
+ const entries = await listWorktrees2(repoRoot);
54469
+ const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
54470
+ if (!managedEntry) return { ok: true };
54471
+ if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
54472
+ return {
54473
+ ok: false,
54474
+ code: "mesh_worktree_cleanup_branch_mismatch",
54475
+ error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
54476
+ recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup." + sessionPreservedNote
54477
+ };
54478
+ }
54479
+ if (args.force !== true) {
54480
+ const { execFile: execFile5 } = await import("child_process");
54481
+ const { promisify: promisify8 } = await import("util");
54482
+ const execFileAsync4 = promisify8(execFile5);
54483
+ try {
54484
+ const { stdout } = await execFileAsync4("git", ["status", "--porcelain"], {
54485
+ cwd: workspace,
54486
+ encoding: "utf8",
54487
+ timeout: 3e4,
54488
+ maxBuffer: 4 * 1024 * 1024,
54489
+ windowsHide: true
54490
+ });
54491
+ if (stdout.trim()) {
54492
+ return {
54493
+ ok: false,
54494
+ code: "mesh_worktree_cleanup_dirty",
54495
+ error: `Refusing to remove dirty worktree: ${workspace}`,
54496
+ recoveryHint: "Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe." + sessionPreservedNote
54497
+ };
54498
+ }
54499
+ } catch {
54500
+ return { ok: true };
54501
+ }
54502
+ }
54503
+ return { ok: true };
54504
+ }
54280
54505
  async cleanupLocalWorktreeNode(args) {
54281
54506
  const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
54282
54507
  if (!workspace) {