@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.
@@ -45,6 +45,20 @@ export type CleanupLocalWorktreeNodeResult = {
45
45
  recoveryHint: string;
46
46
  convergence?: Record<string, unknown>;
47
47
  };
48
+ /**
49
+ * Result of the non-destructive local-worktree removability precheck. `ok:false`
50
+ * carries the same refusal `code`/`error`/`recoveryHint` that the destructive
51
+ * cleanup would have returned, so callers can refuse a removal BEFORE performing
52
+ * any irreversible step (e.g. stopping/deleting delegated sessions).
53
+ */
54
+ export type WorktreeRemovalPrecheckResult = {
55
+ ok: true;
56
+ } | {
57
+ ok: false;
58
+ code: string;
59
+ error: string;
60
+ recoveryHint: string;
61
+ };
48
62
  /**
49
63
  * Router-private collaborators injected at dispatch. Each is a bound method or
50
64
  * field of DaemonCommandRouter; handlers that don't need a given collaborator
@@ -90,6 +104,17 @@ export interface MedFamilyContext {
90
104
  nodeId: string;
91
105
  force?: boolean;
92
106
  }) => Promise<CleanupLocalWorktreeNodeResult>;
107
+ /**
108
+ * Bound `DaemonCommandRouter.precheckLocalWorktreeRemovable` — purely
109
+ * non-destructive validation of whether a local worktree node can be removed.
110
+ * Called BEFORE session cleanup so a refusal does not orphan the session.
111
+ */
112
+ precheckLocalWorktreeRemovable: (args: {
113
+ mesh: any;
114
+ node: any;
115
+ nodeId: string;
116
+ force?: boolean;
117
+ }) => Promise<WorktreeRemovalPrecheckResult>;
93
118
  /** Bound `DaemonCommandRouter.startMeshRefineJob` (async execute path). */
94
119
  startMeshRefineJob: (meshId: string, nodeId: string, args: any) => Promise<CommandRouterResult>;
95
120
  /** Bound `DaemonCommandRouter.batchRefineMeshNodes` (dry-run batch plan). */
@@ -248,6 +248,26 @@ export declare class DaemonCommandRouter {
248
248
  * to give handles time to release, and reports whether residue remains.
249
249
  */
250
250
  private bestEffortRemoveWorktreeDir;
251
+ /**
252
+ * Non-destructive precheck mirroring every REFUSAL condition in
253
+ * {@link cleanupLocalWorktreeNode} — missing workspace / source-repo / branch
254
+ * metadata, unexpected (non-managed) path, branch mismatch — PLUS the
255
+ * dirty-worktree guard that `removeWorktree(requireClean)` enforces
256
+ * (`git status --porcelain`). It performs ZERO destructive actions: no
257
+ * `git worktree remove`, no `git worktree prune`, no directory deletion.
258
+ *
259
+ * remove_mesh_node calls this BEFORE any session cleanup so that a refusal
260
+ * (the common one being a dirty worktree) does not first stop/delete the
261
+ * delegated session and orphan it — the original ordering bug. Success/skip
262
+ * cases that the real cleanup handles idempotently (worktree path already
263
+ * gone, git-de-registered residue) are NOT refusals and return `{ ok: true }`.
264
+ *
265
+ * `force:true` skips the dirty guard, preserving `removeWorktree`'s
266
+ * `requireClean: !force` semantics. This is a read-only superset check; the
267
+ * authoritative `requireClean` guard inside `removeWorktree` is intentionally
268
+ * kept as a second line of defense against a precheck→execute race.
269
+ */
270
+ private precheckLocalWorktreeRemovable;
251
271
  private cleanupLocalWorktreeNode;
252
272
  private getWorktreeForceCleanupConvergence;
253
273
  private isCompletedHostedSession;
package/dist/index.js CHANGED
@@ -389,10 +389,10 @@ function readInjected(value) {
389
389
  }
390
390
  function getDaemonBuildInfo() {
391
391
  if (cached) return cached;
392
- const commit = readInjected(true ? "00b0b1bba6930ac68b6c43e87795cbe63e59b323" : void 0) ?? "unknown";
393
- const commitShort = readInjected(true ? "00b0b1bb" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
394
- const version = readInjected(true ? "0.9.82-rc.419" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
395
- const builtAt = readInjected(true ? "2026-06-28T18:13:37.481Z" : void 0);
392
+ const commit = readInjected(true ? "bd19cb0fbe1e16db1d8d2fcd8c2c871aae3ce33b" : void 0) ?? "unknown";
393
+ const commitShort = readInjected(true ? "bd19cb0f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
394
+ const version = readInjected(true ? "0.9.82-rc.420" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
395
+ const builtAt = readInjected(true ? "2026-06-28T19:41:31.696Z" : void 0);
396
396
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
397
397
  return cached;
398
398
  }
@@ -10913,6 +10913,35 @@ function drainPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId, opts) {
10913
10913
  if (merged.length === 0) return [];
10914
10914
  return reconcilePendingMeshCoordinatorEvents(meshId, merged);
10915
10915
  }
10916
+ function retractPendingDispatchBlockedEvent(meshId, taskId, coordinatorDaemonId) {
10917
+ if (!meshId || !taskId) return 0;
10918
+ let removed = 0;
10919
+ const matchesTask = (event) => {
10920
+ if (!event || event.event !== "mesh:dispatch_blocked") return false;
10921
+ const rowTaskId = readNonEmptyString2(event.metadataEvent?.taskId);
10922
+ return rowTaskId === taskId;
10923
+ };
10924
+ try {
10925
+ const store = MeshRuntimeStore.getInstance();
10926
+ const ids = [];
10927
+ for (const row of store.peekPendingEvents(meshId)) {
10928
+ if (row.event !== "mesh:dispatch_blocked") continue;
10929
+ if (matchesTask(row.payload)) ids.push(row.id);
10930
+ }
10931
+ if (ids.length) removed += store.deletePendingEventsById(ids);
10932
+ } catch {
10933
+ }
10934
+ const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
10935
+ const primaryDaemonId = daemonIds[0];
10936
+ const paths = primaryDaemonId ? [getPendingEventsPath(meshId, primaryDaemonId), getPendingEventsPath(meshId)] : [getPendingEventsPath(meshId)];
10937
+ for (const path43 of paths) {
10938
+ try {
10939
+ removed += selectiveDrainFile(path43, matchesTask).length;
10940
+ } catch {
10941
+ }
10942
+ }
10943
+ return removed;
10944
+ }
10916
10945
  function getPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId) {
10917
10946
  if (!meshId) return [];
10918
10947
  const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
@@ -11965,6 +11994,41 @@ var init_mesh_warmup_deadline = __esm({
11965
11994
  }
11966
11995
  });
11967
11996
 
11997
+ // src/mesh/mesh-clone-grace.ts
11998
+ function normalizeNodeIdKey(nodeId) {
11999
+ return normalizeMeshNodeId({ id: nodeId ?? void 0 }) ?? "";
12000
+ }
12001
+ function noteRecentlyClonedNode(nodeId, nowMs = Date.now()) {
12002
+ const key2 = normalizeNodeIdKey(nodeId);
12003
+ if (!key2) return;
12004
+ recentlyClonedNodeExpiry.set(key2, nowMs + CLONE_BOOTSTRAP_GRACE_MS);
12005
+ if (recentlyClonedNodeExpiry.size > MAX_TRACKED_CLONED_NODES) {
12006
+ const oldest = recentlyClonedNodeExpiry.keys().next().value;
12007
+ if (oldest !== void 0) recentlyClonedNodeExpiry.delete(oldest);
12008
+ }
12009
+ }
12010
+ function isWithinCloneBootstrapGrace(nodeId, nowMs = Date.now()) {
12011
+ const key2 = normalizeNodeIdKey(nodeId);
12012
+ if (!key2) return false;
12013
+ const expiry = recentlyClonedNodeExpiry.get(key2);
12014
+ if (expiry === void 0) return false;
12015
+ if (nowMs >= expiry) {
12016
+ recentlyClonedNodeExpiry.delete(key2);
12017
+ return false;
12018
+ }
12019
+ return true;
12020
+ }
12021
+ var CLONE_BOOTSTRAP_GRACE_MS, recentlyClonedNodeExpiry, MAX_TRACKED_CLONED_NODES;
12022
+ var init_mesh_clone_grace = __esm({
12023
+ "src/mesh/mesh-clone-grace.ts"() {
12024
+ "use strict";
12025
+ init_dist();
12026
+ CLONE_BOOTSTRAP_GRACE_MS = 10 * 60 * 1e3;
12027
+ recentlyClonedNodeExpiry = /* @__PURE__ */ new Map();
12028
+ MAX_TRACKED_CLONED_NODES = 512;
12029
+ }
12030
+ });
12031
+
11968
12032
  // src/mesh/mesh-queue-assignment.ts
11969
12033
  function localCoordinatorDaemonId() {
11970
12034
  return canonicalDaemonId(readNonEmptyString2(loadConfig().machineId));
@@ -12128,6 +12192,7 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
12128
12192
  return false;
12129
12193
  }
12130
12194
  LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
12195
+ retractActionableSkipIfPreviouslyNotified(meshId, task.id);
12131
12196
  beginTaskDispatchInFlight(meshId, task.id);
12132
12197
  if (node?.daemonId && components.dispatchMeshCommand) {
12133
12198
  const isLocalNode = components.cliManager.adapters.has(sessionId);
@@ -12217,6 +12282,28 @@ function isActionableSkipReason(reason) {
12217
12282
  if (!reason) return false;
12218
12283
  return ACTIONABLE_SKIP_REASON_PREFIXES.some((prefix) => reason === prefix || reason.startsWith(prefix));
12219
12284
  }
12285
+ function isTargetNodeTransientlyUnresolved(mesh, task) {
12286
+ const targetNodeId = readNonEmptyString2(task.targetNodeId);
12287
+ if (!targetNodeId) return false;
12288
+ const node = Array.isArray(mesh?.nodes) ? mesh.nodes.find((n) => meshNodeIdMatches(n, targetNodeId)) : void 0;
12289
+ if (node && node.worktreeBootstrap?.status === "running" && !isWorktreeBootstrapStaleRunning(node)) {
12290
+ return true;
12291
+ }
12292
+ return isWithinCloneBootstrapGrace(targetNodeId);
12293
+ }
12294
+ function retractActionableSkipIfPreviouslyNotified(meshId, taskId) {
12295
+ const dedupKey = `${meshId}:${taskId}`;
12296
+ if (!lastActionableSkipNotified.delete(dedupKey)) return;
12297
+ try {
12298
+ const coordinatorDaemonId = readNonEmptyString2(loadConfig().machineId) || void 0;
12299
+ const removed = retractPendingDispatchBlockedEvent(meshId, taskId, coordinatorDaemonId);
12300
+ if (removed > 0) {
12301
+ LOG.info("MeshQueue", `Retracted ${removed} stale dispatch-blocked event(s) for task ${taskId} (mesh ${meshId}) \u2014 its blocker resolved`);
12302
+ }
12303
+ } catch (e) {
12304
+ LOG.warn("MeshQueue", `Failed to retract stale dispatch-blocked event for task ${taskId} (mesh ${meshId}): ${e?.message || e}`);
12305
+ }
12306
+ }
12220
12307
  function actionableSkipGuidance(reason) {
12221
12308
  if (reason === "target_node_id_unmatched") return {
12222
12309
  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)",
@@ -12249,6 +12336,7 @@ function actionableSkipGuidance(reason) {
12249
12336
  }
12250
12337
  function notifyCoordinatorOfActionableSkip(meshId, taskId, reason, nodeId) {
12251
12338
  if (!isActionableSkipReason(reason)) return;
12339
+ if (reason === "target_node_id_unmatched" && isWithinCloneBootstrapGrace(readNonEmptyString2(nodeId))) return;
12252
12340
  const dedupKey = `${meshId}:${taskId}`;
12253
12341
  if (lastActionableSkipNotified.get(dedupKey) === reason) return;
12254
12342
  lastActionableSkipNotified.set(dedupKey, reason);
@@ -12486,9 +12574,13 @@ function markAutoLaunch(meshId, taskId, args) {
12486
12574
  error: args.error
12487
12575
  });
12488
12576
  if (args.status === "skipped") {
12489
- notifyCoordinatorOfActionableSkip(meshId, taskId, args.reason, args.nodeId);
12577
+ if (isActionableSkipReason(args.reason)) {
12578
+ notifyCoordinatorOfActionableSkip(meshId, taskId, args.reason, args.nodeId);
12579
+ } else if (args.reason === TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON) {
12580
+ retractActionableSkipIfPreviouslyNotified(meshId, taskId);
12581
+ }
12490
12582
  } else {
12491
- lastActionableSkipNotified.delete(`${meshId}:${taskId}`);
12583
+ retractActionableSkipIfPreviouslyNotified(meshId, taskId);
12492
12584
  }
12493
12585
  }
12494
12586
  async function resolveUsableProvider(components, nodeId, node, requiredTags) {
@@ -12576,9 +12668,10 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
12576
12668
  const matched = mesh.nodes.filter((n) => !task.targetNodeId || meshNodeIdMatches(n, task.targetNodeId));
12577
12669
  return matched.length > 0 && matched.every((n) => n?.isLocalWorktree === true);
12578
12670
  })();
12671
+ const targetTransientlyUnresolved = targetPinUnmatched && isTargetNodeTransientlyUnresolved(mesh, task);
12579
12672
  markAutoLaunch(meshId, task.id, {
12580
12673
  status: "skipped",
12581
- reason: convergenceOntoWorktree ? "mesh_convergence_target_is_worktree" : targetPinUnmatched ? "target_node_id_unmatched" : "no_node_satisfies_required_tags",
12674
+ reason: convergenceOntoWorktree ? "mesh_convergence_target_is_worktree" : targetTransientlyUnresolved ? TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON : targetPinUnmatched ? "target_node_id_unmatched" : "no_node_satisfies_required_tags",
12582
12675
  nodeId: task.targetNodeId
12583
12676
  });
12584
12677
  continue;
@@ -12931,7 +13024,7 @@ function runIdleMaintenanceThenAssignQueue(components, args) {
12931
13024
  });
12932
13025
  });
12933
13026
  }
12934
- var import_fs13, 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;
13027
+ var import_fs13, 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;
12935
13028
  var init_mesh_queue_assignment = __esm({
12936
13029
  "src/mesh/mesh-queue-assignment.ts"() {
12937
13030
  "use strict";
@@ -12954,6 +13047,7 @@ var init_mesh_queue_assignment = __esm({
12954
13047
  init_mesh_events_utils();
12955
13048
  init_mesh_events_pending();
12956
13049
  init_worktree_bootstrap_config();
13050
+ init_mesh_clone_grace();
12957
13051
  init_mesh_task_inflight();
12958
13052
  IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
12959
13053
  idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
@@ -12980,6 +13074,7 @@ var init_mesh_queue_assignment = __esm({
12980
13074
  "provider_unusable",
12981
13075
  "dirty_workspace"
12982
13076
  ];
13077
+ TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON = "target_node_bootstrap_pending";
12983
13078
  lastActionableSkipNotified = /* @__PURE__ */ new Map();
12984
13079
  }
12985
13080
  });
@@ -48986,6 +49081,7 @@ init_dist();
48986
49081
  init_mesh_host_ownership();
48987
49082
  init_worktree_bootstrap_config();
48988
49083
  init_mesh_events();
49084
+ init_mesh_clone_grace();
48989
49085
  init_config();
48990
49086
  async function decideOssCloneSync(ossCtx, worktreeOssSha, sourceSha, rg) {
48991
49087
  if (!worktreeOssSha || !sourceSha || worktreeOssSha === sourceSha) return "noop";
@@ -49267,6 +49363,26 @@ var meshCrudHandlers = {
49267
49363
  explicitCleanupMode ?? (node?.isLocalWorktree === true ? "stop_and_delete" : void 0) ?? mesh?.policy?.sessionCleanupOnNodeRemove
49268
49364
  );
49269
49365
  const explicitSessionIds = Array.isArray(args?.sessionIds) ? args.sessionIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
49366
+ if (node?.isLocalWorktree) {
49367
+ const nodeDaemonId = typeof node.daemonId === "string" ? node.daemonId.trim() : void 0;
49368
+ const isRemoteWorktree = nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, ctx.deps.statusInstanceId) && ctx.deps.dispatchMeshCommand && !args?._meshDirectDispatch;
49369
+ if (!isRemoteWorktree) {
49370
+ const precheck = await ctx.precheckLocalWorktreeRemovable({ mesh, node, nodeId, force: args?.force === true });
49371
+ if (precheck.ok === false) {
49372
+ return {
49373
+ success: false,
49374
+ removed: false,
49375
+ code: precheck.code,
49376
+ error: precheck.error,
49377
+ recoveryHint: precheck.recoveryHint,
49378
+ // No sessionCleanup key: the session was deliberately NOT
49379
+ // touched. worktreeCleanup mirrors the destructive path's
49380
+ // refusal shape so existing callers see the same code.
49381
+ worktreeCleanup: { success: false, code: precheck.code, error: precheck.error, recoveryHint: precheck.recoveryHint }
49382
+ };
49383
+ }
49384
+ }
49385
+ }
49270
49386
  let sessionCleanup;
49271
49387
  if (node && sessionCleanupMode !== "preserve") {
49272
49388
  sessionCleanup = await ctx.cleanupMeshSessions({
@@ -49377,6 +49493,8 @@ var meshCrudHandlers = {
49377
49493
  ...typeof args === "object" && args !== null ? args : {},
49378
49494
  _meshDirectDispatch: true
49379
49495
  });
49496
+ const forwardedNodeId = forwarded?.node?.id;
49497
+ if (typeof forwardedNodeId === "string" && forwardedNodeId) noteRecentlyClonedNode(forwardedNodeId);
49380
49498
  return forwarded ?? { success: false, error: "no response from remote node" };
49381
49499
  }
49382
49500
  const repoRoot = sourceNode.repoRoot || sourceNode.workspace;
@@ -49426,6 +49544,7 @@ var meshCrudHandlers = {
49426
49544
  if (inlineForReconcile) ctx.updateInlineMeshNode(meshId, inlineForReconcile, node);
49427
49545
  ctx.invalidateAggregateMeshStatus(meshId);
49428
49546
  }
49547
+ if (typeof node?.id === "string" && node.id) noteRecentlyClonedNode(node.id);
49429
49548
  const persistWorktreeSetupState = async (bootstrapState2) => {
49430
49549
  node.worktreeBootstrap = bootstrapState2;
49431
49550
  if (meshRecord.inline) {
@@ -54451,6 +54570,7 @@ var DaemonCommandRouter = class {
54451
54570
  normalizeMeshSessionCleanupMode: this.normalizeMeshSessionCleanupMode.bind(this),
54452
54571
  cleanupMeshSessions: this.cleanupMeshSessions.bind(this),
54453
54572
  cleanupLocalWorktreeNode: this.cleanupLocalWorktreeNode.bind(this),
54573
+ precheckLocalWorktreeRemovable: this.precheckLocalWorktreeRemovable.bind(this),
54454
54574
  startMeshRefineJob: this.startMeshRefineJob.bind(this),
54455
54575
  batchRefineMeshNodes: this.batchRefineMeshNodes.bind(this),
54456
54576
  startMeshRefineBatchJob: this.startMeshRefineBatchJob.bind(this),
@@ -54667,6 +54787,111 @@ var DaemonCommandRouter = class {
54667
54787
  }
54668
54788
  return fs32.existsSync(dir) ? { removed: false, residue: true, error: String(lastErr?.message || lastErr || "unknown rm error") } : { removed: true, residue: false };
54669
54789
  }
54790
+ /**
54791
+ * Non-destructive precheck mirroring every REFUSAL condition in
54792
+ * {@link cleanupLocalWorktreeNode} — missing workspace / source-repo / branch
54793
+ * metadata, unexpected (non-managed) path, branch mismatch — PLUS the
54794
+ * dirty-worktree guard that `removeWorktree(requireClean)` enforces
54795
+ * (`git status --porcelain`). It performs ZERO destructive actions: no
54796
+ * `git worktree remove`, no `git worktree prune`, no directory deletion.
54797
+ *
54798
+ * remove_mesh_node calls this BEFORE any session cleanup so that a refusal
54799
+ * (the common one being a dirty worktree) does not first stop/delete the
54800
+ * delegated session and orphan it — the original ordering bug. Success/skip
54801
+ * cases that the real cleanup handles idempotently (worktree path already
54802
+ * gone, git-de-registered residue) are NOT refusals and return `{ ok: true }`.
54803
+ *
54804
+ * `force:true` skips the dirty guard, preserving `removeWorktree`'s
54805
+ * `requireClean: !force` semantics. This is a read-only superset check; the
54806
+ * authoritative `requireClean` guard inside `removeWorktree` is intentionally
54807
+ * kept as a second line of defense against a precheck→execute race.
54808
+ */
54809
+ async precheckLocalWorktreeRemovable(args) {
54810
+ const sessionPreservedNote = " The delegated session was left running (not stopped) \u2014 resolve the issue and retry mesh_remove_node.";
54811
+ const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
54812
+ if (!workspace) {
54813
+ return {
54814
+ ok: false,
54815
+ code: "mesh_worktree_cleanup_missing_workspace",
54816
+ error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
54817
+ recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains." + sessionPreservedNote
54818
+ };
54819
+ }
54820
+ if (!fs32.existsSync(workspace)) return { ok: true };
54821
+ const sourceNode = args.node?.clonedFromNodeId ? args.mesh?.nodes?.find((n) => meshNodeIdMatches(n, args.node.clonedFromNodeId)) : args.mesh?.nodes?.find((n) => !n.isLocalWorktree);
54822
+ const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
54823
+ if (!repoRoot || !fs32.existsSync(repoRoot)) {
54824
+ return {
54825
+ ok: false,
54826
+ code: "mesh_worktree_cleanup_missing_source_repo",
54827
+ error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
54828
+ recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying." + sessionPreservedNote
54829
+ };
54830
+ }
54831
+ if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
54832
+ return {
54833
+ ok: false,
54834
+ code: "mesh_worktree_cleanup_missing_branch",
54835
+ error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
54836
+ recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata." + sessionPreservedNote
54837
+ };
54838
+ }
54839
+ const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
54840
+ const normalizePath = (value) => {
54841
+ const resolved = (0, import_path16.resolve)(value);
54842
+ try {
54843
+ return fs32.realpathSync(resolved);
54844
+ } catch {
54845
+ return resolved;
54846
+ }
54847
+ };
54848
+ const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
54849
+ const actualPath = normalizePath(workspace);
54850
+ if (actualPath !== expectedPath) {
54851
+ return {
54852
+ ok: false,
54853
+ code: "mesh_worktree_cleanup_unexpected_path",
54854
+ error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
54855
+ 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
54856
+ };
54857
+ }
54858
+ const entries = await listWorktrees2(repoRoot);
54859
+ const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
54860
+ if (!managedEntry) return { ok: true };
54861
+ if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
54862
+ return {
54863
+ ok: false,
54864
+ code: "mesh_worktree_cleanup_branch_mismatch",
54865
+ error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
54866
+ recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup." + sessionPreservedNote
54867
+ };
54868
+ }
54869
+ if (args.force !== true) {
54870
+ const { execFile: execFile5 } = await import("child_process");
54871
+ const { promisify: promisify8 } = await import("util");
54872
+ const execFileAsync4 = promisify8(execFile5);
54873
+ try {
54874
+ const { stdout } = await execFileAsync4("git", ["status", "--porcelain"], {
54875
+ cwd: workspace,
54876
+ encoding: "utf8",
54877
+ timeout: 3e4,
54878
+ maxBuffer: 4 * 1024 * 1024,
54879
+ windowsHide: true
54880
+ });
54881
+ if (stdout.trim()) {
54882
+ return {
54883
+ ok: false,
54884
+ code: "mesh_worktree_cleanup_dirty",
54885
+ error: `Refusing to remove dirty worktree: ${workspace}`,
54886
+ 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
54887
+ };
54888
+ }
54889
+ } catch {
54890
+ return { ok: true };
54891
+ }
54892
+ }
54893
+ return { ok: true };
54894
+ }
54670
54895
  async cleanupLocalWorktreeNode(args) {
54671
54896
  const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
54672
54897
  if (!workspace) {