@adhdev/daemon-core 0.9.82-rc.386 → 0.9.82-rc.387

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.
@@ -75,3 +75,25 @@ export declare function listWorktrees(repoRoot: string): Promise<WorktreeEntry[]
75
75
  * Parse `git worktree list --porcelain` output into structured entries.
76
76
  */
77
77
  export declare function parseWorktreeListOutput(output: string): WorktreeEntry[];
78
+ export interface BranchRefDeleteResult {
79
+ /** True if the branch ref no longer exists after this call. */
80
+ deleted: boolean;
81
+ /** Why it was (not) deleted, for surfacing in the cleanup result. */
82
+ reason: string;
83
+ /** True when a forced delete (`-D`) was needed (e.g. squash/patch-equivalent merge). */
84
+ forced?: boolean;
85
+ }
86
+ /**
87
+ * Delete a local branch ref after its worktree was removed.
88
+ *
89
+ * SAFETY: this is only meant to be called once the caller has independently
90
+ * verified that the branch is fully merged / its content is contained in the
91
+ * default ref (no work loss). It first tries the safe `git branch -d`, which
92
+ * refuses to delete a branch git itself does not consider merged. If
93
+ * `safeDeleteOnly` is false (the caller proved containment by patch-equivalence,
94
+ * which `-d` cannot see), it falls back to `git branch -D`. When the branch is
95
+ * already gone, this reports `deleted: true` idempotently.
96
+ */
97
+ export declare function deleteBranchRef(repoRoot: string, branch: string, opts?: {
98
+ safeDeleteOnly?: boolean;
99
+ }): Promise<BranchRefDeleteResult>;
package/dist/index.js CHANGED
@@ -383,10 +383,10 @@ function readInjected(value) {
383
383
  }
384
384
  function getDaemonBuildInfo() {
385
385
  if (cached) return cached;
386
- const commit = readInjected(true ? "fad1d175ca84a75f52bd0c1d88125fbb783ad241" : void 0) ?? "unknown";
387
- const commitShort = readInjected(true ? "fad1d175" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
388
- const version = readInjected(true ? "0.9.82-rc.386" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
389
- const builtAt = readInjected(true ? "2026-06-26T00:12:44.100Z" : void 0);
386
+ const commit = readInjected(true ? "057d5def5d55af124dfe910ee9accdf244576c14" : void 0) ?? "unknown";
387
+ const commitShort = readInjected(true ? "057d5def" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
388
+ const version = readInjected(true ? "0.9.82-rc.387" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
389
+ const builtAt = readInjected(true ? "2026-06-26T02:21:27.575Z" : void 0);
390
390
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
391
391
  return cached;
392
392
  }
@@ -1413,6 +1413,7 @@ var init_git_diff = __esm({
1413
1413
  var git_worktree_exports = {};
1414
1414
  __export(git_worktree_exports, {
1415
1415
  createWorktree: () => createWorktree,
1416
+ deleteBranchRef: () => deleteBranchRef,
1416
1417
  listWorktrees: () => listWorktrees,
1417
1418
  parseWorktreeListOutput: () => parseWorktreeListOutput,
1418
1419
  removeWorktree: () => removeWorktree,
@@ -1555,6 +1556,53 @@ function parseWorktreeListOutput(output) {
1555
1556
  }
1556
1557
  return entries;
1557
1558
  }
1559
+ async function deleteBranchRef(repoRoot, branch, opts = {}) {
1560
+ const name = (branch || "").trim();
1561
+ if (!name) return { deleted: false, reason: "empty_branch_name" };
1562
+ try {
1563
+ await execFileAsync2("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${name}`], {
1564
+ cwd: repoRoot,
1565
+ encoding: "utf8",
1566
+ timeout: GIT_TIMEOUT_MS,
1567
+ maxBuffer: GIT_MAX_BUFFER,
1568
+ windowsHide: true
1569
+ });
1570
+ } catch {
1571
+ return { deleted: true, reason: "branch_ref_absent" };
1572
+ }
1573
+ try {
1574
+ await execFileAsync2("git", ["branch", "-d", name], {
1575
+ cwd: repoRoot,
1576
+ encoding: "utf8",
1577
+ timeout: GIT_TIMEOUT_MS,
1578
+ maxBuffer: GIT_MAX_BUFFER,
1579
+ windowsHide: true
1580
+ });
1581
+ return { deleted: true, reason: "safe_deleted_merged_branch" };
1582
+ } catch (error) {
1583
+ const stderr = typeof error?.stderr === "string" ? error.stderr : "";
1584
+ const notMerged = /not fully merged/i.test(stderr) || /not fully merged/i.test(String(error?.message || ""));
1585
+ if (!notMerged) {
1586
+ return { deleted: false, reason: `branch_delete_failed: ${stderr.trim() || error?.message || "unknown error"}` };
1587
+ }
1588
+ if (opts.safeDeleteOnly) {
1589
+ return { deleted: false, reason: "branch_not_merged_per_git_safe_delete_only" };
1590
+ }
1591
+ try {
1592
+ await execFileAsync2("git", ["branch", "-D", name], {
1593
+ cwd: repoRoot,
1594
+ encoding: "utf8",
1595
+ timeout: GIT_TIMEOUT_MS,
1596
+ maxBuffer: GIT_MAX_BUFFER,
1597
+ windowsHide: true
1598
+ });
1599
+ return { deleted: true, reason: "force_deleted_patch_equivalent_branch", forced: true };
1600
+ } catch (forceError) {
1601
+ const fErr = typeof forceError?.stderr === "string" ? forceError.stderr : forceError?.message;
1602
+ return { deleted: false, reason: `branch_force_delete_failed: ${String(fErr || "unknown error").trim()}` };
1603
+ }
1604
+ }
1605
+ }
1558
1606
  async function pruneWorktrees(repoRoot) {
1559
1607
  try {
1560
1608
  await execFileAsync2("git", ["worktree", "prune"], {
@@ -4421,6 +4469,12 @@ function isMutationKeywordInCommandContext(text, matchStart, matchEnd) {
4421
4469
  const linePrefix = text.slice(lineStart, matchStart);
4422
4470
  if (/^\s*[$>]\s/.test(text.slice(lineStart))) return true;
4423
4471
  if (/^\s*$/.test(linePrefix)) return true;
4472
+ let tokStart = matchStart;
4473
+ while (tokStart > lineStart && !/\s/.test(text[tokStart - 1])) tokStart--;
4474
+ const beforeToken = text.slice(lineStart, tokStart);
4475
+ const tokenLead = text.slice(tokStart, matchStart);
4476
+ const atCmdPos = /^\s*$/.test(beforeToken) || /(?:&&|\|\||\||;)\s*$/.test(beforeToken);
4477
+ if (atCmdPos && /^(?:\.\/|\.\.\/|~\/|\/)/.test(tokenLead)) return true;
4424
4478
  if (/(?:&&|\|\||\||;)\s*$/.test(linePrefix)) return true;
4425
4479
  if (/(?:^|[\s,])(?:then|run|first)\s+$/i.test(linePrefix)) return true;
4426
4480
  if (/,\s*$/.test(linePrefix)) return true;
@@ -4447,9 +4501,65 @@ function isInsideBackticksOrFence(text, matchStart, matchEnd) {
4447
4501
  }
4448
4502
  return inlineCount % 2 === 1;
4449
4503
  }
4504
+ function isInsidePathSegment(text, matchStart, matchEnd) {
4505
+ const prev = matchStart > 0 ? text[matchStart - 1] : "";
4506
+ const next = matchEnd < text.length ? text[matchEnd] : "";
4507
+ const isSep = (c) => c === "/" || c === "\\";
4508
+ const segChar = (c) => /[A-Za-z0-9._~-]/.test(c);
4509
+ const inPath = isSep(prev) && (next === "" || isSep(next) || segChar(next) || /\s/.test(next)) || isSep(next) && (prev === "" || isSep(prev) || segChar(prev) || /\s/.test(prev));
4510
+ if (!inPath) return false;
4511
+ const lineStart = text.lastIndexOf("\n", matchStart - 1) + 1;
4512
+ let tokStart = matchStart;
4513
+ while (tokStart > lineStart && !/\s/.test(text[tokStart - 1])) tokStart--;
4514
+ const linePrefixBeforeToken = text.slice(lineStart, tokStart);
4515
+ const tokenPrefix = text.slice(tokStart, matchStart);
4516
+ const atLineStart = /^\s*$/.test(linePrefixBeforeToken);
4517
+ const afterConnective = /(?:&&|\|\||\||;)\s*$/.test(linePrefixBeforeToken);
4518
+ const isRunPrefixPath = /^(?:\.\/|\.\.\/|~\/|\/)/.test(tokenPrefix);
4519
+ if ((atLineStart || afterConnective) && isRunPrefixPath) return false;
4520
+ return true;
4521
+ }
4522
+ function isInsideQuotedSpan(text, matchStart, matchEnd) {
4523
+ const pairs = [
4524
+ ["\u201C", "\u201D"],
4525
+ ["\u2018", "\u2019"],
4526
+ ["\u300C", "\u300D"],
4527
+ ["\u300E", "\u300F"],
4528
+ ["\u300A", "\u300B"]
4529
+ ];
4530
+ for (const [open, close] of pairs) {
4531
+ const openIdx = text.lastIndexOf(open, matchStart - 1);
4532
+ if (openIdx < 0) continue;
4533
+ if (text.indexOf(close, openIdx + open.length) >= matchEnd) return true;
4534
+ }
4535
+ const lineStart = text.lastIndexOf("\n", matchStart - 1) + 1;
4536
+ const lineEnd = (() => {
4537
+ const i = text.indexOf("\n", matchEnd);
4538
+ return i < 0 ? text.length : i;
4539
+ })();
4540
+ for (const q of ['"', "'"]) {
4541
+ let count = 0;
4542
+ for (let i = lineStart; i < matchStart; i++) if (text[i] === q) count++;
4543
+ if (count % 2 === 1 && text.indexOf(q, matchEnd) >= 0 && text.indexOf(q, matchEnd) < lineEnd) {
4544
+ if (q === '"') return true;
4545
+ let openPos = -1, c = 0;
4546
+ for (let i = lineStart; i < matchStart; i++) {
4547
+ if (text[i] === q) {
4548
+ c++;
4549
+ if (c % 2 === 1) openPos = i;
4550
+ }
4551
+ }
4552
+ const beforeOpen = openPos > lineStart ? text[openPos - 1] : " ";
4553
+ if (/[\s(\[{>]/.test(beforeOpen) || openPos === lineStart) return true;
4554
+ }
4555
+ }
4556
+ return false;
4557
+ }
4450
4558
  function isRealMutationMatch(text, matchStart, matchEnd) {
4451
4559
  if (hasNegationBefore(text, matchStart)) return false;
4452
4560
  if (hasTrailingNegation(text, matchEnd)) return false;
4561
+ if (isInsidePathSegment(text, matchStart, matchEnd)) return false;
4562
+ if (isInsideQuotedSpan(text, matchStart, matchEnd)) return false;
4453
4563
  return isMutationKeywordInCommandContext(text, matchStart, matchEnd);
4454
4564
  }
4455
4565
  function patternHasRealMutation(pattern, text) {
@@ -47597,13 +47707,16 @@ var meshCrudHandlers = {
47597
47707
  worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
47598
47708
  worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
47599
47709
  forced: worktreeCleanup?.forced === true ? true : void 0,
47600
- forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
47710
+ forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0,
47711
+ branchRefDeleted: typeof worktreeCleanup?.branchRefDeleted === "boolean" ? worktreeCleanup.branchRefDeleted : void 0,
47712
+ branchRefReason: typeof worktreeCleanup?.branchRefReason === "string" ? worktreeCleanup.branchRefReason : void 0
47601
47713
  }
47602
47714
  });
47603
47715
  } catch {
47604
47716
  }
47605
47717
  }
47606
47718
  const residueWarning = worktreeCleanup?.residue === true && typeof worktreeCleanup?.residueWarning === "string" ? worktreeCleanup.residueWarning : void 0;
47719
+ const branchRefWarning = typeof worktreeCleanup?.branchRefWarning === "string" ? worktreeCleanup.branchRefWarning : void 0;
47607
47720
  const skippedLiveSessionIds = Array.isArray(sessionCleanup?.skippedLiveSessionIds) ? sessionCleanup.skippedLiveSessionIds.filter((v) => typeof v === "string") : [];
47608
47721
  const orphanedSessionsRemaining = skippedLiveSessionIds.length > 0;
47609
47722
  const orphanNextAction = orphanedSessionsRemaining ? `Live session(s) [${skippedLiveSessionIds.join(", ")}] were skipped and still survive this node removal. Run mesh_cleanup_sessions with mode:'stop_and_delete' and sessionIds:[${skippedLiveSessionIds.map((id) => `'${id}'`).join(", ")}] to release them.` : void 0;
@@ -47611,6 +47724,7 @@ var meshCrudHandlers = {
47611
47724
  success: true,
47612
47725
  removed,
47613
47726
  ...residueWarning ? { residueWarning } : {},
47727
+ ...branchRefWarning ? { branchRefWarning } : {},
47614
47728
  ...sessionCleanup ? { sessionCleanup } : {},
47615
47729
  ...worktreeCleanup ? { worktreeCleanup } : {},
47616
47730
  ...orphanedSessionsRemaining ? { orphanedSessionsRemaining: true, nextAction: orphanNextAction } : {}
@@ -52880,16 +52994,50 @@ var DaemonCommandRouter = class {
52880
52994
  recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
52881
52995
  };
52882
52996
  }
52883
- const forceFallbackConvergence = args.force ? { allow: true, status: "force_override", source: "caller_force_flag" } : await this.getWorktreeForceCleanupConvergence({ repoRoot, workspace, node: args.node });
52997
+ const mergeConvergence = await this.getWorktreeForceCleanupConvergence({ repoRoot, workspace, node: args.node });
52998
+ const forceFallbackConvergence = args.force ? { allow: true, status: "force_override", source: "caller_force_flag" } : mergeConvergence;
52999
+ const deleteBranchIfMerged = async () => {
53000
+ const branch = String(args.node.worktreeBranch).trim();
53001
+ const status = mergeConvergence.allow ? mergeConvergence.status || "" : "";
53002
+ const MERGED_STATUSES = /* @__PURE__ */ new Set([
53003
+ "merged_to_main",
53004
+ "merged_pushed",
53005
+ "merged_to_default_ref",
53006
+ "cleanup_candidate"
53007
+ ]);
53008
+ const PATCH_EQUIV_STATUS = "patch_equivalent_to_default_ref";
53009
+ if (!branch) {
53010
+ return { branchRefDeleted: false, branchRefReason: "empty_branch_name" };
53011
+ }
53012
+ if (!mergeConvergence.allow || !MERGED_STATUSES.has(status) && status !== PATCH_EQUIV_STATUS) {
53013
+ return {
53014
+ branchRefDeleted: false,
53015
+ branchRefReason: `branch_not_merged_preserved: ${mergeConvergence.error || mergeConvergence.status || "convergence_unverified"}`,
53016
+ branchRefWarning: `Branch ref '${branch}' was preserved (not deleted) because it is not confirmed merged into the default ref \u2014 no work was lost. Merge it (or pass a verified branchConvergence final state) and re-run cleanup, or delete it manually after confirming.`
53017
+ };
53018
+ }
53019
+ const { deleteBranchRef: deleteBranchRef2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
53020
+ const res = await deleteBranchRef2(repoRoot, branch, { safeDeleteOnly: status !== PATCH_EQUIV_STATUS });
53021
+ return {
53022
+ branchRefDeleted: res.deleted,
53023
+ branchRefReason: res.reason,
53024
+ ...res.forced ? { branchRefForced: true } : {},
53025
+ ...res.deleted ? {} : {
53026
+ branchRefWarning: `Branch ref '${branch}' could not be deleted (${res.reason}); it was preserved so no work is lost.`
53027
+ }
53028
+ };
53029
+ };
52884
53030
  try {
52885
53031
  const result = await removeWorktree2(repoRoot, workspace, {
52886
53032
  requireClean: !args.force,
52887
53033
  allowSubmoduleForceFallback: forceFallbackConvergence.allow
52888
53034
  });
53035
+ const branchOutcome = await deleteBranchIfMerged();
52889
53036
  return {
52890
53037
  success: true,
52891
53038
  removedPath: result.removedPath,
52892
53039
  repoRoot,
53040
+ ...branchOutcome,
52893
53041
  ...result.fallback ? {
52894
53042
  fallback: result.fallback,
52895
53043
  forced: result.forced,
@@ -52922,10 +53070,12 @@ var DaemonCommandRouter = class {
52922
53070
  maxBuffer: GIT_MAX_BUFFER_CLEANUP,
52923
53071
  windowsHide: true
52924
53072
  });
53073
+ const branchOutcome = await deleteBranchIfMerged();
52925
53074
  return {
52926
53075
  success: true,
52927
53076
  removedPath: workspace,
52928
53077
  repoRoot,
53078
+ ...branchOutcome,
52929
53079
  fallback: "git_worktree_remove_submodule_deinit",
52930
53080
  forced: true,
52931
53081
  reason: "working_trees_containing_submodules",
@@ -52943,10 +53093,12 @@ var DaemonCommandRouter = class {
52943
53093
  });
52944
53094
  } catch {
52945
53095
  }
53096
+ const branchOutcome = await deleteBranchIfMerged();
52946
53097
  return {
52947
53098
  success: true,
52948
53099
  removedPath: workspace,
52949
53100
  repoRoot,
53101
+ ...branchOutcome,
52950
53102
  fallback: "fs_rm_worktree_prune",
52951
53103
  forced: true,
52952
53104
  reason: "working_trees_containing_submodules",