@staff0rd/assist 0.430.0 → 0.432.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.430.0",
9
+ version: "0.432.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -432,6 +432,16 @@ var assistConfigSchema = z2.strictObject({
432
432
  promptJira: z2.boolean().default(false)
433
433
  }).optional(),
434
434
  repos: z2.record(z2.string(), z2.record(z2.string(), z2.unknown())).optional(),
435
+ worktree: z2.strictObject({
436
+ enabled: z2.boolean().default(false),
437
+ root: z2.string().optional(),
438
+ install: z2.union([z2.boolean(), z2.string()]).default(true),
439
+ copy: z2.array(z2.string()).default([
440
+ ".env",
441
+ "settings.local.json",
442
+ ".claude/settings.local.json"
443
+ ])
444
+ }).optional(),
435
445
  run: z2.array(z2.union([runConfigSchema, runLinkSchema])).optional(),
436
446
  subtasks: z2.array(
437
447
  z2.strictObject({
@@ -1542,6 +1552,7 @@ function warnIfUnexpectedBranch(config) {
1542
1552
  if (!expected) return;
1543
1553
  const current = currentBranch();
1544
1554
  if (current === null || current === expected) return;
1555
+ if (upstreamBranch() === expected) return;
1545
1556
  const line = "\u2500".repeat(64);
1546
1557
  console.warn(
1547
1558
  chalk3.yellow.bold(
@@ -1565,6 +1576,18 @@ function currentBranch() {
1565
1576
  return null;
1566
1577
  }
1567
1578
  }
1579
+ function upstreamBranch() {
1580
+ try {
1581
+ const upstream = execSync(
1582
+ "git rev-parse --abbrev-ref --symbolic-full-name @{upstream}",
1583
+ { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
1584
+ ).trim();
1585
+ const slash = upstream.indexOf("/");
1586
+ return slash === -1 ? upstream || null : upstream.slice(slash + 1) || null;
1587
+ } catch {
1588
+ return null;
1589
+ }
1590
+ }
1568
1591
 
1569
1592
  // src/commands/commit/findConflictMarkers.ts
1570
1593
  import { execSync as execSync2 } from "child_process";
@@ -1726,7 +1749,7 @@ function gitRefUrl(kind, ref, cwd) {
1726
1749
  }
1727
1750
 
1728
1751
  // src/commands/commit/collectCommitRefs.ts
1729
- function collectCommitRefs(message) {
1752
+ function collectCommitRefs(message2) {
1730
1753
  const refs = [];
1731
1754
  const branch2 = git("rev-parse --abbrev-ref HEAD");
1732
1755
  if (branch2 && branch2 !== "HEAD") {
@@ -1743,7 +1766,7 @@ function collectCommitRefs(message) {
1743
1766
  ref: sha,
1744
1767
  url: gitRefUrl("commit", sha)
1745
1768
  };
1746
- const subject = message.split("\n", 1)[0];
1769
+ const subject = message2.split("\n", 1)[0];
1747
1770
  if (subject) ref.title = subject;
1748
1771
  refs.push(ref);
1749
1772
  }
@@ -1762,16 +1785,16 @@ function git(args) {
1762
1785
 
1763
1786
  // src/commands/commit/stageAndCommit.ts
1764
1787
  import { execSync as execSync5 } from "child_process";
1765
- function commitStaged(message) {
1766
- execSync5(`git commit -m ${shellQuote(message)}`, { stdio: "inherit" });
1788
+ function commitStaged(message2) {
1789
+ execSync5(`git commit -m ${shellQuote(message2)}`, { stdio: "inherit" });
1767
1790
  return execSync5("git rev-parse --short=7 HEAD", {
1768
1791
  encoding: "utf8"
1769
1792
  }).trim();
1770
1793
  }
1771
- function stageAndCommit(files, message) {
1794
+ function stageAndCommit(files, message2) {
1772
1795
  const escaped = files.map(shellQuote).join(" ");
1773
1796
  execSync5(`git add ${escaped}`, { stdio: "inherit" });
1774
- return commitStaged(message);
1797
+ return commitStaged(message2);
1775
1798
  }
1776
1799
 
1777
1800
  // src/commands/sessions/summarise/iterateUserMessages.ts
@@ -1866,34 +1889,34 @@ function backlogRefError(subject, context, ids) {
1866
1889
  // src/commands/commit/validateMessage.ts
1867
1890
  var MAX_MESSAGE_LENGTH = 50;
1868
1891
  var CONVENTIONAL_COMMIT_REGEX = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(!)?(\(.+\))?!?: .+$/;
1869
- function validateMessage(message, config) {
1870
- if (message.toLowerCase().includes("claude")) {
1892
+ function validateMessage(message2, config) {
1893
+ if (message2.toLowerCase().includes("claude")) {
1871
1894
  console.error("Error: Commit message must not reference Claude");
1872
1895
  process.exit(1);
1873
1896
  }
1874
- const backlogIds = findBacklogRefs(message);
1897
+ const backlogIds = findBacklogRefs(message2);
1875
1898
  if (backlogIds.length > 0) {
1876
1899
  console.error(
1877
1900
  backlogRefError("Commit message", "commit messages", backlogIds)
1878
1901
  );
1879
1902
  process.exit(1);
1880
1903
  }
1881
- if (config.commit?.conventional && !CONVENTIONAL_COMMIT_REGEX.test(message)) {
1904
+ if (config.commit?.conventional && !CONVENTIONAL_COMMIT_REGEX.test(message2)) {
1882
1905
  console.error(
1883
1906
  "Error: Commit message must follow conventional commit format (e.g., 'feat: add feature', 'fix(scope): fix bug')"
1884
1907
  );
1885
1908
  process.exit(1);
1886
1909
  }
1887
- if (message.length > MAX_MESSAGE_LENGTH) {
1910
+ if (message2.length > MAX_MESSAGE_LENGTH) {
1888
1911
  console.error(
1889
- `Error: Commit message must be ${MAX_MESSAGE_LENGTH} characters or less (current: ${message.length})`
1912
+ `Error: Commit message must be ${MAX_MESSAGE_LENGTH} characters or less (current: ${message2.length})`
1890
1913
  );
1891
1914
  process.exit(1);
1892
1915
  }
1893
1916
  }
1894
1917
 
1895
1918
  // src/commands/commit.ts
1896
- async function execCommit(files, message, config) {
1919
+ async function execCommit(files, message2, config) {
1897
1920
  try {
1898
1921
  warnIfUnexpectedBranch(config);
1899
1922
  const pulled = Boolean(config.commit?.pull);
@@ -1901,21 +1924,21 @@ async function execCommit(files, message, config) {
1901
1924
  execSync6("git pull --autostash", { stdio: "inherit" });
1902
1925
  }
1903
1926
  abortOnConflicts(files, pulled);
1904
- const sha = files.length > 0 ? stageAndCommit(files, message) : commitStaged(message);
1927
+ const sha = files.length > 0 ? stageAndCommit(files, message2) : commitStaged(message2);
1905
1928
  console.log(`Committed: ${sha}`);
1906
1929
  if (config.commit?.push) {
1907
1930
  execSync6("git push", { stdio: "inherit" });
1908
1931
  console.log("Pushed to remote");
1909
1932
  }
1910
- await recordCommitActivity(message);
1933
+ await recordCommitActivity(message2);
1911
1934
  process.exit(0);
1912
1935
  } catch {
1913
1936
  process.exit(1);
1914
1937
  }
1915
1938
  }
1916
- async function recordCommitActivity(message) {
1939
+ async function recordCommitActivity(message2) {
1917
1940
  if (resolveSessionItemId() === null) return;
1918
- await recordSessionRefs(collectCommitRefs(message));
1941
+ await recordSessionRefs(collectCommitRefs(message2));
1919
1942
  }
1920
1943
  async function commit(args) {
1921
1944
  if (args[0] === "status") {
@@ -1928,11 +1951,11 @@ async function commit(args) {
1928
1951
  console.error("Usage: assist commit <message> [files...]");
1929
1952
  process.exit(1);
1930
1953
  }
1931
- const message = args[0];
1954
+ const message2 = args[0];
1932
1955
  const files = args.slice(1);
1933
1956
  const config = loadConfig();
1934
- validateMessage(message, config);
1935
- await execCommit(files, message, config);
1957
+ validateMessage(message2, config);
1958
+ await execCommit(files, message2, config);
1936
1959
  }
1937
1960
 
1938
1961
  // src/commands/coverage.ts
@@ -1970,12 +1993,12 @@ async function exitOnCancel(promise) {
1970
1993
  }
1971
1994
 
1972
1995
  // src/shared/promptMultiselect.ts
1973
- async function promptMultiselect(message, options2) {
1996
+ async function promptMultiselect(message2, options2) {
1974
1997
  const { selected } = await exitOnCancel(
1975
1998
  enquirer.prompt({
1976
1999
  type: "multiselect",
1977
2000
  name: "selected",
1978
- message,
2001
+ message: message2,
1979
2002
  choices: options2.map((opt) => ({
1980
2003
  name: opt.value,
1981
2004
  message: `${opt.name} - ${chalk4.dim(opt.description)}`
@@ -2217,12 +2240,12 @@ import chalk13 from "chalk";
2217
2240
 
2218
2241
  // src/shared/promptConfirm.ts
2219
2242
  import enquirer2 from "enquirer";
2220
- async function promptConfirm(message, initial = true) {
2243
+ async function promptConfirm(message2, initial = true) {
2221
2244
  const { confirmed } = await exitOnCancel(
2222
2245
  enquirer2.prompt({
2223
2246
  type: "confirm",
2224
2247
  name: "confirmed",
2225
- message,
2248
+ message: message2,
2226
2249
  initial,
2227
2250
  // @ts-expect-error - enquirer types don't include symbols but it's supported
2228
2251
  symbols: {
@@ -4532,13 +4555,13 @@ function getSnoreToastPath() {
4532
4555
  return path21.join(notifierPath, "vendor", "snoreToast", "snoretoast-x64.exe");
4533
4556
  }
4534
4557
  function showWindowsNotificationFromWsl(options2) {
4535
- const { title, message, sound } = options2;
4558
+ const { title, message: message2, sound } = options2;
4536
4559
  const snoreToastPath = getSnoreToastPath();
4537
4560
  try {
4538
4561
  fs16.chmodSync(snoreToastPath, 493);
4539
4562
  } catch {
4540
4563
  }
4541
- const args = ["-t", title, "-m", message];
4564
+ const args = ["-t", title, "-m", message2];
4542
4565
  if (sound) {
4543
4566
  args.push("-s", "ms-winsoundevent:Notification.Default");
4544
4567
  }
@@ -4552,14 +4575,14 @@ function showWindowsNotificationFromWsl(options2) {
4552
4575
 
4553
4576
  // src/commands/notify/showNotification/index.ts
4554
4577
  function showNotification(options2) {
4555
- const { title, message, sound } = options2;
4578
+ const { title, message: message2, sound } = options2;
4556
4579
  const platform = detectPlatform();
4557
4580
  if (platform === "wsl") {
4558
- return showWindowsNotificationFromWsl({ title, message, sound });
4581
+ return showWindowsNotificationFromWsl({ title, message: message2, sound });
4559
4582
  }
4560
4583
  const notificationOptions = {
4561
4584
  title,
4562
- message,
4585
+ message: message2,
4563
4586
  wait: false
4564
4587
  };
4565
4588
  if (platform === "windows") {
@@ -4581,7 +4604,7 @@ async function notify() {
4581
4604
  }
4582
4605
  const inputData = await readStdin();
4583
4606
  const data = JSON.parse(inputData);
4584
- const { notification_type, cwd, message } = data;
4607
+ const { notification_type, cwd, message: message2 } = data;
4585
4608
  const projectName = cwd?.split(/[/\\]/).pop() ?? "Unknown Project";
4586
4609
  let title;
4587
4610
  let body;
@@ -4589,7 +4612,7 @@ async function notify() {
4589
4612
  switch (notification_type) {
4590
4613
  case "permission_prompt":
4591
4614
  title = "Claude needs permission";
4592
- body = `${projectName} - ${message || "Permission required"}`;
4615
+ body = `${projectName} - ${message2 || "Permission required"}`;
4593
4616
  sound = "Alarm";
4594
4617
  break;
4595
4618
  case "idle_prompt":
@@ -4599,7 +4622,7 @@ async function notify() {
4599
4622
  break;
4600
4623
  default:
4601
4624
  title = "Claude Code";
4602
- body = message ? `${projectName} - ${message}` : projectName;
4625
+ body = message2 ? `${projectName} - ${message2}` : projectName;
4603
4626
  sound = "Default";
4604
4627
  }
4605
4628
  showNotification({ title, message: body, sound });
@@ -4853,8 +4876,8 @@ function readScheduleBlock(crontab) {
4853
4876
  }
4854
4877
 
4855
4878
  // src/commands/backup/scheduleBackup.ts
4856
- function fail(message) {
4857
- console.error(chalk29.red(message));
4879
+ function fail(message2) {
4880
+ console.error(chalk29.red(message2));
4858
4881
  process.exit(1);
4859
4882
  }
4860
4883
  async function scheduleBackup({
@@ -5371,8 +5394,8 @@ function buildArgs(prompt, options2) {
5371
5394
 
5372
5395
  // src/commands/sessions/daemon/appendDaemonLog.ts
5373
5396
  import { appendFileSync } from "fs";
5374
- function appendDaemonLog(message) {
5375
- const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message}`;
5397
+ function appendDaemonLog(message2) {
5398
+ const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message2}`;
5376
5399
  try {
5377
5400
  appendFileSync(daemonPaths.log, `${line}
5378
5401
  `);
@@ -6330,10 +6353,10 @@ async function reconcileResumePhase(orm, item, startPhase, resumeSessionId) {
6330
6353
  if (resumedPhaseIdx === void 0 || resumedPhaseIdx === startPhase) {
6331
6354
  return startPhase;
6332
6355
  }
6333
- const message = `resume reconciliation: currentPhase implied phase ${startPhase + 1} but the resumed conversation ${resumeSessionId} ran phase ${resumedPhaseIdx + 1}; resuming phase ${resumedPhaseIdx + 1} to match the conversation`;
6334
- appendDaemonLog(`backlog run ${item.id}: ${message}`);
6356
+ const message2 = `resume reconciliation: currentPhase implied phase ${startPhase + 1} but the resumed conversation ${resumeSessionId} ran phase ${resumedPhaseIdx + 1}; resuming phase ${resumedPhaseIdx + 1} to match the conversation`;
6357
+ appendDaemonLog(`backlog run ${item.id}: ${message2}`);
6335
6358
  try {
6336
- await appendComment(orm, item.id, `Resume reconciled \u2014 ${message}`, {
6359
+ await appendComment(orm, item.id, `Resume reconciled \u2014 ${message2}`, {
6337
6360
  phase: resumedPhaseIdx + 1
6338
6361
  });
6339
6362
  } catch {
@@ -6396,10 +6419,10 @@ async function awaitClaude(done2, context) {
6396
6419
  try {
6397
6420
  return await done2;
6398
6421
  } catch (error) {
6399
- const message = error instanceof Error ? error.message : String(error);
6422
+ const message2 = error instanceof Error ? error.message : String(error);
6400
6423
  console.error(
6401
6424
  chalk39.red(`
6402
- Failed to launch Claude for ${context}: ${message}`)
6425
+ Failed to launch Claude for ${context}: ${message2}`)
6403
6426
  );
6404
6427
  return CLAUDE_SPAWN_FAILED;
6405
6428
  }
@@ -6425,7 +6448,7 @@ async function isDaemonRunning() {
6425
6448
 
6426
6449
  // src/commands/sessions/daemon/sendToDaemon.ts
6427
6450
  var WRITE_TIMEOUT_MS = 500;
6428
- function sendToDaemon(message) {
6451
+ function sendToDaemon(message2) {
6429
6452
  return new Promise((resolve19, reject) => {
6430
6453
  connectToDaemon().then((socket) => {
6431
6454
  const timer = setTimeout(() => {
@@ -6436,7 +6459,7 @@ function sendToDaemon(message) {
6436
6459
  clearTimeout(timer);
6437
6460
  reject(error);
6438
6461
  });
6439
- socket.write(`${JSON.stringify(message)}
6462
+ socket.write(`${JSON.stringify(message2)}
6440
6463
  `, () => {
6441
6464
  clearTimeout(timer);
6442
6465
  socket.end();
@@ -6462,7 +6485,7 @@ function readSocketLines(socket, onLine) {
6462
6485
 
6463
6486
  // src/commands/sessions/daemon/sendToDaemonAwaitAck.ts
6464
6487
  var ACK_TIMEOUT_MS = 1e3;
6465
- function sendToDaemonAwaitAck(message) {
6488
+ function sendToDaemonAwaitAck(message2) {
6466
6489
  return new Promise((resolve19, reject) => {
6467
6490
  connectToDaemon().then((socket) => {
6468
6491
  let settled = false;
@@ -6486,7 +6509,7 @@ function sendToDaemonAwaitAck(message) {
6486
6509
  "close",
6487
6510
  () => finish(new Error("daemon closed before acknowledging"))
6488
6511
  );
6489
- socket.write(`${JSON.stringify(message)}
6512
+ socket.write(`${JSON.stringify(message2)}
6490
6513
  `);
6491
6514
  }, reject);
6492
6515
  });
@@ -7223,13 +7246,13 @@ async function findSessionJsonlPath(sessionId) {
7223
7246
  // src/commands/backlog/verifyResumeConversation.ts
7224
7247
  async function verifyResumeConversation(itemId2, resumeSessionId, phaseLabel2) {
7225
7248
  if (await findSessionJsonlPath(resumeSessionId)) return true;
7226
- const message = `${phaseLabel2}: resume found no conversation for session ${resumeSessionId}; phase not advanced`;
7249
+ const message2 = `${phaseLabel2}: resume found no conversation for session ${resumeSessionId}; phase not advanced`;
7227
7250
  console.error(chalk42.red(`
7228
- ${message}`));
7229
- appendDaemonLog(`backlog run ${itemId2}: ${message}`);
7251
+ ${message2}`));
7252
+ appendDaemonLog(`backlog run ${itemId2}: ${message2}`);
7230
7253
  try {
7231
7254
  const { orm } = await getReady();
7232
- await appendComment(orm, itemId2, `Resume failed \u2014 ${message}`);
7255
+ await appendComment(orm, itemId2, `Resume failed \u2014 ${message2}`);
7233
7256
  } catch {
7234
7257
  }
7235
7258
  return false;
@@ -9569,8 +9592,8 @@ async function runRestartItem(item, { runRestartDaemon, reExec }) {
9569
9592
  reExec();
9570
9593
  }
9571
9594
  } catch (error) {
9572
- const message = error instanceof Error ? error.message : String(error);
9573
- console.error(chalk57.red(`Restart failed: ${message}`));
9595
+ const message2 = error instanceof Error ? error.message : String(error);
9596
+ console.error(chalk57.red(`Restart failed: ${message2}`));
9574
9597
  }
9575
9598
  }
9576
9599
 
@@ -10120,8 +10143,8 @@ function originToSshUrl(origin) {
10120
10143
  }
10121
10144
 
10122
10145
  // src/commands/backlog/cloneRepo.ts
10123
- function fail2(message) {
10124
- console.log(chalk66.red(message));
10146
+ function fail2(message2) {
10147
+ console.log(chalk66.red(message2));
10125
10148
  process.exitCode = 1;
10126
10149
  }
10127
10150
  async function cloneRepo(originRaw) {
@@ -10777,8 +10800,8 @@ function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
10777
10800
  }
10778
10801
 
10779
10802
  // src/commands/backlog/link.ts
10780
- function fail3(message) {
10781
- console.log(chalk78.red(message));
10803
+ function fail3(message2) {
10804
+ console.log(chalk78.red(message2));
10782
10805
  return void 0;
10783
10806
  }
10784
10807
  function parseLinkType(type) {
@@ -10900,8 +10923,8 @@ Pass the full origin.`
10900
10923
  }
10901
10924
 
10902
10925
  // src/commands/backlog/move-repo/index.ts
10903
- function fail4(message) {
10904
- console.log(chalk81.red(message));
10926
+ function fail4(message2) {
10927
+ console.log(chalk81.red(message2));
10905
10928
  process.exitCode = 1;
10906
10929
  }
10907
10930
  async function moveRepo(oldOriginRaw, newOriginRaw, options2 = {}) {
@@ -11056,11 +11079,11 @@ async function handleLaunchSignal(slashCommand, once) {
11056
11079
  await next({ allowEdits: true, once });
11057
11080
  }
11058
11081
  } catch (error) {
11059
- const message = error instanceof Error ? error.message : String(error);
11082
+ const message2 = error instanceof Error ? error.message : String(error);
11060
11083
  console.error(
11061
11084
  chalk83.yellow(
11062
11085
  `
11063
- Could not complete post-run step (${message}).
11086
+ Could not complete post-run step (${message2}).
11064
11087
  This is usually a transient database/network blip \u2014 the work is saved and the session is safe to resume.`
11065
11088
  )
11066
11089
  );
@@ -13168,15 +13191,15 @@ function loadDenyConfig(global) {
13168
13191
  }
13169
13192
 
13170
13193
  // src/commands/deny/denyAdd.ts
13171
- function denyAdd(pattern2, message, options2) {
13194
+ function denyAdd(pattern2, message2, options2) {
13172
13195
  const { deny, saveDeny } = loadDenyConfig(options2.global);
13173
13196
  if (deny.some((r) => r.pattern === pattern2)) {
13174
13197
  console.log(chalk110.yellow(`Deny rule already exists for: ${pattern2}`));
13175
13198
  return;
13176
13199
  }
13177
- deny.push({ pattern: pattern2, message });
13200
+ deny.push({ pattern: pattern2, message: message2 });
13178
13201
  saveDeny(deny);
13179
- console.log(chalk110.green(`Added deny rule: ${pattern2} \u2192 ${message}`));
13202
+ console.log(chalk110.green(`Added deny rule: ${pattern2} \u2192 ${message2}`));
13180
13203
  }
13181
13204
 
13182
13205
  // src/commands/deny/denyList.ts
@@ -14733,14 +14756,14 @@ function parseGitLogCommits(output, ignore3, afterDate) {
14733
14756
  const commitsByDate = /* @__PURE__ */ new Map();
14734
14757
  for (const line of lines) {
14735
14758
  const [date, hash, ...messageParts] = line.split("|");
14736
- const message = messageParts.join("|");
14759
+ const message2 = messageParts.join("|");
14737
14760
  if (afterDate && date <= afterDate) {
14738
14761
  continue;
14739
14762
  }
14740
14763
  const files = getCommitFiles(hash);
14741
14764
  if (!shouldIgnoreCommit(files, ignore3)) {
14742
14765
  const existing = commitsByDate.get(date) || [];
14743
- existing.push({ date, hash, message, files });
14766
+ existing.push({ date, hash, message: message2, files });
14744
14767
  commitsByDate.set(date, existing);
14745
14768
  }
14746
14769
  }
@@ -16353,10 +16376,10 @@ function advisory(count7) {
16353
16376
  const noun = count7 === 1 ? "handover" : "handovers";
16354
16377
  return `${count7} unrecalled ${noun} for this repo. Run /recall to load.`;
16355
16378
  }
16356
- function emit2(message) {
16379
+ function emit2(message2) {
16357
16380
  const json = JSON.stringify({
16358
16381
  hookSpecificOutput: { hookEventName: "SessionStart" },
16359
- systemMessage: message
16382
+ systemMessage: message2
16360
16383
  });
16361
16384
  console.log(json);
16362
16385
  return json;
@@ -16540,11 +16563,11 @@ import { execSync as execSync39 } from "child_process";
16540
16563
  // src/shared/promptInput.ts
16541
16564
  import Enquirer from "enquirer";
16542
16565
  var prompts = Enquirer;
16543
- async function promptInput(name, message, initial) {
16544
- return exitOnCancel(new prompts.Input({ name, message, initial }).run());
16566
+ async function promptInput(name, message2, initial) {
16567
+ return exitOnCancel(new prompts.Input({ name, message: message2, initial }).run());
16545
16568
  }
16546
- async function promptPassword(name, message) {
16547
- return exitOnCancel(new prompts.Password({ name, message }).run());
16569
+ async function promptPassword(name, message2) {
16570
+ return exitOnCancel(new prompts.Password({ name, message: message2 }).run());
16548
16571
  }
16549
16572
 
16550
16573
  // src/commands/jira/jiraAuth.ts
@@ -18021,9 +18044,9 @@ function deleteCommentsCache(prNumber) {
18021
18044
 
18022
18045
  // src/commands/prs/replyToComment.ts
18023
18046
  import { execSync as execSync41 } from "child_process";
18024
- function replyToComment(org, repo, prNumber, commentId, message) {
18047
+ function replyToComment(org, repo, prNumber, commentId, message2) {
18025
18048
  execSync41(
18026
- `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, String.raw`\"`)}" -F in_reply_to=${commentId}`,
18049
+ `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message2.replace(/"/g, String.raw`\"`)}" -F in_reply_to=${commentId}`,
18027
18050
  { stdio: ["inherit", "pipe", "inherit"] }
18028
18051
  );
18029
18052
  }
@@ -18071,12 +18094,12 @@ function cleanupCacheIfDone(cache2, prNumber, commentId) {
18071
18094
  );
18072
18095
  if (!hasRemaining) deleteCommentsCache(prNumber);
18073
18096
  }
18074
- function resolveCommentWithReply(commentId, message) {
18097
+ function resolveCommentWithReply(commentId, message2) {
18075
18098
  const prNumber = getCurrentPrNumber();
18076
18099
  const { org, repo } = getRepoInfo();
18077
18100
  const cache2 = requireCache(prNumber);
18078
18101
  const comment3 = requireLineComment(cache2, commentId);
18079
- replyToComment(org, repo, prNumber, commentId, message);
18102
+ replyToComment(org, repo, prNumber, commentId, message2);
18080
18103
  console.log("Reply posted successfully.");
18081
18104
  resolveThread(comment3.threadId);
18082
18105
  console.log("Thread resolved successfully.");
@@ -18099,9 +18122,9 @@ function fixed(commentId, sha) {
18099
18122
  const fullSha = verifySha(sha);
18100
18123
  const { org, repo } = getRepoInfo();
18101
18124
  const repoUrl = `https://github.com/${org}/${repo}`;
18102
- const message = `Fixed in [${fullSha}](${repoUrl}/commit/${fullSha})`;
18125
+ const message2 = `Fixed in [${fullSha}](${repoUrl}/commit/${fullSha})`;
18103
18126
  execSync43("git push", { stdio: "inherit" });
18104
- resolveCommentWithReply(commentId, message);
18127
+ resolveCommentWithReply(commentId, message2);
18105
18128
  } catch (error) {
18106
18129
  if (isGhNotInstalled(error)) {
18107
18130
  console.error("Error: GitHub CLI (gh) is not installed.");
@@ -19018,9 +19041,9 @@ function getItemFields(itemId2) {
19018
19041
 
19019
19042
  // src/commands/ravendb/selectOpSecret.ts
19020
19043
  var { Input, Select } = Enquirer2;
19021
- async function selectOne(message, choices) {
19044
+ async function selectOne(message2, choices) {
19022
19045
  if (choices.length === 1) return choices[0].value;
19023
- const selected = await new Select({ name: "choice", message, choices }).run();
19046
+ const selected = await new Select({ name: "choice", message: message2, choices }).run();
19024
19047
  return choices.find((c) => c.name === selected)?.value ?? selected;
19025
19048
  }
19026
19049
  async function selectOpSecret(searchTerm) {
@@ -20735,8 +20758,8 @@ function findRootParent(file, importedBy, visited) {
20735
20758
  function clusterFiles(graph) {
20736
20759
  const clusters = /* @__PURE__ */ new Map();
20737
20760
  for (const file of graph.files) {
20738
- const basename14 = path52.basename(file, path52.extname(file));
20739
- if (basename14 === "index") continue;
20761
+ const basename17 = path52.basename(file, path52.extname(file));
20762
+ if (basename17 === "index") continue;
20740
20763
  const importers = graph.importedBy.get(file);
20741
20764
  if (!importers || importers.size !== 1) continue;
20742
20765
  const parent = [...importers][0];
@@ -21470,9 +21493,9 @@ function postFindings(findings) {
21470
21493
  posted++;
21471
21494
  } catch (error) {
21472
21495
  failed2++;
21473
- const message = error instanceof Error ? error.message : String(error);
21496
+ const message2 = error instanceof Error ? error.message : String(error);
21474
21497
  console.error(
21475
- `Failed to post comment on ${finding.file}:${finding.line}: ${message}`
21498
+ `Failed to post comment on ${finding.file}:${finding.line}: ${message2}`
21476
21499
  );
21477
21500
  }
21478
21501
  }
@@ -21491,8 +21514,8 @@ function submitPendingReview(body) {
21491
21514
  console.error("Error: GitHub CLI (gh) is not installed.");
21492
21515
  return;
21493
21516
  }
21494
- const message = error instanceof Error ? error.message : String(error);
21495
- console.error(`Failed to submit review: ${message}`);
21517
+ const message2 = error instanceof Error ? error.message : String(error);
21518
+ console.error(`Failed to submit review: ${message2}`);
21496
21519
  }
21497
21520
  }
21498
21521
 
@@ -22283,10 +22306,10 @@ function messageFor(err, command) {
22283
22306
  return err.message || String(err);
22284
22307
  }
22285
22308
  function handleSpawnError(ctx, err) {
22286
- const message = messageFor(err, ctx.command);
22309
+ const message2 = messageFor(err, ctx.command);
22287
22310
  const stderr = ctx.stderr ? `${ctx.stderr}
22288
- ${message}` : message;
22289
- if (!ctx.quiet) console.error(`[${ctx.name}] failed: ${message}`);
22311
+ ${message2}` : message2;
22312
+ if (!ctx.quiet) console.error(`[${ctx.name}] failed: ${message2}`);
22290
22313
  return {
22291
22314
  exitCode: 127,
22292
22315
  stderr,
@@ -25150,8 +25173,8 @@ import { createInterface as createInterface6 } from "readline";
25150
25173
  var RING_CAPACITY = 1e3;
25151
25174
  var ring = [];
25152
25175
  var sink;
25153
- function daemonLog(message) {
25154
- emit3(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message}`);
25176
+ function daemonLog(message2) {
25177
+ emit3(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message2}`);
25155
25178
  }
25156
25179
  function relayDaemonLog(line) {
25157
25180
  emit3(`[windows] ${line}`);
@@ -25177,7 +25200,7 @@ var persistedSessionSchema = z5.object({
25177
25200
  subtitle: z5.string().optional(),
25178
25201
  commandType: z5.enum(["claude", "run", "assist"]),
25179
25202
  harness: z5.enum(["claude", "codex", "pi"]).optional(),
25180
- status: z5.enum(["running", "waiting", "done", "error"]).optional(),
25203
+ status: z5.enum(["running", "waiting", "done", "error", "stopped"]).optional(),
25181
25204
  cwd: z5.string(),
25182
25205
  startedAt: z5.number(),
25183
25206
  runningMs: z5.number().optional(),
@@ -25187,7 +25210,8 @@ var persistedSessionSchema = z5.object({
25187
25210
  runArgs: z5.array(z5.string()).optional(),
25188
25211
  assistArgs: z5.array(z5.string()).optional(),
25189
25212
  activity: activitySchema.optional(),
25190
- starred: z5.boolean().optional()
25213
+ starred: z5.boolean().optional(),
25214
+ undurable: z5.object({ reason: z5.string() }).optional()
25191
25215
  });
25192
25216
 
25193
25217
  // src/commands/sessions/daemon/toPersistedSession.ts
@@ -25208,7 +25232,8 @@ function toPersistedSession(session) {
25208
25232
  runArgs: session.runArgs,
25209
25233
  assistArgs: session.assistArgs,
25210
25234
  activity: session.activity,
25211
- starred: session.starred
25235
+ starred: session.starred,
25236
+ undurable: session.undurable
25212
25237
  };
25213
25238
  }
25214
25239
  function accumulatedRunningMs(session) {
@@ -25230,7 +25255,7 @@ function savePersistedSessions(sessions) {
25230
25255
  }
25231
25256
  function persistLiveSessions(sessions) {
25232
25257
  const live = [...sessions.values()].filter(
25233
- (s) => s.pty && s.status !== "done"
25258
+ (s) => (s.pty || s.status === "stopped") && s.status !== "done"
25234
25259
  );
25235
25260
  savePersistedSessions(live.map(toPersistedSession));
25236
25261
  logPersist(live);
@@ -25282,7 +25307,7 @@ function requestDrain(socket) {
25282
25307
  }
25283
25308
 
25284
25309
  // src/commands/sessions/daemon/runDaemon.ts
25285
- import { mkdirSync as mkdirSync26 } from "fs";
25310
+ import { mkdirSync as mkdirSync27 } from "fs";
25286
25311
 
25287
25312
  // src/commands/sessions/daemon/createAutoExit.ts
25288
25313
  var DEFAULT_GRACE_MS = 6e4;
@@ -25385,7 +25410,8 @@ function toSessionInfo({
25385
25410
  starred,
25386
25411
  usedPct,
25387
25412
  design,
25388
- pendingPrPreview
25413
+ pendingPrPreview,
25414
+ undurable
25389
25415
  }) {
25390
25416
  return {
25391
25417
  id,
@@ -25413,7 +25439,8 @@ function toSessionInfo({
25413
25439
  starred,
25414
25440
  usedPct,
25415
25441
  design,
25416
- pendingPrPreview
25442
+ pendingPrPreview,
25443
+ undurable
25417
25444
  };
25418
25445
  }
25419
25446
 
@@ -25503,20 +25530,56 @@ var ClientHub = class extends Set {
25503
25530
  };
25504
25531
  };
25505
25532
 
25533
+ // src/commands/sessions/daemon/createSession.ts
25534
+ import { randomUUID as randomUUID5 } from "crypto";
25535
+
25536
+ // src/commands/sessions/daemon/resolveRunConfig.ts
25537
+ function resolveRunConfig(name, cwd) {
25538
+ try {
25539
+ const { run: run4 } = loadConfigFrom(cwd);
25540
+ const configs = resolveRunConfigs(run4, getConfigDirFrom(cwd));
25541
+ const exact = configs.find((r) => r.name === name);
25542
+ if (exact) return exact;
25543
+ const suffixMatches = configs.filter((r) => r.name.endsWith(`:${name}`));
25544
+ return suffixMatches.length === 1 ? suffixMatches[0] : void 0;
25545
+ } catch {
25546
+ return void 0;
25547
+ }
25548
+ }
25549
+
25550
+ // src/commands/sessions/daemon/serverRunMeta.ts
25551
+ function serverRunMeta(runName, cwd) {
25552
+ const dir = cwd ?? process.cwd();
25553
+ const config = resolveRunConfig(runName, dir);
25554
+ if (!config?.server) return { server: false };
25555
+ return { server: true, port: config.port, origin: getCurrentOrigin(dir) };
25556
+ }
25557
+
25558
+ // src/commands/sessions/daemon/readDesignSystemPrompt.ts
25559
+ import * as fs35 from "fs";
25560
+ import * as path58 from "path";
25561
+ import { fileURLToPath as fileURLToPath8 } from "url";
25562
+ var __filename4 = fileURLToPath8(import.meta.url);
25563
+ var __dirname6 = path58.dirname(__filename4);
25564
+ function readDesignSystemPrompt() {
25565
+ const promptPath = path58.join(__dirname6, "..", "claude", "system-prompt.md");
25566
+ return fs35.readFileSync(promptPath, "utf8");
25567
+ }
25568
+
25506
25569
  // src/commands/sessions/daemon/spawnPty.ts
25507
25570
  import * as pty from "node-pty";
25508
25571
 
25509
25572
  // src/commands/sessions/daemon/ensureSpawnHelperExecutable.ts
25510
25573
  import { chmodSync, existsSync as existsSync57, statSync as statSync9 } from "fs";
25511
25574
  import { createRequire as createRequire3 } from "module";
25512
- import path58 from "path";
25575
+ import path59 from "path";
25513
25576
  var require4 = createRequire3(import.meta.url);
25514
25577
  var ensured = false;
25515
25578
  function ensureSpawnHelperExecutable() {
25516
25579
  if (ensured || process.platform !== "darwin") return;
25517
25580
  ensured = true;
25518
- const ptyRoot = path58.join(path58.dirname(require4.resolve("node-pty")), "..");
25519
- const helper = path58.join(
25581
+ const ptyRoot = path59.join(path59.dirname(require4.resolve("node-pty")), "..");
25582
+ const helper = path59.join(
25520
25583
  ptyRoot,
25521
25584
  "prebuilds",
25522
25585
  `${process.platform}-${process.arch}`,
@@ -25553,62 +25616,6 @@ function shellEscape(s) {
25553
25616
  return `'${s.replace(/'/g, String.raw`'\''`)}'`;
25554
25617
  }
25555
25618
 
25556
- // src/commands/sessions/daemon/createAssistSession.ts
25557
- function createAssistSession(id, assistArgs, cwd, meta) {
25558
- const startedAt = Date.now();
25559
- return {
25560
- id,
25561
- name: `assist ${assistArgs.join(" ")}`,
25562
- title: meta?.title,
25563
- subtitle: meta?.subtitle,
25564
- commandType: "assist",
25565
- status: "running",
25566
- startedAt,
25567
- runningMs: 0,
25568
- runningSince: startedAt,
25569
- pty: spawnPty(["assist", ...assistArgs], cwd, id),
25570
- scrollback: "",
25571
- assistArgs,
25572
- cwd
25573
- };
25574
- }
25575
-
25576
- // src/commands/sessions/daemon/createSession.ts
25577
- import { randomUUID as randomUUID5 } from "crypto";
25578
-
25579
- // src/commands/sessions/daemon/resolveRunConfig.ts
25580
- function resolveRunConfig(name, cwd) {
25581
- try {
25582
- const { run: run4 } = loadConfigFrom(cwd);
25583
- const configs = resolveRunConfigs(run4, getConfigDirFrom(cwd));
25584
- const exact = configs.find((r) => r.name === name);
25585
- if (exact) return exact;
25586
- const suffixMatches = configs.filter((r) => r.name.endsWith(`:${name}`));
25587
- return suffixMatches.length === 1 ? suffixMatches[0] : void 0;
25588
- } catch {
25589
- return void 0;
25590
- }
25591
- }
25592
-
25593
- // src/commands/sessions/daemon/serverRunMeta.ts
25594
- function serverRunMeta(runName, cwd) {
25595
- const dir = cwd ?? process.cwd();
25596
- const config = resolveRunConfig(runName, dir);
25597
- if (!config?.server) return { server: false };
25598
- return { server: true, port: config.port, origin: getCurrentOrigin(dir) };
25599
- }
25600
-
25601
- // src/commands/sessions/daemon/readDesignSystemPrompt.ts
25602
- import * as fs35 from "fs";
25603
- import * as path59 from "path";
25604
- import { fileURLToPath as fileURLToPath8 } from "url";
25605
- var __filename4 = fileURLToPath8(import.meta.url);
25606
- var __dirname6 = path59.dirname(__filename4);
25607
- function readDesignSystemPrompt() {
25608
- const promptPath = path59.join(__dirname6, "..", "claude", "system-prompt.md");
25609
- return fs35.readFileSync(promptPath, "utf8");
25610
- }
25611
-
25612
25619
  // src/commands/sessions/daemon/spawnClaude.ts
25613
25620
  function spawnClaude2(opts = {}) {
25614
25621
  return spawnPty(buildArgs3(opts), opts.cwd, opts.sessionId);
@@ -25693,6 +25700,170 @@ function createRunSession(id, runName, runArgs, cwd, meta = serverRunMeta(runNam
25693
25700
  };
25694
25701
  }
25695
25702
 
25703
+ // src/commands/sessions/daemon/worktree/reapWorktree.ts
25704
+ import { existsSync as existsSync58 } from "fs";
25705
+
25706
+ // src/commands/sessions/daemon/worktree/git.ts
25707
+ import { execFile as execFile7, execFileSync as execFileSync12 } from "child_process";
25708
+ function isWindowsPath(cwd) {
25709
+ return /^[A-Za-z]:[\\/]/.test(cwd);
25710
+ }
25711
+ function gitInvocation(cwd, args) {
25712
+ const windows = isWindowsPath(cwd);
25713
+ return {
25714
+ file: windows ? "git.exe" : "git",
25715
+ argv: windows ? ["-C", cwd, ...args] : args,
25716
+ options: { encoding: "utf8", ...windows ? {} : { cwd } }
25717
+ };
25718
+ }
25719
+ function git3(cwd, args) {
25720
+ const { file, argv, options: options2 } = gitInvocation(cwd, args);
25721
+ return new Promise((resolve19, reject) => {
25722
+ execFile7(file, argv, options2, (error, stdout) => {
25723
+ if (error) reject(error);
25724
+ else resolve19(stdout.toString());
25725
+ });
25726
+ });
25727
+ }
25728
+ async function gitOrNull(cwd, args) {
25729
+ try {
25730
+ return (await git3(cwd, args)).trim() || null;
25731
+ } catch {
25732
+ return null;
25733
+ }
25734
+ }
25735
+ function gitSync(cwd, args) {
25736
+ const { file, argv, options: options2 } = gitInvocation(cwd, args);
25737
+ return execFileSync12(file, argv, {
25738
+ ...options2,
25739
+ stdio: ["pipe", "pipe", "pipe"]
25740
+ }).toString();
25741
+ }
25742
+ function gitSyncOrNull(cwd, args) {
25743
+ try {
25744
+ return gitSync(cwd, args).trim() || null;
25745
+ } catch {
25746
+ return null;
25747
+ }
25748
+ }
25749
+ function listWorktreePaths(cwd) {
25750
+ const out = gitSyncOrNull(cwd, ["worktree", "list", "--porcelain"]);
25751
+ if (!out) return [];
25752
+ return out.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice("worktree ".length).trim());
25753
+ }
25754
+ function mainWorktree(cwd) {
25755
+ return listWorktreePaths(cwd)[0] ?? null;
25756
+ }
25757
+ function listLocalBranches(cwd) {
25758
+ const out = gitSyncOrNull(cwd, [
25759
+ "for-each-ref",
25760
+ "--format=%(refname:short)",
25761
+ "refs/heads"
25762
+ ]);
25763
+ return out ? out.split("\n").map((line) => line.trim()) : [];
25764
+ }
25765
+ function gitCommonDir(cwd) {
25766
+ return gitSyncOrNull(cwd, [
25767
+ "rev-parse",
25768
+ "--path-format=absolute",
25769
+ "--git-common-dir"
25770
+ ]);
25771
+ }
25772
+
25773
+ // src/commands/sessions/daemon/worktree/treeDurability.ts
25774
+ function treeDurability(state) {
25775
+ if (state.dirty) return { durable: false, reason: "uncommitted changes" };
25776
+ if (!state.push && state.localOnlyCommits)
25777
+ return { durable: false, reason: "unpushed commits" };
25778
+ return { durable: true };
25779
+ }
25780
+ async function hasLocalOnlyCommits(cwd) {
25781
+ const upstream = await gitOrNull(cwd, [
25782
+ "rev-parse",
25783
+ "--abbrev-ref",
25784
+ "--symbolic-full-name",
25785
+ "@{upstream}"
25786
+ ]);
25787
+ if (upstream) {
25788
+ const ahead = await gitOrNull(cwd, [
25789
+ "rev-list",
25790
+ "--count",
25791
+ "@{upstream}..HEAD"
25792
+ ]);
25793
+ return Number(ahead ?? "0") > 0;
25794
+ }
25795
+ const remoteBranches = await gitOrNull(cwd, [
25796
+ "branch",
25797
+ "-r",
25798
+ "--contains",
25799
+ "HEAD"
25800
+ ]);
25801
+ return !remoteBranches;
25802
+ }
25803
+ async function collectTreeState(cwd, push) {
25804
+ const status3 = await gitOrNull(cwd, ["status", "--porcelain"]);
25805
+ return {
25806
+ dirty: status3 !== null,
25807
+ push,
25808
+ localOnlyCommits: await hasLocalOnlyCommits(cwd)
25809
+ };
25810
+ }
25811
+ function pushOnCommit(cwd) {
25812
+ try {
25813
+ return loadConfigFrom(cwd).commit.push;
25814
+ } catch {
25815
+ return false;
25816
+ }
25817
+ }
25818
+ async function checkDurability(cwd) {
25819
+ return treeDurability(await collectTreeState(cwd, pushOnCommit(cwd)));
25820
+ }
25821
+
25822
+ // src/commands/sessions/daemon/worktree/reapWorktree.ts
25823
+ async function reapWorktree(worktreePath, force = false) {
25824
+ if (!existsSync58(worktreePath)) {
25825
+ daemonLog(`worktree ${worktreePath} already gone; skipping reap`);
25826
+ return false;
25827
+ }
25828
+ if (!force) {
25829
+ const durability = await checkDurability(worktreePath);
25830
+ if (!durability.durable) {
25831
+ daemonLog(`worktree ${worktreePath} not reaped: ${durability.reason}`);
25832
+ return false;
25833
+ }
25834
+ }
25835
+ const clone = mainWorktree(worktreePath) ?? worktreePath;
25836
+ const branch2 = await gitOrNull(worktreePath, [
25837
+ "symbolic-ref",
25838
+ "--short",
25839
+ "HEAD"
25840
+ ]);
25841
+ try {
25842
+ await git3(clone, [
25843
+ "worktree",
25844
+ "remove",
25845
+ ...force ? ["--force"] : [],
25846
+ worktreePath
25847
+ ]);
25848
+ if (branch2) await deleteBranch(clone, branch2);
25849
+ daemonLog(`worktree ${worktreePath} reaped${force ? " (forced)" : ""}`);
25850
+ return true;
25851
+ } catch (error) {
25852
+ daemonLog(
25853
+ `worktree ${worktreePath} reap failed: ${error instanceof Error ? error.message : String(error)}`
25854
+ );
25855
+ return false;
25856
+ }
25857
+ }
25858
+ async function deleteBranch(clone, branch2) {
25859
+ const current = await gitOrNull(clone, ["symbolic-ref", "--short", "HEAD"]);
25860
+ if (branch2 === current) return;
25861
+ try {
25862
+ await git3(clone, ["branch", "-D", branch2]);
25863
+ } catch {
25864
+ }
25865
+ }
25866
+
25696
25867
  // src/commands/sessions/daemon/dismissSession.ts
25697
25868
  function dismissSession(sessions, id) {
25698
25869
  const s = sessions.get(id);
@@ -25700,10 +25871,12 @@ function dismissSession(sessions, id) {
25700
25871
  if (s.status !== "done") s.pty?.kill();
25701
25872
  s.activityWatcher?.close();
25702
25873
  s.transcriptWatcher?.close();
25874
+ s.gitWatcher?.close();
25703
25875
  removeActivity(s.id);
25704
25876
  if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
25705
25877
  sessions.delete(id);
25706
25878
  daemonLog(`session ${id} dismissed (${s.name})`);
25879
+ if (s.worktree) void reapWorktree(s.worktree.path);
25707
25880
  return true;
25708
25881
  }
25709
25882
  function drainSessions(sessions, onDrained) {
@@ -25717,6 +25890,113 @@ function drainSessions(sessions, onDrained) {
25717
25890
  return ids.length;
25718
25891
  }
25719
25892
 
25893
+ // src/commands/sessions/daemon/killPtyTree.ts
25894
+ function killPtyTree(pty2) {
25895
+ if (process.platform === "win32") {
25896
+ pty2.kill();
25897
+ return;
25898
+ }
25899
+ try {
25900
+ process.kill(-pty2.pid, "SIGHUP");
25901
+ } catch {
25902
+ try {
25903
+ pty2.kill();
25904
+ } catch {
25905
+ }
25906
+ }
25907
+ }
25908
+
25909
+ // src/commands/sessions/daemon/setStatus.ts
25910
+ function setStatus2(session, newStatus) {
25911
+ const now = Date.now();
25912
+ if (session.status === "running" && session.runningSince != null) {
25913
+ session.runningMs += now - session.runningSince;
25914
+ }
25915
+ session.runningSince = newStatus === "running" ? now : null;
25916
+ session.status = newStatus;
25917
+ }
25918
+
25919
+ // src/commands/sessions/daemon/worktree/watchGitState.ts
25920
+ import { existsSync as existsSync59, watch } from "fs";
25921
+ var DEBOUNCE_MS = 500;
25922
+ function watchGitState(cwd, onChange) {
25923
+ const common = gitCommonDir(cwd);
25924
+ if (!common || !existsSync59(common)) return void 0;
25925
+ let timer = null;
25926
+ try {
25927
+ return watch(common, { recursive: true }, () => {
25928
+ if (timer) clearTimeout(timer);
25929
+ timer = setTimeout(onChange, DEBOUNCE_MS);
25930
+ });
25931
+ } catch (error) {
25932
+ daemonLog(
25933
+ `git-state watch failed for ${cwd}: ${error instanceof Error ? error.message : String(error)}`
25934
+ );
25935
+ return void 0;
25936
+ }
25937
+ }
25938
+
25939
+ // src/commands/sessions/daemon/worktree/resolveDoneDurability.ts
25940
+ async function resolveDoneDurability(session, finalize, notify2) {
25941
+ const worktree = session.worktree;
25942
+ if (!worktree) {
25943
+ finalize();
25944
+ return;
25945
+ }
25946
+ const durability = await checkDurability(worktree.path);
25947
+ if (!durability.durable) {
25948
+ holdStopped(session, durability.reason, finalize, notify2);
25949
+ return;
25950
+ }
25951
+ await reapWorktree(worktree.path);
25952
+ session.worktree = void 0;
25953
+ session.undurable = void 0;
25954
+ session.gitWatcher?.close();
25955
+ session.gitWatcher = void 0;
25956
+ finalize();
25957
+ }
25958
+ function holdStopped(session, reason, finalize, notify2) {
25959
+ if (session.undurable?.reason !== reason)
25960
+ daemonLog(`session ${session.id} stopped; reap blocked: ${reason}`);
25961
+ session.undurable = { reason };
25962
+ setStatus2(session, "stopped");
25963
+ if (!session.gitWatcher && session.worktree)
25964
+ session.gitWatcher = watchGitState(session.worktree.path, () => {
25965
+ if (session.status !== "stopped") return;
25966
+ void resolveDoneDurability(session, finalize, notify2);
25967
+ });
25968
+ notify2();
25969
+ }
25970
+
25971
+ // src/commands/sessions/daemon/dismissSessionGated.ts
25972
+ function dismissSessionGated(sessions, id, notify2, discard = false) {
25973
+ const s = sessions.get(id);
25974
+ if (!s?.worktree) {
25975
+ if (dismissSession(sessions, id)) notify2();
25976
+ return;
25977
+ }
25978
+ const removeCard = () => {
25979
+ if (dismissSession(sessions, id)) notify2();
25980
+ };
25981
+ const discardTree = async () => {
25982
+ if (s.worktree) {
25983
+ await reapWorktree(s.worktree.path, true);
25984
+ s.worktree = void 0;
25985
+ }
25986
+ removeCard();
25987
+ };
25988
+ if (s.pty) {
25989
+ s.pendingDismiss = discard ? () => void discardTree() : () => void resolveDoneDurability(s, removeCard, notify2);
25990
+ daemonLog(
25991
+ `session ${id} ${discard ? "discard" : "dismiss"} requested: killing process tree first`
25992
+ );
25993
+ killPtyTree(s.pty);
25994
+ return;
25995
+ }
25996
+ if (discard) void discardTree();
25997
+ else void resolveDoneDurability(s, removeCard, notify2);
25998
+ }
25999
+
25720
26000
  // src/shared/db/recordPhaseActiveMs.ts
25721
26001
  import { sql as sql20 } from "drizzle-orm";
25722
26002
  async function recordPhaseActiveMs(db, itemId2, phaseIdx, activeMs) {
@@ -25897,6 +26177,12 @@ function applySetStatus(sessions, id, status3, source, onStatusChange) {
25897
26177
  );
25898
26178
  return;
25899
26179
  }
26180
+ if (session.status === "stopped") {
26181
+ daemonLog(
26182
+ `set-status for stopped session id=${id} status=${status3} (held pending durable close; ignoring)`
26183
+ );
26184
+ return;
26185
+ }
25900
26186
  if (source === "permission") session.permissionActive = true;
25901
26187
  if (status3 === "running") session.permissionActive = false;
25902
26188
  onStatusChange(session, status3);
@@ -26070,16 +26356,6 @@ function applyUsageRecord(sessions, windows, notify2, claudeSessionId, transcrip
26070
26356
  notify2();
26071
26357
  }
26072
26358
 
26073
- // src/commands/sessions/daemon/setStatus.ts
26074
- function setStatus2(session, newStatus) {
26075
- const now = Date.now();
26076
- if (session.status === "running" && session.runningSince != null) {
26077
- session.runningMs += now - session.runningSince;
26078
- }
26079
- session.runningSince = newStatus === "running" ? now : null;
26080
- session.status = newStatus;
26081
- }
26082
-
26083
26359
  // src/commands/sessions/daemon/shouldAutoDismiss.ts
26084
26360
  function shouldAutoDismiss(session, exitCode) {
26085
26361
  if (session.status !== "done" || exitCode !== 0) {
@@ -26125,13 +26401,26 @@ function shouldAutoRun(session) {
26125
26401
  // src/commands/sessions/daemon/applyStatusChange.ts
26126
26402
  function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseForRun) {
26127
26403
  if (session.status === status3) return;
26404
+ const deps2 = { dismiss, notify: notify2, reuseForRun };
26405
+ if (status3 === "done" && session.worktree) {
26406
+ void resolveDoneDurability(
26407
+ session,
26408
+ () => finishStatusChange(session, "done", exitCode, deps2),
26409
+ notify2
26410
+ );
26411
+ return;
26412
+ }
26413
+ finishStatusChange(session, status3, exitCode, deps2);
26414
+ }
26415
+ function finishStatusChange(session, status3, exitCode, deps2) {
26128
26416
  daemonLog(`session ${session.id} status: ${session.status} -> ${status3}`);
26129
26417
  void flushPhaseActiveMs(session);
26130
26418
  setStatus2(session, status3);
26419
+ if (status3 !== "waiting") session.undurable = void 0;
26131
26420
  const autoRun = shouldAutoRun(session);
26132
26421
  if (autoRun.run) {
26133
- reuseForRun(session, autoRun.itemId);
26134
- notify2();
26422
+ deps2.reuseForRun(session, autoRun.itemId);
26423
+ deps2.notify();
26135
26424
  return;
26136
26425
  }
26137
26426
  if (status3 === "done" && autoRun.reason != null) {
@@ -26139,8 +26428,8 @@ function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseFo
26139
26428
  `session ${session.id} auto-run enabled but skipped (exit ${exitCode ?? "none"}): ${autoRun.reason}`
26140
26429
  );
26141
26430
  }
26142
- if (shouldAutoDismiss(session, exitCode)) dismiss(session.id);
26143
- else notify2();
26431
+ if (shouldAutoDismiss(session, exitCode)) deps2.dismiss(session.id);
26432
+ else deps2.notify();
26144
26433
  }
26145
26434
 
26146
26435
  // src/commands/sessions/daemon/makeStatusChangeHandler.ts
@@ -26161,7 +26450,7 @@ function handleFailedResume(session, exitCode, onStatusChange) {
26161
26450
  }
26162
26451
 
26163
26452
  // src/commands/sessions/daemon/watchActivity.ts
26164
- import { existsSync as existsSync58, mkdirSync as mkdirSync25, watch } from "fs";
26453
+ import { existsSync as existsSync60, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
26165
26454
  import { dirname as dirname29 } from "path";
26166
26455
 
26167
26456
  // src/commands/sessions/daemon/applyReviewPause.ts
@@ -26212,7 +26501,7 @@ async function seedRunningMsFromUsage(session, notify2) {
26212
26501
  }
26213
26502
 
26214
26503
  // src/commands/sessions/daemon/watchActivity.ts
26215
- var DEBOUNCE_MS = 50;
26504
+ var DEBOUNCE_MS2 = 50;
26216
26505
  function watchActivity(session, notify2, onClaudeSessionId) {
26217
26506
  if (session.commandType !== "assist" || !session.cwd) return;
26218
26507
  const path71 = activityPath(session.id);
@@ -26237,12 +26526,12 @@ function watchActivity(session, notify2, onClaudeSessionId) {
26237
26526
  void seedRunningMsFromUsage(session, notify2);
26238
26527
  notify2();
26239
26528
  };
26240
- session.activityWatcher = watch(dir, (_event, filename) => {
26529
+ session.activityWatcher = watch2(dir, (_event, filename) => {
26241
26530
  if (filename && !path71.endsWith(filename)) return;
26242
26531
  if (timer) clearTimeout(timer);
26243
- timer = setTimeout(read, DEBOUNCE_MS);
26532
+ timer = setTimeout(read, DEBOUNCE_MS2);
26244
26533
  });
26245
- if (existsSync58(path71)) read();
26534
+ if (existsSync60(path71)) read();
26246
26535
  }
26247
26536
  function refreshActivity(session) {
26248
26537
  if (session.commandType !== "assist" || !session.cwd) return;
@@ -26277,6 +26566,15 @@ function handlePtyExit(session, exitCode, onStatusChange) {
26277
26566
  return;
26278
26567
  }
26279
26568
  session.pty = null;
26569
+ if (session.pendingDismiss) {
26570
+ const dismiss = session.pendingDismiss;
26571
+ session.pendingDismiss = void 0;
26572
+ daemonLog(
26573
+ `session ${session.id} ("${session.name}") pty exited (code ${exitCode}) for dismiss; resolving durability now the process is gone`
26574
+ );
26575
+ dismiss();
26576
+ return;
26577
+ }
26280
26578
  if (handleStoppedExit(session, exitCode, onStatusChange)) return;
26281
26579
  refreshActivity(session);
26282
26580
  if (handleFailedResume(session, exitCode, onStatusChange)) return;
@@ -26317,6 +26615,10 @@ function wirePtyEvents(session, clients, onStatusChange) {
26317
26615
 
26318
26616
  // src/commands/sessions/daemon/respawnSession.ts
26319
26617
  function respawnSession(session, respawn, status3, clients, onStatusChange) {
26618
+ session.gitWatcher?.close();
26619
+ session.gitWatcher = void 0;
26620
+ session.undurable = void 0;
26621
+ session.pendingDismiss = void 0;
26320
26622
  session.scrollback = "";
26321
26623
  session.startedAt = Date.now();
26322
26624
  session.runningMs = 0;
@@ -26324,6 +26626,11 @@ function respawnSession(session, respawn, status3, clients, onStatusChange) {
26324
26626
  setStatus2(session, status3);
26325
26627
  session.restored = void 0;
26326
26628
  session.pty = respawn();
26629
+ if (session.cols && session.rows)
26630
+ try {
26631
+ session.pty?.resize(session.cols, session.rows);
26632
+ } catch {
26633
+ }
26327
26634
  broadcast(clients, { type: "clear", sessionId: session.id });
26328
26635
  wirePtyEvents(session, clients, onStatusChange);
26329
26636
  }
@@ -26405,20 +26712,6 @@ function restartSession(session, clients, onStatusChange) {
26405
26712
  }
26406
26713
  return true;
26407
26714
  }
26408
- function killPtyTree(pty2) {
26409
- if (process.platform === "win32") {
26410
- pty2.kill();
26411
- return;
26412
- }
26413
- try {
26414
- process.kill(-pty2.pid, "SIGHUP");
26415
- } catch {
26416
- try {
26417
- pty2.kill();
26418
- } catch {
26419
- }
26420
- }
26421
- }
26422
26715
 
26423
26716
  // src/commands/sessions/daemon/restartManagedSession.ts
26424
26717
  function restartManagedSession(sessions, id, clients, onStatusChange) {
@@ -26519,9 +26812,9 @@ function normalizeEntry(entry) {
26519
26812
  return null;
26520
26813
  }
26521
26814
  function normalizeAssistant(entry) {
26522
- const message = asRecord2(entry.message);
26523
- const stopReason = typeof message?.stop_reason === "string" ? message.stop_reason : null;
26524
- const content = message?.content;
26815
+ const message2 = asRecord2(entry.message);
26816
+ const stopReason = typeof message2?.stop_reason === "string" ? message2.stop_reason : null;
26817
+ const content = message2?.content;
26525
26818
  const toolUses = [];
26526
26819
  if (Array.isArray(content))
26527
26820
  for (const block of content) {
@@ -26699,6 +26992,12 @@ function runningSession(base, persisted, pty2) {
26699
26992
  function waitingSession(base, persisted, pty2) {
26700
26993
  return restoredSession(base, persisted, pty2, "waiting");
26701
26994
  }
26995
+ function stoppedSession(base, persisted) {
26996
+ return {
26997
+ ...restoredSession(base, persisted, null, "stopped"),
26998
+ undurable: persisted.undurable
26999
+ };
27000
+ }
26702
27001
  function restoredSession(base, persisted, pty2, status3) {
26703
27002
  const startedAt = Date.now();
26704
27003
  return {
@@ -26781,6 +27080,7 @@ function isUpdate(persisted) {
26781
27080
  // src/commands/sessions/daemon/restoreSession.ts
26782
27081
  function restoreSession(id, persisted) {
26783
27082
  const base = restoreBase(id, persisted);
27083
+ if (persisted.status === "stopped") return stoppedSession(base, persisted);
26784
27084
  if (isUpdate(persisted)) return updatedSession(id, persisted);
26785
27085
  const idle = deriveRestoreStatus(persisted) !== "running";
26786
27086
  if (needsWrapperRelaunch(persisted)) {
@@ -26862,27 +27162,6 @@ function restoreAll(spawn12, sessions) {
26862
27162
  });
26863
27163
  }
26864
27164
 
26865
- // src/commands/sessions/daemon/resumeSession.ts
26866
- function resumeSession(id, sessionId, cwd, name) {
26867
- const startedAt = Date.now();
26868
- return {
26869
- id,
26870
- name: name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`,
26871
- commandType: "claude",
26872
- status: "waiting",
26873
- startedAt,
26874
- runningMs: 0,
26875
- runningSince: null,
26876
- pty: spawnClaude2({ resumeSessionId: sessionId, cwd, sessionId: id }),
26877
- scrollback: "",
26878
- cwd,
26879
- /* why: bind the card to the conversation it is resuming so a daemon restart
26880
- * re-resumes this same transcript, not whichever .jsonl a cwd poller would
26881
- * have re-discovered (#413). */
26882
- claudeSessionId: sessionId
26883
- };
26884
- }
26885
-
26886
27165
  // src/commands/sessions/daemon/retrySession.ts
26887
27166
  function retrySession(session, clients, onStatusChange) {
26888
27167
  const respawn = respawnThunk(session);
@@ -26958,6 +27237,9 @@ function runRetry(sessions, id, replace, deps2) {
26958
27237
  function reuseSessionForRun(session, itemId2, clients, onStatusChange) {
26959
27238
  const assistArgs = ["backlog", "run", String(itemId2)];
26960
27239
  session.pty?.kill();
27240
+ session.gitWatcher?.close();
27241
+ session.gitWatcher = void 0;
27242
+ session.undurable = void 0;
26961
27243
  session.assistArgs = assistArgs;
26962
27244
  session.name = `assist ${assistArgs.join(" ")}`;
26963
27245
  session.commandType = "assist";
@@ -26987,7 +27269,7 @@ function shutdownSessions(sessions) {
26987
27269
  }
26988
27270
 
26989
27271
  // src/commands/sessions/daemon/watchTranscript.ts
26990
- import { existsSync as existsSync60, watch as watch2 } from "fs";
27272
+ import { existsSync as existsSync62, watch as watch3 } from "fs";
26991
27273
 
26992
27274
  // src/commands/sessions/daemon/reconcileTranscriptStatus.ts
26993
27275
  async function resolveTranscriptPath(session) {
@@ -26999,7 +27281,8 @@ async function resolveTranscriptPath(session) {
26999
27281
  return filePath;
27000
27282
  }
27001
27283
  async function reconcileTranscriptStatus(session, onStatusChange) {
27002
- if (session.status === "done" || session.status === "error") return;
27284
+ if (session.status === "done" || session.status === "error" || session.status === "stopped")
27285
+ return;
27003
27286
  const filePath = await resolveTranscriptPath(session);
27004
27287
  if (!filePath) return;
27005
27288
  const entries = await readTranscriptTail(filePath);
@@ -27016,12 +27299,12 @@ async function reconcileTranscriptStatus(session, onStatusChange) {
27016
27299
  }
27017
27300
 
27018
27301
  // src/commands/sessions/daemon/watchTranscript.ts
27019
- var DEBOUNCE_MS2 = 100;
27302
+ var DEBOUNCE_MS3 = 100;
27020
27303
  function watchTranscript(session, onStatusChange) {
27021
27304
  if (!session.cwd || !session.claudeSessionId) return;
27022
27305
  if (session.watchedTranscriptId === session.claudeSessionId) return;
27023
27306
  const dir = projectDirForCwd(session.cwd);
27024
- if (!existsSync60(dir)) return;
27307
+ if (!existsSync62(dir)) return;
27025
27308
  if (session.transcriptWatcher) {
27026
27309
  session.transcriptWatcher.close();
27027
27310
  session.transcriptWatcher = void 0;
@@ -27034,10 +27317,10 @@ function watchTranscript(session, onStatusChange) {
27034
27317
  void reconcileTranscriptStatus(session, onStatusChange);
27035
27318
  };
27036
27319
  try {
27037
- session.transcriptWatcher = watch2(dir, (_event, changed) => {
27320
+ session.transcriptWatcher = watch3(dir, (_event, changed) => {
27038
27321
  if (changed && changed !== fileName) return;
27039
27322
  if (timer) clearTimeout(timer);
27040
- timer = setTimeout(run4, DEBOUNCE_MS2);
27323
+ timer = setTimeout(run4, DEBOUNCE_MS3);
27041
27324
  });
27042
27325
  } catch (error) {
27043
27326
  daemonLog(
@@ -27172,19 +27455,19 @@ async function defaultConnect() {
27172
27455
  }
27173
27456
 
27174
27457
  // src/commands/sessions/daemon/hasPersistedWindowsSessions.ts
27175
- import { existsSync as existsSync61, readFileSync as readFileSync49 } from "fs";
27458
+ import { existsSync as existsSync63, readFileSync as readFileSync49 } from "fs";
27176
27459
  import { posix } from "path";
27177
27460
  function hasPersistedWindowsSessions() {
27178
27461
  const sessionsFile = windowsSessionsFileFromWsl();
27179
27462
  if (!sessionsFile) return false;
27180
27463
  try {
27181
- if (!existsSync61(sessionsFile)) return false;
27464
+ if (!existsSync63(sessionsFile)) return false;
27182
27465
  const data = JSON.parse(readFileSync49(sessionsFile, "utf8"));
27183
27466
  return Array.isArray(data) && data.length > 0;
27184
27467
  } catch (error) {
27185
- const message = error instanceof Error ? error.message : String(error);
27468
+ const message2 = error instanceof Error ? error.message : String(error);
27186
27469
  daemonLog(
27187
- `windows proxy: could not read windows sessions.json: ${message}`
27470
+ `windows proxy: could not read windows sessions.json: ${message2}`
27188
27471
  );
27189
27472
  return false;
27190
27473
  }
@@ -27211,8 +27494,8 @@ async function discoverWindowsSessions(conn) {
27211
27494
  try {
27212
27495
  await conn.ensure();
27213
27496
  } catch (error) {
27214
- const message = error instanceof Error ? error.message : String(error);
27215
- daemonLog(`windows proxy: discovery failed: ${message}`);
27497
+ const message2 = error instanceof Error ? error.message : String(error);
27498
+ daemonLog(`windows proxy: discovery failed: ${message2}`);
27216
27499
  }
27217
27500
  }
27218
27501
 
@@ -27250,11 +27533,11 @@ async function forwardWindowsCreate(conn, state, client, data) {
27250
27533
  state.pendingCreators.push({ client, timer });
27251
27534
  conn.write(stripOutboundSessionId(data));
27252
27535
  } catch (error) {
27253
- const message = error instanceof Error ? error.message : String(error);
27254
- daemonLog(`windows proxy: forwardCreate failed: ${message}`);
27536
+ const message2 = error instanceof Error ? error.message : String(error);
27537
+ daemonLog(`windows proxy: forwardCreate failed: ${message2}`);
27255
27538
  sendTo(client, {
27256
27539
  type: "error",
27257
- message: `Windows session unavailable: ${message}`
27540
+ message: `Windows session unavailable: ${message2}`
27258
27541
  });
27259
27542
  }
27260
27543
  }
@@ -27289,10 +27572,10 @@ function takePendingCreator(state) {
27289
27572
  clearTimeout(pending.timer);
27290
27573
  return pending.client;
27291
27574
  }
27292
- function failPendingCreators(state, message) {
27575
+ function failPendingCreators(state, message2) {
27293
27576
  for (const { client, timer } of state.pendingCreators) {
27294
27577
  clearTimeout(timer);
27295
- sendTo(client, { type: "error", message });
27578
+ sendTo(client, { type: "error", message: message2 });
27296
27579
  }
27297
27580
  state.pendingCreators = [];
27298
27581
  }
@@ -27432,9 +27715,9 @@ async function healWindowsDaemon() {
27432
27715
  try {
27433
27716
  await runOnWindowsHost("assist update", UPDATE_TIMEOUT_MS);
27434
27717
  } catch (error) {
27435
- const message = error instanceof Error ? error.message : String(error);
27718
+ const message2 = error instanceof Error ? error.message : String(error);
27436
27719
  daemonLog(
27437
- `windows daemon: auto-heal: \`assist update\` failed: ${message}`
27720
+ `windows daemon: auto-heal: \`assist update\` failed: ${message2}`
27438
27721
  );
27439
27722
  throw error;
27440
27723
  }
@@ -27609,11 +27892,11 @@ async function autoHealWindowsDaemon(conn, state, heal, version2) {
27609
27892
  daemonLog("windows proxy: heal complete, reconnecting to windows daemon");
27610
27893
  await conn.ensure();
27611
27894
  } catch (error) {
27612
- const message = error instanceof Error ? error.message : String(error);
27613
- daemonLog(`windows proxy: auto-heal failed: ${message}`);
27895
+ const message2 = error instanceof Error ? error.message : String(error);
27896
+ daemonLog(`windows proxy: auto-heal failed: ${message2}`);
27614
27897
  state.broadcast({
27615
27898
  type: "error",
27616
- message: `Windows host auto-update failed: ${message}`
27899
+ message: `Windows host auto-update failed: ${message2}`
27617
27900
  });
27618
27901
  }
27619
27902
  }
@@ -27721,6 +28004,335 @@ var WindowsProxy = class {
27721
28004
  }
27722
28005
  };
27723
28006
 
28007
+ // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
28008
+ import { existsSync as existsSync66 } from "fs";
28009
+ import { basename as basename14 } from "path";
28010
+
28011
+ // src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
28012
+ var DISABLED = {
28013
+ enabled: false,
28014
+ install: true,
28015
+ copy: [".env", "settings.local.json", ".claude/settings.local.json"]
28016
+ };
28017
+ function worktreeConfigFor(cwd) {
28018
+ try {
28019
+ return loadConfigFrom(cwd).worktree ?? DISABLED;
28020
+ } catch {
28021
+ return DISABLED;
28022
+ }
28023
+ }
28024
+
28025
+ // src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
28026
+ function detectExistingWorktree(cwd) {
28027
+ const repoRoot = findRepoRoot(cwd) ?? cwd;
28028
+ if (!worktreeConfigFor(repoRoot).enabled) return void 0;
28029
+ const clone = mainWorktree(repoRoot);
28030
+ if (clone && clone !== repoRoot) return { path: repoRoot, clone };
28031
+ return void 0;
28032
+ }
28033
+
28034
+ // src/commands/sessions/daemon/worktree/seedWorktree.ts
28035
+ import { execFile as execFile8 } from "child_process";
28036
+ import { copyFileSync, existsSync as existsSync65, mkdirSync as mkdirSync26 } from "fs";
28037
+ import { dirname as dirname30, join as join71 } from "path";
28038
+
28039
+ // src/commands/sessions/daemon/worktree/resolveInstallCommand.ts
28040
+ import { existsSync as existsSync64 } from "fs";
28041
+ import { join as join70 } from "path";
28042
+ function detectInstallCommand(repoRoot) {
28043
+ if (!existsSync64(join70(repoRoot, "package.json"))) return null;
28044
+ if (existsSync64(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
28045
+ if (existsSync64(join70(repoRoot, "yarn.lock"))) return "yarn install";
28046
+ if (existsSync64(join70(repoRoot, "bun.lockb"))) return "bun install";
28047
+ return "npm install";
28048
+ }
28049
+ function resolveInstallCommand(repoRoot, install) {
28050
+ if (install === false) return null;
28051
+ if (typeof install === "string") return install;
28052
+ return detectInstallCommand(repoRoot);
28053
+ }
28054
+
28055
+ // src/commands/sessions/daemon/worktree/seedWorktree.ts
28056
+ function seedWorktree(worktreePath, clone) {
28057
+ const cfg = worktreeConfigFor(clone);
28058
+ copyConfigFiles(worktreePath, clone, cfg.copy);
28059
+ runInstall(worktreePath, clone, cfg.install);
28060
+ }
28061
+ function copyConfigFiles(worktreePath, clone, copy) {
28062
+ for (const rel of copy) {
28063
+ const src = join71(clone, rel);
28064
+ if (!existsSync65(src)) continue;
28065
+ const dest = join71(worktreePath, rel);
28066
+ try {
28067
+ mkdirSync26(dirname30(dest), { recursive: true });
28068
+ copyFileSync(src, dest);
28069
+ daemonLog(`worktree ${worktreePath} seeded ${rel}`);
28070
+ } catch (error) {
28071
+ daemonLog(
28072
+ `worktree ${worktreePath} failed to seed ${rel}: ${message(error)}`
28073
+ );
28074
+ }
28075
+ }
28076
+ }
28077
+ function runInstall(worktreePath, clone, install) {
28078
+ const command = resolveInstallCommand(clone, install);
28079
+ if (!command) return;
28080
+ const windows = /^[A-Za-z]:[\\/]/.test(worktreePath);
28081
+ const shell = windows ? "cmd.exe" : process.env.SHELL ?? "bash";
28082
+ const shellArgs = windows ? ["/c", command] : ["-l", "-c", command];
28083
+ daemonLog(`worktree ${worktreePath} installing deps: ${command}`);
28084
+ execFile8(shell, shellArgs, { cwd: worktreePath }, (error) => {
28085
+ daemonLog(
28086
+ error ? `worktree ${worktreePath} install failed: ${error.message}` : `worktree ${worktreePath} install complete`
28087
+ );
28088
+ });
28089
+ }
28090
+ function message(error) {
28091
+ return error instanceof Error ? error.message : String(error);
28092
+ }
28093
+
28094
+ // src/commands/sessions/daemon/worktree/bindNewWorktree.ts
28095
+ function boundTreeRoots(sessions) {
28096
+ const roots = /* @__PURE__ */ new Set();
28097
+ for (const session of sessions.values())
28098
+ if (session.cwd) roots.add(findRepoRoot(session.cwd) ?? session.cwd);
28099
+ return roots;
28100
+ }
28101
+ function bindNewWorktree(session, alloc, notify2) {
28102
+ if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
28103
+ return;
28104
+ session.worktree = { path: alloc.cwd, clone: alloc.clone };
28105
+ if (alloc.created) seedWorktree(alloc.cwd, alloc.clone);
28106
+ notify2();
28107
+ }
28108
+ function bindRestoredWorktrees(sessions) {
28109
+ for (const session of sessions.values()) {
28110
+ if (session.worktree || !session.cwd) continue;
28111
+ const worktree = detectExistingWorktree(session.cwd);
28112
+ if (worktree) session.worktree = worktree;
28113
+ }
28114
+ }
28115
+ function bindResumedWorktree(session, cwd, notify2) {
28116
+ if (!session) return;
28117
+ const worktree = detectExistingWorktree(cwd);
28118
+ if (!worktree) return;
28119
+ session.worktree = worktree;
28120
+ notify2();
28121
+ }
28122
+
28123
+ // src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
28124
+ var REGISTRY_FILE = "worktrees.json";
28125
+ function readWorktreeRegistry() {
28126
+ const data = loadJson(REGISTRY_FILE);
28127
+ return Object.entries(data).map(([path71, record]) => ({ path: path71, ...record }));
28128
+ }
28129
+ function recordWorktree(path71, clone, origin) {
28130
+ const data = loadJson(REGISTRY_FILE);
28131
+ data[path71] = { clone, origin };
28132
+ saveJson(REGISTRY_FILE, data);
28133
+ }
28134
+
28135
+ // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
28136
+ function reconcileWorktreesOnRestore(sessions) {
28137
+ bindRestoredWorktrees(sessions);
28138
+ void pruneWorktrees([...sessions.values()].map((s) => s.cwd));
28139
+ }
28140
+ async function pruneWorktrees(liveCwds) {
28141
+ const accounted = /* @__PURE__ */ new Set();
28142
+ for (const cwd of liveCwds) if (cwd) accounted.add(cwd);
28143
+ for (const s of loadPersistedSessions()) accounted.add(s.cwd);
28144
+ const clonesToPrune = /* @__PURE__ */ new Set();
28145
+ const orphanBranches = [];
28146
+ for (const { path: path71, clone } of readWorktreeRegistry()) {
28147
+ if (accounted.has(path71)) continue;
28148
+ if (!existsSync66(path71)) {
28149
+ clonesToPrune.add(clone);
28150
+ orphanBranches.push({ clone, branch: basename14(path71) });
28151
+ continue;
28152
+ }
28153
+ daemonLog(`worktree ${path71} orphaned across restart; reconciling`);
28154
+ await reapWorktree(path71);
28155
+ }
28156
+ for (const clone of clonesToPrune) {
28157
+ if (!existsSync66(clone)) continue;
28158
+ try {
28159
+ await git3(clone, ["worktree", "prune"]);
28160
+ daemonLog(`worktree bookkeeping pruned for clone ${clone}`);
28161
+ } catch {
28162
+ }
28163
+ }
28164
+ for (const { clone, branch: branch2 } of orphanBranches)
28165
+ if (existsSync66(clone)) await pruneStaleWorktreeBranch(clone, branch2);
28166
+ }
28167
+ async function pruneStaleWorktreeBranch(clone, branch2) {
28168
+ if (!listLocalBranches(clone).includes(branch2)) return;
28169
+ try {
28170
+ await git3(clone, ["branch", "-d", branch2]);
28171
+ daemonLog(`stale worktree branch ${branch2} pruned from clone ${clone}`);
28172
+ } catch {
28173
+ daemonLog(
28174
+ `stale worktree branch ${branch2} retained (unmerged; surfaced not destroyed) in clone ${clone}`
28175
+ );
28176
+ }
28177
+ }
28178
+
28179
+ // src/commands/sessions/daemon/worktree/rearmStoppedSessions.ts
28180
+ function rearmStoppedSessions(sessions, notify2) {
28181
+ for (const s of sessions.values()) {
28182
+ if (s.status !== "stopped" || !s.worktree) continue;
28183
+ void resolveDoneDurability(
28184
+ s,
28185
+ () => {
28186
+ if (dismissSession(sessions, s.id)) notify2();
28187
+ },
28188
+ notify2
28189
+ );
28190
+ }
28191
+ }
28192
+
28193
+ // src/commands/sessions/daemon/createAssistSession.ts
28194
+ function createAssistSession(id, assistArgs, cwd, meta) {
28195
+ const startedAt = Date.now();
28196
+ return {
28197
+ id,
28198
+ name: `assist ${assistArgs.join(" ")}`,
28199
+ title: meta?.title,
28200
+ subtitle: meta?.subtitle,
28201
+ commandType: "assist",
28202
+ status: "running",
28203
+ startedAt,
28204
+ runningMs: 0,
28205
+ runningSince: startedAt,
28206
+ pty: spawnPty(["assist", ...assistArgs], cwd, id),
28207
+ scrollback: "",
28208
+ assistArgs,
28209
+ cwd
28210
+ };
28211
+ }
28212
+
28213
+ // src/commands/sessions/daemon/resumeSession.ts
28214
+ function resumeSession(id, sessionId, cwd, name) {
28215
+ const startedAt = Date.now();
28216
+ return {
28217
+ id,
28218
+ name: name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`,
28219
+ commandType: "claude",
28220
+ status: "waiting",
28221
+ startedAt,
28222
+ runningMs: 0,
28223
+ runningSince: null,
28224
+ pty: spawnClaude2({ resumeSessionId: sessionId, cwd, sessionId: id }),
28225
+ scrollback: "",
28226
+ cwd,
28227
+ /* why: bind the card to the conversation it is resuming so a daemon restart
28228
+ * re-resumes this same transcript, not whichever .jsonl a cwd poller would
28229
+ * have re-discovered (#413). */
28230
+ claudeSessionId: sessionId
28231
+ };
28232
+ }
28233
+
28234
+ // src/commands/sessions/daemon/worktree/createWorktree.ts
28235
+ import { existsSync as existsSync67 } from "fs";
28236
+ import { basename as basename16, dirname as dirname31 } from "path";
28237
+
28238
+ // src/commands/sessions/daemon/worktree/planAllocation.ts
28239
+ import { basename as basename15, join as join72 } from "path";
28240
+ function planAllocation(clone, boundTreeRoots2) {
28241
+ return boundTreeRoots2.has(clone) ? "spill" : "primary";
28242
+ }
28243
+ function nextWorktreePath(clone, base, isTaken) {
28244
+ const name = basename15(clone);
28245
+ for (let n = 2; n < 1e3; n++) {
28246
+ const candidate = join72(base, `${name}-${n}`);
28247
+ if (!isTaken(candidate)) return candidate;
28248
+ }
28249
+ throw new Error(`no free worktree suffix for ${clone}`);
28250
+ }
28251
+
28252
+ // src/commands/sessions/daemon/worktree/createWorktree.ts
28253
+ function createWorktree(clone, root, boundTreeRoots2) {
28254
+ const base = root ? expandTilde2(root) : dirname31(clone);
28255
+ const registered = new Set(listWorktreePaths(clone));
28256
+ const branches = new Set(listLocalBranches(clone));
28257
+ const path71 = nextWorktreePath(
28258
+ clone,
28259
+ base,
28260
+ (candidate) => registered.has(candidate) || existsSync67(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
28261
+ );
28262
+ const trunk = cloneTrunkBranch(clone);
28263
+ gitSync(clone, ["config", "push.default", "upstream"]);
28264
+ gitSync(clone, [
28265
+ "worktree",
28266
+ "add",
28267
+ "--track",
28268
+ "-b",
28269
+ basename16(path71),
28270
+ path71,
28271
+ `origin/${trunk}`
28272
+ ]);
28273
+ recordWorktree(path71, clone, getCurrentOrigin(clone));
28274
+ daemonLog(
28275
+ `worktree allocated ${path71} (branch ${basename16(path71)} tracking origin/${trunk}) for clone ${clone}`
28276
+ );
28277
+ return path71;
28278
+ }
28279
+ function cloneTrunkBranch(clone) {
28280
+ const branch2 = gitSyncOrNull(clone, ["symbolic-ref", "--short", "HEAD"]);
28281
+ if (branch2) return branch2;
28282
+ const head = gitSyncOrNull(clone, [
28283
+ "symbolic-ref",
28284
+ "--short",
28285
+ "refs/remotes/origin/HEAD"
28286
+ ]);
28287
+ if (head) {
28288
+ const slash = head.indexOf("/");
28289
+ return slash === -1 ? head : head.slice(slash + 1);
28290
+ }
28291
+ return "main";
28292
+ }
28293
+
28294
+ // src/commands/sessions/daemon/worktree/allocateTree.ts
28295
+ function allocateTree(requestedCwd, boundTreeRoots2) {
28296
+ if (!requestedCwd)
28297
+ return { cwd: requestedCwd, kind: "primary", created: false };
28298
+ const repoRoot = findRepoRoot(requestedCwd) ?? requestedCwd;
28299
+ const cfg = worktreeConfigFor(repoRoot);
28300
+ if (!cfg.enabled)
28301
+ return { cwd: requestedCwd, kind: "primary", created: false };
28302
+ const clone = mainWorktree(repoRoot) ?? repoRoot;
28303
+ if (planAllocation(clone, boundTreeRoots2) === "primary")
28304
+ return { cwd: clone, kind: "primary", created: false, clone };
28305
+ const path71 = createWorktree(clone, cfg.root, boundTreeRoots2);
28306
+ return { cwd: path71, kind: "worktree", created: true, clone };
28307
+ }
28308
+
28309
+ // src/commands/sessions/daemon/worktree/spawnInTree.ts
28310
+ function allocateAndBind(ctx, cwd, create) {
28311
+ const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions));
28312
+ const id = ctx.spawnWith((sid) => create(sid, alloc.cwd));
28313
+ bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify);
28314
+ return id;
28315
+ }
28316
+ function spawnInTree(ctx, prompt, cwd, design, harness) {
28317
+ return allocateAndBind(
28318
+ ctx,
28319
+ cwd,
28320
+ (sid, resolvedCwd) => createSession(sid, prompt, resolvedCwd, design, harness)
28321
+ );
28322
+ }
28323
+ function spawnAssistInTree(ctx, assistArgs, cwd, meta) {
28324
+ return allocateAndBind(
28325
+ ctx,
28326
+ cwd,
28327
+ (sid, resolvedCwd) => createAssistSession(sid, assistArgs, resolvedCwd, meta)
28328
+ );
28329
+ }
28330
+ function resumeInTree(ctx, sessionId, cwd, name) {
28331
+ const id = ctx.spawnWith((sid) => resumeSession(sid, sessionId, cwd, name));
28332
+ bindResumedWorktree(ctx.sessions.get(id), cwd, ctx.notify);
28333
+ return id;
28334
+ }
28335
+
27724
28336
  // src/commands/sessions/daemon/watchPromptSubmit.ts
27725
28337
  var CARRIAGE_RETURN = "\r";
27726
28338
  function isPromptSubmit(data) {
@@ -27744,6 +28356,8 @@ function writeToSession(sessions, id, data, onStatusChange) {
27744
28356
  function resizeSession(sessions, id, cols, rows) {
27745
28357
  const s = sessions.get(id);
27746
28358
  if (!s || s.status === "done" || !s.pty) return;
28359
+ s.cols = cols;
28360
+ s.rows = rows;
27747
28361
  try {
27748
28362
  s.pty.resize(cols, rows);
27749
28363
  } catch (error) {
@@ -27818,7 +28432,10 @@ var SessionManager = class {
27818
28432
  );
27819
28433
  }
27820
28434
  restore() {
27821
- return restoreAll(this.spawnWith, this.sessions);
28435
+ const names = restoreAll(this.spawnWith, this.sessions);
28436
+ reconcileWorktreesOnRestore(this.sessions);
28437
+ rearmStoppedSessions(this.sessions, this.notify);
28438
+ return names;
27822
28439
  }
27823
28440
  drain = () => drainSessions(this.sessions, this.notify);
27824
28441
  add(session) {
@@ -27828,10 +28445,15 @@ var SessionManager = class {
27828
28445
  return session.id;
27829
28446
  }
27830
28447
  spawnWith = (create) => this.add(create(sessionLimits.nextId(this.sessions.size, this.idCounter)));
28448
+ treeCtx() {
28449
+ return {
28450
+ sessions: this.sessions,
28451
+ spawnWith: this.spawnWith,
28452
+ notify: this.notify
28453
+ };
28454
+ }
27831
28455
  spawn(prompt, cwd, design, harness) {
27832
- return this.spawnWith(
27833
- (id) => createSession(id, prompt, cwd, design, harness)
27834
- );
28456
+ return spawnInTree(this.treeCtx(), prompt, cwd, design, harness);
27835
28457
  }
27836
28458
  spawnRun(runName, runArgs, cwd, meta) {
27837
28459
  return this.spawnWith(
@@ -27842,12 +28464,10 @@ var SessionManager = class {
27842
28464
  return liveServerRun(this.sessions, origin, excludeId);
27843
28465
  }
27844
28466
  spawnAssist(assistArgs, cwd, meta) {
27845
- return this.spawnWith(
27846
- (id) => createAssistSession(id, assistArgs, cwd, meta)
27847
- );
28467
+ return spawnAssistInTree(this.treeCtx(), assistArgs, cwd, meta);
27848
28468
  }
27849
28469
  resume(sessionId, cwd, name) {
27850
- return this.spawnWith((id) => resumeSession(id, sessionId, cwd, name));
28470
+ return resumeInTree(this.treeCtx(), sessionId, cwd, name);
27851
28471
  }
27852
28472
  onStatusChange = makeStatusChangeHandler(
27853
28473
  (id) => this.dismissSession(id),
@@ -27884,7 +28504,10 @@ var SessionManager = class {
27884
28504
  return result;
27885
28505
  }
27886
28506
  dismissSession = (id) => {
27887
- if (dismissSession(this.sessions, id)) this.notify();
28507
+ dismissSessionGated(this.sessions, id, this.notify);
28508
+ };
28509
+ discardSession = (id) => {
28510
+ dismissSessionGated(this.sessions, id, this.notify, true);
27888
28511
  };
27889
28512
  stopSession(id) {
27890
28513
  stopServerSession(this.sessions, id);
@@ -28165,6 +28788,7 @@ var messageHandlers = {
28165
28788
  sendTo(client, { type: "error", message: result.reason });
28166
28789
  }),
28167
28790
  dismiss: routed((_client, m, d) => m.dismissSession(d.sessionId)),
28791
+ discard: routed((_client, m, d) => m.discardSession(d.sessionId)),
28168
28792
  stop: routed((_client, m, d) => m.stopSession(d.sessionId)),
28169
28793
  "set-autorun": routed(
28170
28794
  (_client, m, d) => m.setAutoRun(d.sessionId, d.enabled)
@@ -28326,7 +28950,7 @@ async function recoverFromAddrInUse(server, manager, checkAutoExit) {
28326
28950
 
28327
28951
  // src/commands/sessions/daemon/runDaemon.ts
28328
28952
  async function runDaemon() {
28329
- mkdirSync26(daemonPaths.dir, { recursive: true });
28953
+ mkdirSync27(daemonPaths.dir, { recursive: true });
28330
28954
  daemonLog(
28331
28955
  `starting (reason: ${process.env.ASSIST_DAEMON_SPAWN_REASON ?? "manual"})`
28332
28956
  );
@@ -28379,6 +29003,50 @@ function registerSetStatusCommand(cmd) {
28379
29003
  );
28380
29004
  }
28381
29005
 
29006
+ // src/commands/sessions/sessionsConfigHelp.ts
29007
+ var sessionsConfigHelp = [
29008
+ {
29009
+ key: "sessions.windowsProjectsRoot",
29010
+ setter: "assist config set sessions.windowsProjectsRoot /mnt/c/Users/you/.claude/projects",
29011
+ note: "WSL path to the Windows .claude/projects root to discover"
29012
+ },
29013
+ {
29014
+ key: "sessions.windowsDaemonHost",
29015
+ setter: "assist config set sessions.windowsDaemonHost 127.0.0.1",
29016
+ note: "host the WSL daemon dials to reach the Windows daemon (default: 127.0.0.1)"
29017
+ },
29018
+ {
29019
+ key: "sessions.windowsDaemonPort",
29020
+ setter: "assist config set sessions.windowsDaemonPort 51764",
29021
+ note: "TCP port the Windows daemon listens on (default: 51764)"
29022
+ },
29023
+ {
29024
+ key: "sessions.windowsVersionCheck",
29025
+ setter: "assist config set sessions.windowsVersionCheck block",
29026
+ note: "Windows daemon version mismatch handling: block | warn | off"
29027
+ },
29028
+ {
29029
+ key: "worktree.enabled",
29030
+ setter: "assist config set worktree.enabled true --repo",
29031
+ note: "opt in per repo: spill concurrent sessions into adjacent <clone>-N worktrees (default off)"
29032
+ },
29033
+ {
29034
+ key: "worktree.root",
29035
+ setter: "assist config set worktree.root ~/git --repo",
29036
+ note: "optional; parent dir for <clone>-N worktrees (default: the clone's parent)"
29037
+ },
29038
+ {
29039
+ key: "worktree.install",
29040
+ setter: "assist config set worktree.install true --repo",
29041
+ note: "per-worktree dep install: true auto-detects the package manager, or give an explicit command; false to skip"
29042
+ },
29043
+ {
29044
+ key: "worktree.copy",
29045
+ setter: "assist config set worktree.copy .env,.claude/settings.local.json --repo",
29046
+ note: "gitignored files copied into a new worktree so it can build/run"
29047
+ }
29048
+ ];
29049
+
28382
29050
  // src/commands/sessions/summarise/index.ts
28383
29051
  import * as fs41 from "fs";
28384
29052
  import chalk204 from "chalk";
@@ -28397,7 +29065,7 @@ function summaryPathFor(jsonlPath2) {
28397
29065
  }
28398
29066
 
28399
29067
  // src/commands/sessions/summarise/summariseSession.ts
28400
- import { execFileSync as execFileSync12 } from "child_process";
29068
+ import { execFileSync as execFileSync13 } from "child_process";
28401
29069
 
28402
29070
  // src/commands/sessions/summarise/extractFirstUserMessage.ts
28403
29071
  function extractFirstUserMessage(filePath) {
@@ -28421,7 +29089,7 @@ function summariseSession(jsonlPath2) {
28421
29089
  }
28422
29090
  const prompt = buildPrompt3(firstMessage, backlogIds);
28423
29091
  try {
28424
- const output = execFileSync12("claude", ["-p", "--model", "haiku", prompt], {
29092
+ const output = execFileSync13("claude", ["-p", "--model", "haiku", prompt], {
28425
29093
  encoding: "utf8",
28426
29094
  timeout: 3e4,
28427
29095
  stdio: ["ignore", "pipe", "ignore"]
@@ -28516,28 +29184,7 @@ function registerSessions(program2) {
28516
29184
  );
28517
29185
  cmd.command("summarise").description("Generate one-line summaries for Claude sessions").option("-f, --force", "Re-generate all summaries, even existing ones").option("-n, --limit <count>", "Maximum number of sessions to summarise").action(summarise2);
28518
29186
  registerSetStatusCommand(cmd);
28519
- configHelp(cmd, [
28520
- {
28521
- key: "sessions.windowsProjectsRoot",
28522
- setter: "assist config set sessions.windowsProjectsRoot /mnt/c/Users/you/.claude/projects",
28523
- note: "WSL path to the Windows .claude/projects root to discover"
28524
- },
28525
- {
28526
- key: "sessions.windowsDaemonHost",
28527
- setter: "assist config set sessions.windowsDaemonHost 127.0.0.1",
28528
- note: "host the WSL daemon dials to reach the Windows daemon (default: 127.0.0.1)"
28529
- },
28530
- {
28531
- key: "sessions.windowsDaemonPort",
28532
- setter: "assist config set sessions.windowsDaemonPort 51764",
28533
- note: "TCP port the Windows daemon listens on (default: 51764)"
28534
- },
28535
- {
28536
- key: "sessions.windowsVersionCheck",
28537
- setter: "assist config set sessions.windowsVersionCheck block",
28538
- note: "Windows daemon version mismatch handling: block | warn | off"
28539
- }
28540
- ]);
29187
+ configHelp(cmd, sessionsConfigHelp);
28541
29188
  }
28542
29189
 
28543
29190
  // src/commands/statusLine.ts
@@ -28616,9 +29263,9 @@ function buildLimitsSegment(rateLimits) {
28616
29263
 
28617
29264
  // src/commands/readGitBranch.ts
28618
29265
  import { readFileSync as readFileSync52, statSync as statSync11 } from "fs";
28619
- import { isAbsolute as isAbsolute2, join as join70, resolve as resolve17 } from "path";
29266
+ import { isAbsolute as isAbsolute2, join as join73, resolve as resolve17 } from "path";
28620
29267
  function resolveGitDir(cwd) {
28621
- const dotGit = join70(cwd, ".git");
29268
+ const dotGit = join73(cwd, ".git");
28622
29269
  let stat2;
28623
29270
  try {
28624
29271
  stat2 = statSync11(dotGit);
@@ -28648,7 +29295,7 @@ function readGitBranch(cwd) {
28648
29295
  }
28649
29296
  let head;
28650
29297
  try {
28651
- head = readFileSync52(join70(gitDir, "HEAD"), "utf8");
29298
+ head = readFileSync52(join73(gitDir, "HEAD"), "utf8");
28652
29299
  } catch {
28653
29300
  return null;
28654
29301
  }