@staff0rd/assist 0.433.5 → 0.434.1

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.433.5",
9
+ version: "0.434.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -4198,7 +4198,7 @@ ${failed2.length} script(s) failed:`);
4198
4198
  }
4199
4199
  }
4200
4200
  function runEntry(entry) {
4201
- return new Promise((resolve19) => {
4201
+ return new Promise((resolve20) => {
4202
4202
  const startTime = Date.now();
4203
4203
  const child = spawnCommand(
4204
4204
  entry.fullCommand,
@@ -4210,7 +4210,7 @@ function runEntry(entry) {
4210
4210
  child.on("close", (code) => {
4211
4211
  const exitCode = code ?? 1;
4212
4212
  flushIfFailed(exitCode, chunks);
4213
- resolve19({
4213
+ resolve20({
4214
4214
  script: entry.name,
4215
4215
  code: exitCode,
4216
4216
  durationMs: Date.now() - startTime
@@ -5357,8 +5357,8 @@ function spawnInherit(command, args, options2 = {}) {
5357
5357
  env,
5358
5358
  cwd: options2.cwd
5359
5359
  });
5360
- const done2 = new Promise((resolve19, reject) => {
5361
- child.on("close", (code) => resolve19(code ?? 0));
5360
+ const done2 = new Promise((resolve20, reject) => {
5361
+ child.on("close", (code) => resolve20(code ?? 0));
5362
5362
  child.on("error", reject);
5363
5363
  });
5364
5364
  return { child, done: done2 };
@@ -6431,9 +6431,9 @@ Failed to launch Claude for ${context}: ${message2}`)
6431
6431
  // src/commands/sessions/daemon/connectToDaemon.ts
6432
6432
  import * as net from "net";
6433
6433
  function connectToDaemon() {
6434
- return new Promise((resolve19, reject) => {
6434
+ return new Promise((resolve20, reject) => {
6435
6435
  const socket = net.connect(daemonPaths.socket);
6436
- socket.once("connect", () => resolve19(socket));
6436
+ socket.once("connect", () => resolve20(socket));
6437
6437
  socket.once("error", reject);
6438
6438
  });
6439
6439
  }
@@ -6449,7 +6449,7 @@ async function isDaemonRunning() {
6449
6449
  // src/commands/sessions/daemon/sendToDaemon.ts
6450
6450
  var WRITE_TIMEOUT_MS = 500;
6451
6451
  function sendToDaemon(message2) {
6452
- return new Promise((resolve19, reject) => {
6452
+ return new Promise((resolve20, reject) => {
6453
6453
  connectToDaemon().then((socket) => {
6454
6454
  const timer = setTimeout(() => {
6455
6455
  socket.destroy();
@@ -6463,7 +6463,7 @@ function sendToDaemon(message2) {
6463
6463
  `, () => {
6464
6464
  clearTimeout(timer);
6465
6465
  socket.end();
6466
- resolve19();
6466
+ resolve20();
6467
6467
  });
6468
6468
  }, reject);
6469
6469
  });
@@ -6486,7 +6486,7 @@ function readSocketLines(socket, onLine) {
6486
6486
  // src/commands/sessions/daemon/sendToDaemonAwaitAck.ts
6487
6487
  var ACK_TIMEOUT_MS = 1e3;
6488
6488
  function sendToDaemonAwaitAck(message2) {
6489
- return new Promise((resolve19, reject) => {
6489
+ return new Promise((resolve20, reject) => {
6490
6490
  connectToDaemon().then((socket) => {
6491
6491
  let settled = false;
6492
6492
  const finish = (error) => {
@@ -6495,7 +6495,7 @@ function sendToDaemonAwaitAck(message2) {
6495
6495
  clearTimeout(timer);
6496
6496
  socket.destroy();
6497
6497
  if (error) reject(error);
6498
- else resolve19();
6498
+ else resolve20();
6499
6499
  };
6500
6500
  const timer = setTimeout(
6501
6501
  () => finish(new Error("timed out awaiting daemon ack")),
@@ -6569,7 +6569,7 @@ async function deliverReliably(sessionId, status3, payload) {
6569
6569
  }
6570
6570
  }
6571
6571
  function sleep(ms) {
6572
- return new Promise((resolve19) => setTimeout(resolve19, ms));
6572
+ return new Promise((resolve20) => setTimeout(resolve20, ms));
6573
6573
  }
6574
6574
  function describeError(error) {
6575
6575
  return error instanceof Error ? error.message : String(error);
@@ -8065,7 +8065,7 @@ function spawnDaemon(reason) {
8065
8065
  child.unref();
8066
8066
  }
8067
8067
  function delay(ms) {
8068
- return new Promise((resolve19) => setTimeout(resolve19, ms));
8068
+ return new Promise((resolve20) => setTimeout(resolve20, ms));
8069
8069
  }
8070
8070
 
8071
8071
  // src/shared/createBundleHandler.ts
@@ -8333,12 +8333,12 @@ async function loadVisibleItems(req) {
8333
8333
 
8334
8334
  // src/commands/backlog/web/parseStatusBody.ts
8335
8335
  function readBody(req) {
8336
- return new Promise((resolve19, reject) => {
8336
+ return new Promise((resolve20, reject) => {
8337
8337
  let body = "";
8338
8338
  req.on("data", (chunk) => {
8339
8339
  body += chunk.toString();
8340
8340
  });
8341
- req.on("end", () => resolve19(body));
8341
+ req.on("end", () => resolve20(body));
8342
8342
  req.on("error", reject);
8343
8343
  });
8344
8344
  }
@@ -8620,6 +8620,64 @@ async function diff2(req, res) {
8620
8620
  }
8621
8621
  }
8622
8622
 
8623
+ // src/commands/sessions/web/fileContent.ts
8624
+ import { readFile, stat as stat2 } from "fs/promises";
8625
+ import { isAbsolute, relative as relative2, resolve as resolve9 } from "path";
8626
+
8627
+ // src/commands/sessions/web/windowsCwdToWslPath.ts
8628
+ function windowsCwdToWslPath(cwd) {
8629
+ const match = /^([A-Za-z]):[\\/](.*)$/.exec(cwd);
8630
+ if (!match) return cwd;
8631
+ const drive = match[1].toLowerCase();
8632
+ const rest = match[2].replace(/\\/g, "/");
8633
+ return `/mnt/${drive}/${rest}`;
8634
+ }
8635
+
8636
+ // src/commands/sessions/web/toGitCwd.ts
8637
+ function toGitCwd(cwd) {
8638
+ return detectPlatform() === "wsl" ? windowsCwdToWslPath(cwd) : cwd;
8639
+ }
8640
+
8641
+ // src/commands/sessions/web/fileContent.ts
8642
+ var MAX_FILE_BYTES = 2 * 1024 * 1024;
8643
+ function resolveWithinCwd(cwd, path71) {
8644
+ const root = resolve9(toGitCwd(cwd));
8645
+ const target = resolve9(root, path71);
8646
+ const rel = relative2(root, target);
8647
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return null;
8648
+ return target;
8649
+ }
8650
+ async function fileContent(req, res) {
8651
+ const cwd = getCwdParam(req, res);
8652
+ if (!cwd) return;
8653
+ const path71 = new URL(req.url ?? "/", "http://localhost").searchParams.get(
8654
+ "path"
8655
+ );
8656
+ if (!path71) {
8657
+ respondJson(res, 400, { error: "Missing path" });
8658
+ return;
8659
+ }
8660
+ const target = resolveWithinCwd(cwd, path71);
8661
+ if (!target) {
8662
+ respondJson(res, 400, { error: "Path outside cwd" });
8663
+ return;
8664
+ }
8665
+ try {
8666
+ const info = await stat2(target);
8667
+ if (!info.isFile()) {
8668
+ respondJson(res, 404, { error: "File not found" });
8669
+ return;
8670
+ }
8671
+ if (info.size > MAX_FILE_BYTES) {
8672
+ respondJson(res, 413, { error: "File too large" });
8673
+ return;
8674
+ }
8675
+ respondJson(res, 200, { content: await readFile(target, "utf8") });
8676
+ } catch {
8677
+ respondJson(res, 404, { error: "File not found" });
8678
+ }
8679
+ }
8680
+
8623
8681
  // src/shared/db/listBackups.ts
8624
8682
  import { desc } from "drizzle-orm";
8625
8683
  async function listBackups(db) {
@@ -8663,20 +8721,6 @@ function getHtml() {
8663
8721
  </html>`;
8664
8722
  }
8665
8723
 
8666
- // src/commands/sessions/web/windowsCwdToWslPath.ts
8667
- function windowsCwdToWslPath(cwd) {
8668
- const match = /^([A-Za-z]):[\\/](.*)$/.exec(cwd);
8669
- if (!match) return cwd;
8670
- const drive = match[1].toLowerCase();
8671
- const rest = match[2].replace(/\\/g, "/");
8672
- return `/mnt/${drive}/${rest}`;
8673
- }
8674
-
8675
- // src/commands/sessions/web/toGitCwd.ts
8676
- function toGitCwd(cwd) {
8677
- return detectPlatform() === "wsl" ? windowsCwdToWslPath(cwd) : cwd;
8678
- }
8679
-
8680
8724
  // src/commands/sessions/web/handleServerRuns.ts
8681
8725
  function getServerRuns(rawCwd) {
8682
8726
  try {
@@ -9242,17 +9286,17 @@ async function stopDaemon() {
9242
9286
  }
9243
9287
  }
9244
9288
  function closedBeforeTimeout(socket) {
9245
- return new Promise((resolve19) => {
9289
+ return new Promise((resolve20) => {
9246
9290
  const timer = setTimeout(() => {
9247
9291
  socket.destroy();
9248
- resolve19(false);
9292
+ resolve20(false);
9249
9293
  }, STOP_TIMEOUT_MS);
9250
9294
  socket.resume();
9251
9295
  socket.on("error", () => {
9252
9296
  });
9253
9297
  socket.once("close", () => {
9254
9298
  clearTimeout(timer);
9255
- resolve19(true);
9299
+ resolve20(true);
9256
9300
  });
9257
9301
  });
9258
9302
  }
@@ -9303,8 +9347,8 @@ async function restartWeb(req, res, deps2 = {}) {
9303
9347
  respondJson(res, 400, { error: "Invalid target" });
9304
9348
  return;
9305
9349
  }
9306
- await new Promise((resolve19) => {
9307
- res.once("finish", resolve19);
9350
+ await new Promise((resolve20) => {
9351
+ res.once("finish", resolve20);
9308
9352
  respondJson(res, 200, { ok: true });
9309
9353
  });
9310
9354
  if (target === "daemon" || target === "both") {
@@ -9488,6 +9532,7 @@ var routes2 = {
9488
9532
  "GET /api/github-url": githubUrl,
9489
9533
  "GET /api/git-status": gitStatus,
9490
9534
  "GET /api/diff": diff2,
9535
+ "GET /api/file": fileContent,
9491
9536
  "GET /api/jira-site": jiraSite,
9492
9537
  "GET /api/harness": harnessCapabilities,
9493
9538
  "GET /api/pr-status": prStatus,
@@ -10275,7 +10320,7 @@ function registerExportCommand(cmd) {
10275
10320
  }
10276
10321
 
10277
10322
  // src/commands/backlog/import/index.ts
10278
- import { readFile } from "fs/promises";
10323
+ import { readFile as readFile2 } from "fs/promises";
10279
10324
  import chalk71 from "chalk";
10280
10325
 
10281
10326
  // src/commands/backlog/dump/countCopyRows.ts
@@ -10452,7 +10497,7 @@ async function restore(client, parsed) {
10452
10497
 
10453
10498
  // src/commands/backlog/import/index.ts
10454
10499
  async function importBacklog(file, options2 = {}) {
10455
- const raw = file ? await readFile(file) : await readStdinBuffer();
10500
+ const raw = file ? await readFile2(file) : await readStdinBuffer();
10456
10501
  const parsed = parseDump(raw);
10457
10502
  validateDump(parsed);
10458
10503
  const { tables } = parsed.header;
@@ -12396,7 +12441,7 @@ function findBuiltinDenyRaw(rawCommand) {
12396
12441
  }
12397
12442
 
12398
12443
  // src/shared/isApprovedRead.ts
12399
- import { resolve as resolve10, sep } from "path";
12444
+ import { resolve as resolve11, sep } from "path";
12400
12445
 
12401
12446
  // src/shared/tokenize.ts
12402
12447
  function tokenize(command) {
@@ -12478,7 +12523,7 @@ function extractGraphqlQuery(args) {
12478
12523
 
12479
12524
  // src/shared/loadCliReads.ts
12480
12525
  import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync22 } from "fs";
12481
- import { dirname as dirname21, resolve as resolve9 } from "path";
12526
+ import { dirname as dirname21, resolve as resolve10 } from "path";
12482
12527
  import { fileURLToPath as fileURLToPath5 } from "url";
12483
12528
  var __filename3 = fileURLToPath5(import.meta.url);
12484
12529
  var __dirname4 = dirname21(__filename3);
@@ -12493,13 +12538,13 @@ var cachedReads;
12493
12538
  var cachedWrites;
12494
12539
  function getCliReadsLines() {
12495
12540
  if (!cachedReads) {
12496
- cachedReads = readLines(resolve9(packageRoot(), "allowed.cli-reads"));
12541
+ cachedReads = readLines(resolve10(packageRoot(), "allowed.cli-reads"));
12497
12542
  }
12498
12543
  return cachedReads;
12499
12544
  }
12500
12545
  function getCliWritesLines() {
12501
12546
  if (!cachedWrites) {
12502
- cachedWrites = readLines(resolve9(packageRoot(), "allowed.cli-writes"));
12547
+ cachedWrites = readLines(resolve10(packageRoot(), "allowed.cli-writes"));
12503
12548
  }
12504
12549
  return cachedWrites;
12505
12550
  }
@@ -12508,7 +12553,7 @@ function loadCliReads() {
12508
12553
  }
12509
12554
  function saveCliReads(commands) {
12510
12555
  writeFileSync22(
12511
- resolve9(packageRoot(), "allowed.cli-reads"),
12556
+ resolve10(packageRoot(), "allowed.cli-reads"),
12512
12557
  `${commands.join("\n")}
12513
12558
  `
12514
12559
  );
@@ -12630,19 +12675,19 @@ function isCdToCwd(command) {
12630
12675
  const parts = command.split(/\s+/);
12631
12676
  if (parts[0] !== "cd" || parts.length > 2) return false;
12632
12677
  if (parts.length === 1) return false;
12633
- const resolved = resolve10(normalizeMsysPath(parts[1]));
12634
- return resolved === resolve10(process.cwd());
12678
+ const resolved = resolve11(normalizeMsysPath(parts[1]));
12679
+ return resolved === resolve11(process.cwd());
12635
12680
  }
12636
12681
  function isCdToReadAllowedDir(command) {
12637
12682
  const parts = command.split(/\s+/);
12638
12683
  if (parts[0] !== "cd" || parts.length !== 2) return void 0;
12639
- const target = resolve10(normalizeMsysPath(parts[1]));
12684
+ const target = resolve11(normalizeMsysPath(parts[1]));
12640
12685
  for (const entry of readSettingsPerms("allow")) {
12641
12686
  const m = entry.match(READ_RE);
12642
12687
  if (!m) continue;
12643
12688
  const base = globBaseDir(m[1]);
12644
12689
  if (!base) continue;
12645
- const resolved = resolve10(normalizeMsysPath(base));
12690
+ const resolved = resolve11(normalizeMsysPath(base));
12646
12691
  if (target === resolved || target.startsWith(resolved + sep)) {
12647
12692
  return `cd to Read-allowed directory: ${entry}`;
12648
12693
  }
@@ -12959,12 +13004,12 @@ function hasSubcommands(helpText) {
12959
13004
  // src/commands/permitCliReads/runHelp.ts
12960
13005
  import { exec as exec2 } from "child_process";
12961
13006
  function runHelp(args) {
12962
- return new Promise((resolve19) => {
13007
+ return new Promise((resolve20) => {
12963
13008
  exec2(
12964
13009
  `${args.join(" ")} --help`,
12965
13010
  { encoding: "utf8", timeout: 3e4 },
12966
13011
  (_err, stdout, stderr) => {
12967
- resolve19(stdout || stderr || "");
13012
+ resolve20(stdout || stderr || "");
12968
13013
  }
12969
13014
  );
12970
13015
  });
@@ -15285,10 +15330,10 @@ function findSlnFiles(dir, maxDepth, depth = 0) {
15285
15330
  continue;
15286
15331
  const full = path32.join(dir, entry);
15287
15332
  try {
15288
- const stat2 = statSync5(full);
15289
- if (stat2.isFile() && entry.endsWith(".sln")) {
15333
+ const stat3 = statSync5(full);
15334
+ if (stat3.isFile() && entry.endsWith(".sln")) {
15290
15335
  results.push(full);
15291
- } else if (stat2.isDirectory()) {
15336
+ } else if (stat3.isDirectory()) {
15292
15337
  results.push(...findSlnFiles(full, maxDepth, depth + 1));
15293
15338
  }
15294
15339
  } catch {
@@ -16753,12 +16798,12 @@ function registerList(program2) {
16753
16798
 
16754
16799
  // src/commands/mermaid/index.ts
16755
16800
  import { mkdirSync as mkdirSync16, readdirSync as readdirSync9 } from "fs";
16756
- import { resolve as resolve12 } from "path";
16801
+ import { resolve as resolve13 } from "path";
16757
16802
  import chalk156 from "chalk";
16758
16803
 
16759
16804
  // src/commands/mermaid/exportFile.ts
16760
16805
  import { readFileSync as readFileSync36, writeFileSync as writeFileSync31 } from "fs";
16761
- import { basename as basename11, extname as extname2, resolve as resolve11 } from "path";
16806
+ import { basename as basename11, extname as extname2, resolve as resolve12 } from "path";
16762
16807
  import chalk155 from "chalk";
16763
16808
 
16764
16809
  // src/commands/mermaid/renderBlock.ts
@@ -16806,7 +16851,7 @@ async function exportFile(file, outDir, krokiUrl, onlyIndex) {
16806
16851
  for (const [i, source] of blocks.entries()) {
16807
16852
  const idx = i + 1;
16808
16853
  if (onlyIndex !== void 0 && idx !== onlyIndex) continue;
16809
- const outPath = resolve11(outDir, `${stem}-${idx}.svg`);
16854
+ const outPath = resolve12(outDir, `${stem}-${idx}.svg`);
16810
16855
  const svg = await renderBlock(krokiUrl, source);
16811
16856
  writeFileSync31(outPath, svg, "utf8");
16812
16857
  console.log(chalk155.green(` \u2192 ${outPath}`));
@@ -16820,7 +16865,7 @@ function extractMermaidBlocks(markdown) {
16820
16865
  // src/commands/mermaid/index.ts
16821
16866
  async function mermaidExport(file, options2 = {}) {
16822
16867
  const { mermaid } = loadConfig();
16823
- const outDir = resolve12(process.cwd(), options2.out ?? ".");
16868
+ const outDir = resolve13(process.cwd(), options2.out ?? ".");
16824
16869
  mkdirSync16(outDir, { recursive: true });
16825
16870
  if (options2.index !== void 0) {
16826
16871
  if (!Number.isInteger(options2.index) || options2.index < 1) {
@@ -16945,7 +16990,7 @@ function createNetcapHandler(options2) {
16945
16990
  }
16946
16991
 
16947
16992
  // src/commands/netcap/prepareExtensionForLoad.ts
16948
- import { cp, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
16993
+ import { cp, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
16949
16994
  import { networkInterfaces } from "os";
16950
16995
  import { join as join47 } from "path";
16951
16996
  import chalk157 from "chalk";
@@ -16971,7 +17016,7 @@ function lanIPv4() {
16971
17016
  }
16972
17017
  async function configureBackground(dir, host, port, filter) {
16973
17018
  const file = join47(dir, "background.js");
16974
- const source = await readFile2(file, "utf8");
17019
+ const source = await readFile3(file, "utf8");
16975
17020
  await writeFile3(
16976
17021
  file,
16977
17022
  source.replace(
@@ -17010,7 +17055,7 @@ async function prepareExtensionForLoad(port, filter = "") {
17010
17055
  }
17011
17056
 
17012
17057
  // src/commands/netcap/resolveNetcapOutPath.ts
17013
- import { isAbsolute, join as join49, resolve as resolve13 } from "path";
17058
+ import { isAbsolute as isAbsolute2, join as join49, resolve as resolve14 } from "path";
17014
17059
 
17015
17060
  // src/commands/netcap/defaultCapturePath.ts
17016
17061
  import { homedir as homedir19 } from "os";
@@ -17022,7 +17067,7 @@ function defaultCapturePath() {
17022
17067
  // src/commands/netcap/resolveNetcapOutPath.ts
17023
17068
  function resolveNetcapOutPath(out) {
17024
17069
  if (!out) return defaultCapturePath();
17025
- const dir = isAbsolute(out) ? out : resolve13(process.cwd(), out);
17070
+ const dir = isAbsolute2(out) ? out : resolve14(process.cwd(), out);
17026
17071
  return join49(dir, "capture.jsonl");
17027
17072
  }
17028
17073
 
@@ -17113,25 +17158,25 @@ function isVisibleText(t) {
17113
17158
  return /[a-zA-Z]{3,}/.test(t);
17114
17159
  }
17115
17160
  var isHashtag = (t) => /^#[A-Za-z0-9_]+$/.test(t);
17116
- function collectRscText(v, resolve19, sink2, seen) {
17161
+ function collectRscText(v, resolve20, sink2, seen) {
17117
17162
  if (v == null) return;
17118
17163
  if (typeof v === "string") {
17119
17164
  if (isRscRef(v)) {
17120
17165
  if (!seen.has(v)) {
17121
17166
  seen.add(v);
17122
- collectRscText(resolve19(v), resolve19, sink2, seen);
17167
+ collectRscText(resolve20(v), resolve20, sink2, seen);
17123
17168
  }
17124
17169
  } else if (isHashtag(v)) sink2.hashtags.push(v);
17125
17170
  else if (isVisibleText(v)) sink2.text.push(v);
17126
17171
  return;
17127
17172
  }
17128
17173
  if (Array.isArray(v)) {
17129
- for (const x of v) collectRscText(x, resolve19, sink2, seen);
17174
+ for (const x of v) collectRscText(x, resolve20, sink2, seen);
17130
17175
  return;
17131
17176
  }
17132
17177
  if (typeof v === "object") {
17133
17178
  for (const val of Object.values(v)) {
17134
- collectRscText(val, resolve19, sink2, seen);
17179
+ collectRscText(val, resolve20, sink2, seen);
17135
17180
  }
17136
17181
  }
17137
17182
  }
@@ -17163,7 +17208,7 @@ function visitObjects(root, fn) {
17163
17208
  }
17164
17209
  }
17165
17210
  }
17166
- function buildMentionMap(rows, resolve19) {
17211
+ function buildMentionMap(rows, resolve20) {
17167
17212
  const map = /* @__PURE__ */ new Map();
17168
17213
  visitObjects(rows, (o) => {
17169
17214
  const url = profileActionUrl(o);
@@ -17171,7 +17216,7 @@ function buildMentionMap(rows, resolve19) {
17171
17216
  const slug = slugFromProfileUrl(url);
17172
17217
  if (!slug || map.has(slug)) return;
17173
17218
  const sink2 = { text: [], hashtags: [] };
17174
- collectRscText(o.children, resolve19, sink2, /* @__PURE__ */ new Set());
17219
+ collectRscText(o.children, resolve20, sink2, /* @__PURE__ */ new Set());
17175
17220
  const name = sink2.text.join(" ").replace(/\s+/g, " ").trim();
17176
17221
  map.set(slug, name ? { slug, name, url } : { slug, url });
17177
17222
  });
@@ -17257,10 +17302,10 @@ function buildPost(raw, mentionMap, author) {
17257
17302
 
17258
17303
  // src/commands/netcap/walkPostRow.ts
17259
17304
  var isCommentary = (o) => asObject(o.viewTrackingSpecs)?.viewName === "feed-commentary";
17260
- function walkPostRow(v, resolve19, raw) {
17305
+ function walkPostRow(v, resolve20, raw) {
17261
17306
  if (v == null || typeof v !== "object") return;
17262
17307
  if (Array.isArray(v)) {
17263
- for (const x of v) walkPostRow(x, resolve19, raw);
17308
+ for (const x of v) walkPostRow(x, resolve20, raw);
17264
17309
  return;
17265
17310
  }
17266
17311
  const o = v;
@@ -17274,9 +17319,9 @@ function walkPostRow(v, resolve19, raw) {
17274
17319
  }
17275
17320
  if (isCommentary(o)) {
17276
17321
  const sink2 = { text: raw.text, hashtags: raw.hashtags };
17277
- collectRscText(o.children, resolve19, sink2, /* @__PURE__ */ new Set());
17322
+ collectRscText(o.children, resolve20, sink2, /* @__PURE__ */ new Set());
17278
17323
  }
17279
- for (const val of Object.values(o)) walkPostRow(val, resolve19, raw);
17324
+ for (const val of Object.values(o)) walkPostRow(val, resolve20, raw);
17280
17325
  }
17281
17326
 
17282
17327
  // src/commands/netcap/extractLinkedInPosts.ts
@@ -17290,8 +17335,8 @@ function findCommentaryRows(rows) {
17290
17335
  }
17291
17336
  function extractLinkedInPosts(flight, author = findAuthorSlug(flight)) {
17292
17337
  const rows = parseRscRows(flight);
17293
- const resolve19 = makeRscResolver(rows);
17294
- const mentionMap = buildMentionMap(rows, resolve19);
17338
+ const resolve20 = makeRscResolver(rows);
17339
+ const mentionMap = buildMentionMap(rows, resolve20);
17295
17340
  const posts = [];
17296
17341
  for (const id of findCommentaryRows(rows)) {
17297
17342
  const raw = {
@@ -17301,7 +17346,7 @@ function extractLinkedInPosts(flight, author = findAuthorSlug(flight)) {
17301
17346
  links: [],
17302
17347
  related: []
17303
17348
  };
17304
- walkPostRow(rows[id], resolve19, raw);
17349
+ walkPostRow(rows[id], resolve20, raw);
17305
17350
  const post = buildPost(raw, mentionMap, author);
17306
17351
  if (post) posts.push(post);
17307
17352
  }
@@ -18600,7 +18645,7 @@ function parseIncoming(line, requestId) {
18600
18645
 
18601
18646
  // src/commands/prs/requestPrDecision.ts
18602
18647
  function requestPrDecision(request) {
18603
- return new Promise((resolve19, reject) => {
18648
+ return new Promise((resolve20, reject) => {
18604
18649
  connectToDaemon().then((socket) => {
18605
18650
  let settled = false;
18606
18651
  const finish = (error, decision) => {
@@ -18608,7 +18653,7 @@ function requestPrDecision(request) {
18608
18653
  settled = true;
18609
18654
  socket.destroy();
18610
18655
  if (error) reject(error);
18611
- else resolve19(decision);
18656
+ else resolve20(decision);
18612
18657
  };
18613
18658
  readSocketLines(socket, (line) => {
18614
18659
  const incoming = parseIncoming(line, request.requestId);
@@ -19510,7 +19555,7 @@ function getViolations(pattern2, options2 = {}, maxLines = DEFAULT_MAX_LINES) {
19510
19555
 
19511
19556
  // src/commands/refactor/check/index.ts
19512
19557
  function runScript(script, cwd) {
19513
- return new Promise((resolve19) => {
19558
+ return new Promise((resolve20) => {
19514
19559
  const child = spawn5("npm", ["run", script], {
19515
19560
  stdio: "pipe",
19516
19561
  shell: true,
@@ -19524,7 +19569,7 @@ function runScript(script, cwd) {
19524
19569
  output += data.toString();
19525
19570
  });
19526
19571
  child.on("close", (code) => {
19527
- resolve19({ script, code: code ?? 1, output });
19572
+ resolve20({ script, code: code ?? 1, output });
19528
19573
  });
19529
19574
  });
19530
19575
  }
@@ -22336,12 +22381,12 @@ function onCloseResult(ctx, code) {
22336
22381
  return { ...closed, stderr: ctx.stderr.value, stdout: ctx.stdout.value };
22337
22382
  }
22338
22383
  function waitForChildExit(ctx) {
22339
- return new Promise((resolve19) => {
22384
+ return new Promise((resolve20) => {
22340
22385
  let settled = false;
22341
22386
  const settle = (result) => {
22342
22387
  if (settled) return;
22343
22388
  settled = true;
22344
- resolve19(result);
22389
+ resolve20(result);
22345
22390
  };
22346
22391
  ctx.child.on("error", (err) => settle(onErrorResult(ctx, err)));
22347
22392
  ctx.child.on("close", (code) => settle(onCloseResult(ctx, code)));
@@ -23448,9 +23493,9 @@ function createReadlineInterface() {
23448
23493
  });
23449
23494
  }
23450
23495
  function askQuestion(rl, question) {
23451
- return new Promise((resolve19) => {
23496
+ return new Promise((resolve20) => {
23452
23497
  rl.question(question, (answer) => {
23453
- resolve19(answer.trim());
23498
+ resolve20(answer.trim());
23454
23499
  });
23455
23500
  });
23456
23501
  }
@@ -24215,7 +24260,7 @@ function extractCode(url, expectedState) {
24215
24260
  return code;
24216
24261
  }
24217
24262
  function waitForCallback(port, expectedState) {
24218
- return new Promise((resolve19, reject) => {
24263
+ return new Promise((resolve20, reject) => {
24219
24264
  const timeout = setTimeout(() => {
24220
24265
  server.close();
24221
24266
  reject(new Error("Authorization timed out after 120 seconds"));
@@ -24232,7 +24277,7 @@ function waitForCallback(port, expectedState) {
24232
24277
  const code = extractCode(url, expectedState);
24233
24278
  respondHtml(res, 200, "Authorization successful!");
24234
24279
  server.close();
24235
- resolve19(code);
24280
+ resolve20(code);
24236
24281
  } catch (error) {
24237
24282
  respondHtml(res, 400, error.message);
24238
24283
  server.close();
@@ -24497,7 +24542,7 @@ var rootConfigHelp = {
24497
24542
  };
24498
24543
 
24499
24544
  // src/commands/run/index.ts
24500
- import { resolve as resolve15 } from "path";
24545
+ import { resolve as resolve16 } from "path";
24501
24546
 
24502
24547
  // src/commands/run/findRunConfig.ts
24503
24548
  function exitNoRunConfigs() {
@@ -24596,12 +24641,12 @@ function runPreCommands(pre, cwd) {
24596
24641
  // src/commands/run/spawnRunCommand.ts
24597
24642
  import { execFileSync as execFileSync10, spawn as spawn8 } from "child_process";
24598
24643
  import { existsSync as existsSync54 } from "fs";
24599
- import { dirname as dirname27, join as join64, resolve as resolve14 } from "path";
24644
+ import { dirname as dirname27, join as join64, resolve as resolve15 } from "path";
24600
24645
  function resolveCommand2(command) {
24601
24646
  if (process.platform !== "win32" || command !== "bash") return command;
24602
24647
  try {
24603
24648
  const gitPath = execFileSync10("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
24604
- const gitRoot = resolve14(dirname27(gitPath), "..");
24649
+ const gitRoot = resolve15(dirname27(gitPath), "..");
24605
24650
  const gitBash = join64(gitRoot, "bin", "bash.exe");
24606
24651
  if (existsSync54(gitBash)) return gitBash;
24607
24652
  } catch {
@@ -24649,7 +24694,7 @@ function listRunConfigs(verbose) {
24649
24694
  }
24650
24695
  }
24651
24696
  function execRunConfig(config, args) {
24652
- const cwd = config.cwd ? resolve15(getConfigDir(), config.cwd) : void 0;
24697
+ const cwd = config.cwd ? resolve16(getConfigDir(), config.cwd) : void 0;
24653
24698
  if (config.pre) runPreCommands(config.pre, cwd);
24654
24699
  const resolved = resolveParams(config.params, args);
24655
24700
  spawnRunCommand(
@@ -24889,7 +24934,7 @@ function registerRun(program2) {
24889
24934
  import { execSync as execSync58 } from "child_process";
24890
24935
  import { existsSync as existsSync56, mkdirSync as mkdirSync24, unlinkSync as unlinkSync21, writeFileSync as writeFileSync42 } from "fs";
24891
24936
  import { tmpdir as tmpdir8 } from "os";
24892
- import { join as join67, resolve as resolve16 } from "path";
24937
+ import { join as join67, resolve as resolve17 } from "path";
24893
24938
  import chalk203 from "chalk";
24894
24939
 
24895
24940
  // src/commands/screenshot/captureWindowPs1.ts
@@ -25023,7 +25068,7 @@ function buildOutputPath(outputDir, processName) {
25023
25068
  mkdirSync24(outputDir, { recursive: true });
25024
25069
  }
25025
25070
  const timestamp6 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
25026
- return resolve16(outputDir, `${processName}-${timestamp6}.png`);
25071
+ return resolve17(outputDir, `${processName}-${timestamp6}.png`);
25027
25072
  }
25028
25073
  function runPowerShellScript(processName, outputPath) {
25029
25074
  const scriptPath = join67(tmpdir8(), `assist-screenshot-${Date.now()}.ps1`);
@@ -25039,7 +25084,7 @@ function runPowerShellScript(processName, outputPath) {
25039
25084
  }
25040
25085
  function screenshot(processName) {
25041
25086
  const config = loadConfig();
25042
- const outputDir = resolve16(config.screenshot.outputDir);
25087
+ const outputDir = resolve17(config.screenshot.outputDir);
25043
25088
  const outputPath = buildOutputPath(outputDir, processName);
25044
25089
  console.log(chalk203.gray(`Capturing window for process "${processName}" ...`));
25045
25090
  try {
@@ -25072,10 +25117,10 @@ var STATUS_TIMEOUT_MS = 5e3;
25072
25117
  function queryDaemon(socket) {
25073
25118
  socket.write(`${JSON.stringify({ type: "ping" })}
25074
25119
  `);
25075
- return new Promise((resolve19) => {
25120
+ return new Promise((resolve20) => {
25076
25121
  const result = { sessions: [] };
25077
25122
  const pending = /* @__PURE__ */ new Set(["sessions", "pong"]);
25078
- const timer = setTimeout(() => resolve19(result), STATUS_TIMEOUT_MS);
25123
+ const timer = setTimeout(() => resolve20(result), STATUS_TIMEOUT_MS);
25079
25124
  const lines = createInterface5({ input: socket });
25080
25125
  lines.on("error", () => {
25081
25126
  });
@@ -25083,7 +25128,7 @@ function queryDaemon(socket) {
25083
25128
  applyLine(result, pending, line);
25084
25129
  if (pending.size === 0) {
25085
25130
  clearTimeout(timer);
25086
- resolve19(result);
25131
+ resolve20(result);
25087
25132
  }
25088
25133
  });
25089
25134
  });
@@ -25200,7 +25245,7 @@ var persistedSessionSchema = z5.object({
25200
25245
  assistArgs: z5.array(z5.string()).optional(),
25201
25246
  activity: activitySchema.optional(),
25202
25247
  starred: z5.boolean().optional(),
25203
- undurable: z5.object({ reason: z5.string() }).optional()
25248
+ undurable: z5.object({ reason: z5.string(), removesTree: z5.boolean().optional() }).optional()
25204
25249
  });
25205
25250
 
25206
25251
  // src/commands/sessions/daemon/toPersistedSession.ts
@@ -25259,40 +25304,95 @@ function logPersist(live) {
25259
25304
  );
25260
25305
  }
25261
25306
 
25307
+ // src/commands/sessions/daemon/clearPersistedSessionsOnDrain.ts
25308
+ function clearPersistedSessionsOnDrain() {
25309
+ const held = loadPersistedSessions().filter((s) => s.status === "stopped");
25310
+ savePersistedSessions(held);
25311
+ console.log(
25312
+ held.length > 0 ? `Sessions daemon is not running; cleared persisted sessions (kept ${held.length} stopped session(s) holding unpushed work)` : "Sessions daemon is not running; cleared persisted sessions"
25313
+ );
25314
+ }
25315
+
25316
+ // src/commands/sessions/daemon/readDaemonMessage.ts
25317
+ function readDaemonMessage(lines, timeoutMs, fallback, match) {
25318
+ return new Promise((resolve20) => {
25319
+ const finish = (value) => {
25320
+ clearTimeout(timer);
25321
+ lines.off("line", onLine);
25322
+ resolve20(value);
25323
+ };
25324
+ const timer = setTimeout(() => finish(fallback), timeoutMs);
25325
+ const onLine = (line) => {
25326
+ try {
25327
+ const matched = match(JSON.parse(line));
25328
+ if (matched !== void 0) finish(matched);
25329
+ } catch {
25330
+ }
25331
+ };
25332
+ lines.on("line", onLine);
25333
+ });
25334
+ }
25335
+
25262
25336
  // src/commands/sessions/daemon/drainDaemon.ts
25263
25337
  var DRAIN_TIMEOUT_MS = 5e3;
25264
- async function drainDaemon() {
25338
+ var LIST_TIMEOUT_MS = 2e3;
25339
+ async function drainDaemon(options2 = {}) {
25265
25340
  let socket;
25266
25341
  try {
25267
25342
  socket = await connectToDaemon();
25268
25343
  } catch {
25269
- savePersistedSessions([]);
25270
- console.log("Sessions daemon is not running; cleared persisted sessions");
25344
+ clearPersistedSessionsOnDrain();
25271
25345
  return;
25272
25346
  }
25273
- const count7 = await requestDrain(socket);
25347
+ const lines = createInterface6({ input: socket });
25348
+ lines.on("error", () => {
25349
+ });
25350
+ const live = await liveSessions(lines);
25351
+ if (live.length > 0 && options2.yes !== true) {
25352
+ reportLive(live);
25353
+ if (!await confirmDrain()) {
25354
+ socket.destroy();
25355
+ return;
25356
+ }
25357
+ }
25358
+ const count7 = await requestDrain(socket, lines);
25274
25359
  socket.destroy();
25275
25360
  console.log(`Drained ${count7} session(s)`);
25276
25361
  }
25277
- function requestDrain(socket) {
25362
+ function reportLive(live) {
25363
+ console.log(`${live.length} live session(s) would be closed:`);
25364
+ for (const s of live)
25365
+ console.log(` - ${s.name} [${s.status}]${s.cwd ? ` in ${s.cwd}` : ""}`);
25366
+ }
25367
+ async function confirmDrain() {
25368
+ if (!process.stdin.isTTY) {
25369
+ console.log("Refusing to close live sessions; re-run with --yes");
25370
+ process.exitCode = 1;
25371
+ return false;
25372
+ }
25373
+ if (await promptConfirm("Close them all?", false)) return true;
25374
+ console.log("Drain cancelled");
25375
+ return false;
25376
+ }
25377
+ function liveSessions(lines) {
25378
+ return readDaemonMessage(
25379
+ lines,
25380
+ LIST_TIMEOUT_MS,
25381
+ [],
25382
+ (data) => data.type === "sessions" ? (data.sessions ?? []).filter(
25383
+ (s) => s.status === "running" || s.status === "waiting"
25384
+ ) : void 0
25385
+ );
25386
+ }
25387
+ function requestDrain(socket, lines) {
25278
25388
  socket.write(`${JSON.stringify({ type: "drain" })}
25279
25389
  `);
25280
- return new Promise((resolve19) => {
25281
- const timer = setTimeout(() => resolve19(0), DRAIN_TIMEOUT_MS);
25282
- const lines = createInterface6({ input: socket });
25283
- lines.on("error", () => {
25284
- });
25285
- lines.on("line", (line) => {
25286
- try {
25287
- const data = JSON.parse(line);
25288
- if (data.type === "drained") {
25289
- clearTimeout(timer);
25290
- resolve19(data.count ?? 0);
25291
- }
25292
- } catch {
25293
- }
25294
- });
25295
- });
25390
+ return readDaemonMessage(
25391
+ lines,
25392
+ DRAIN_TIMEOUT_MS,
25393
+ 0,
25394
+ (data) => data.type === "drained" ? data.count ?? 0 : void 0
25395
+ );
25296
25396
  }
25297
25397
 
25298
25398
  // src/commands/sessions/daemon/runDaemon.ts
@@ -25702,8 +25802,40 @@ function createRunSession(id, runName, runArgs, cwd, meta = serverRunMeta(runNam
25702
25802
  };
25703
25803
  }
25704
25804
 
25805
+ // src/commands/sessions/daemon/killPtyTree.ts
25806
+ function killPtyTree(pty2) {
25807
+ if (process.platform === "win32") {
25808
+ pty2.kill();
25809
+ return;
25810
+ }
25811
+ try {
25812
+ process.kill(-pty2.pid, "SIGHUP");
25813
+ } catch {
25814
+ try {
25815
+ pty2.kill();
25816
+ } catch {
25817
+ }
25818
+ }
25819
+ }
25820
+
25821
+ // src/commands/sessions/daemon/worktree/treeUnderClose.ts
25822
+ function treeUnderClose(session) {
25823
+ if (session.worktree) return { path: session.worktree.path, removable: true };
25824
+ if (session.cwd) return { path: session.cwd, removable: false };
25825
+ return void 0;
25826
+ }
25827
+
25828
+ // src/commands/sessions/daemon/worktree/otherTreeHolders.ts
25829
+ function otherTreeHolders(sessions, session) {
25830
+ const tree = treeUnderClose(session)?.path;
25831
+ if (!tree) return [];
25832
+ return [...sessions.values()].filter(
25833
+ (s) => s.id !== session.id && treeUnderClose(s)?.path === tree
25834
+ );
25835
+ }
25836
+
25705
25837
  // src/commands/sessions/daemon/worktree/reapWorktree.ts
25706
- import { existsSync as existsSync58 } from "fs";
25838
+ import { existsSync as existsSync59 } from "fs";
25707
25839
 
25708
25840
  // src/commands/sessions/daemon/worktree/git.ts
25709
25841
  import { execFile as execFile7, execFileSync as execFileSync12 } from "child_process";
@@ -25720,10 +25852,10 @@ function gitInvocation(cwd, args) {
25720
25852
  }
25721
25853
  function git3(cwd, args) {
25722
25854
  const { file, argv, options: options2 } = gitInvocation(cwd, args);
25723
- return new Promise((resolve19, reject) => {
25855
+ return new Promise((resolve20, reject) => {
25724
25856
  execFile7(file, argv, options2, (error, stdout) => {
25725
25857
  if (error) reject(error);
25726
- else resolve19(stdout.toString());
25858
+ else resolve20(stdout.toString());
25727
25859
  });
25728
25860
  });
25729
25861
  }
@@ -25734,6 +25866,16 @@ async function gitOrNull(cwd, args) {
25734
25866
  return null;
25735
25867
  }
25736
25868
  }
25869
+ async function gitResult(cwd, args) {
25870
+ try {
25871
+ return { ok: true, out: (await git3(cwd, args)).trim() };
25872
+ } catch (error) {
25873
+ return {
25874
+ ok: false,
25875
+ error: error instanceof Error ? error.message : String(error)
25876
+ };
25877
+ }
25878
+ }
25737
25879
  function gitSync(cwd, args) {
25738
25880
  const { file, argv, options: options2 } = gitInvocation(cwd, args);
25739
25881
  return execFileSync12(file, argv, {
@@ -25773,6 +25915,7 @@ function gitCommonDir(cwd) {
25773
25915
  }
25774
25916
 
25775
25917
  // src/commands/sessions/daemon/worktree/treeDurability.ts
25918
+ import { existsSync as existsSync58 } from "fs";
25776
25919
  function treeDurability(state) {
25777
25920
  if (state.dirty) return { durable: false, reason: "uncommitted changes" };
25778
25921
  if (!state.push && state.localOnlyCommits)
@@ -25780,35 +25923,27 @@ function treeDurability(state) {
25780
25923
  return { durable: true };
25781
25924
  }
25782
25925
  async function hasLocalOnlyCommits(cwd) {
25783
- const upstream = await gitOrNull(cwd, [
25926
+ const upstream = await gitResult(cwd, [
25784
25927
  "rev-parse",
25785
25928
  "--abbrev-ref",
25786
25929
  "--symbolic-full-name",
25787
25930
  "@{upstream}"
25788
25931
  ]);
25789
- if (upstream) {
25790
- const ahead = await gitOrNull(cwd, [
25932
+ if (upstream.ok && upstream.out) {
25933
+ const ahead = await gitResult(cwd, [
25791
25934
  "rev-list",
25792
25935
  "--count",
25793
25936
  "@{upstream}..HEAD"
25794
25937
  ]);
25795
- return Number(ahead ?? "0") > 0;
25938
+ return !ahead.ok || Number(ahead.out) > 0;
25796
25939
  }
25797
- const remoteBranches = await gitOrNull(cwd, [
25940
+ const remoteBranches = await gitResult(cwd, [
25798
25941
  "branch",
25799
25942
  "-r",
25800
25943
  "--contains",
25801
25944
  "HEAD"
25802
25945
  ]);
25803
- return !remoteBranches;
25804
- }
25805
- async function collectTreeState(cwd, push) {
25806
- const status3 = await gitOrNull(cwd, ["status", "--porcelain"]);
25807
- return {
25808
- dirty: status3 !== null,
25809
- push,
25810
- localOnlyCommits: await hasLocalOnlyCommits(cwd)
25811
- };
25946
+ return !remoteBranches.ok || remoteBranches.out === "";
25812
25947
  }
25813
25948
  function pushOnCommit(cwd) {
25814
25949
  try {
@@ -25818,12 +25953,20 @@ function pushOnCommit(cwd) {
25818
25953
  }
25819
25954
  }
25820
25955
  async function checkDurability(cwd) {
25821
- return treeDurability(await collectTreeState(cwd, pushOnCommit(cwd)));
25956
+ if (!existsSync58(cwd)) return { durable: true };
25957
+ const status3 = await gitResult(cwd, ["status", "--porcelain"]);
25958
+ if (!status3.ok)
25959
+ return { durable: false, reason: `tree state unreadable: ${status3.error}` };
25960
+ return treeDurability({
25961
+ dirty: status3.out !== "",
25962
+ push: pushOnCommit(cwd),
25963
+ localOnlyCommits: await hasLocalOnlyCommits(cwd)
25964
+ });
25822
25965
  }
25823
25966
 
25824
25967
  // src/commands/sessions/daemon/worktree/reapWorktree.ts
25825
25968
  async function reapWorktree(worktreePath, force = false) {
25826
- if (!existsSync58(worktreePath)) {
25969
+ if (!existsSync59(worktreePath)) {
25827
25970
  daemonLog(`worktree ${worktreePath} already gone; skipping reap`);
25828
25971
  return false;
25829
25972
  }
@@ -25870,7 +26013,8 @@ async function deleteBranch(clone, branch2) {
25870
26013
  function dismissSession(sessions, id) {
25871
26014
  const s = sessions.get(id);
25872
26015
  if (!s) return false;
25873
- if (s.status !== "done") s.pty?.kill();
26016
+ const sharedWith = otherTreeHolders(sessions, s);
26017
+ if (s.status !== "done" && s.pty) killPtyTree(s.pty);
25874
26018
  s.activityWatcher?.close();
25875
26019
  s.transcriptWatcher?.close();
25876
26020
  s.gitWatcher?.close();
@@ -25878,36 +26022,39 @@ function dismissSession(sessions, id) {
25878
26022
  if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
25879
26023
  sessions.delete(id);
25880
26024
  daemonLog(`session ${id} dismissed (${s.name})`);
25881
- if (s.worktree) void reapWorktree(s.worktree.path);
26025
+ if (!s.worktree) return true;
26026
+ if (sharedWith.length > 0)
26027
+ daemonLog(
26028
+ `worktree ${s.worktree.path} kept: still held by session(s) ${sharedWith.map((h) => h.id).join(", ")}`
26029
+ );
26030
+ else void reapWorktree(s.worktree.path);
25882
26031
  return true;
25883
26032
  }
25884
- function drainSessions(sessions, onDrained) {
25885
- const names = [...sessions.values()].map((s) => s.name);
25886
- const ids = [...sessions.keys()];
25887
- for (const id of ids) dismissSession(sessions, id);
25888
- onDrained();
25889
- daemonLog(
25890
- ids.length > 0 ? `drained ${ids.length} session(s): ${names.join(", ")}` : "drained 0 sessions"
25891
- );
25892
- return ids.length;
25893
- }
25894
26033
 
25895
- // src/commands/sessions/daemon/killPtyTree.ts
25896
- function killPtyTree(pty2) {
25897
- if (process.platform === "win32") {
25898
- pty2.kill();
25899
- return;
25900
- }
26034
+ // src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
26035
+ var DISABLED = {
26036
+ enabled: false,
26037
+ install: true,
26038
+ copy: [".env", "settings.local.json", ".claude/settings.local.json"]
26039
+ };
26040
+ function worktreeConfigFor(cwd) {
25901
26041
  try {
25902
- process.kill(-pty2.pid, "SIGHUP");
26042
+ return loadConfigFrom(cwd).worktree ?? DISABLED;
25903
26043
  } catch {
25904
- try {
25905
- pty2.kill();
25906
- } catch {
25907
- }
26044
+ return DISABLED;
25908
26045
  }
25909
26046
  }
25910
26047
 
26048
+ // src/commands/sessions/daemon/worktree/closeGateApplies.ts
26049
+ function closeGateApplies(sessions, session) {
26050
+ if (otherTreeHolders(sessions, session).length > 0) return false;
26051
+ if (session.worktree) return true;
26052
+ if (!session.cwd) return false;
26053
+ if (session.status !== "running" && session.status !== "waiting")
26054
+ return false;
26055
+ return worktreeConfigFor(session.cwd).enabled === true;
26056
+ }
26057
+
25911
26058
  // src/commands/sessions/daemon/setStatus.ts
25912
26059
  function setStatus2(session, newStatus) {
25913
26060
  const now = Date.now();
@@ -25919,11 +26066,11 @@ function setStatus2(session, newStatus) {
25919
26066
  }
25920
26067
 
25921
26068
  // src/commands/sessions/daemon/worktree/watchGitState.ts
25922
- import { existsSync as existsSync59, watch } from "fs";
26069
+ import { existsSync as existsSync60, watch } from "fs";
25923
26070
  var DEBOUNCE_MS = 500;
25924
26071
  function watchGitState(cwd, onChange) {
25925
26072
  const common = gitCommonDir(cwd);
25926
- if (!common || !existsSync59(common)) return void 0;
26073
+ if (!common || !existsSync60(common)) return void 0;
25927
26074
  let timer = null;
25928
26075
  try {
25929
26076
  return watch(common, { recursive: true }, () => {
@@ -25938,19 +26085,19 @@ function watchGitState(cwd, onChange) {
25938
26085
  }
25939
26086
  }
25940
26087
 
25941
- // src/commands/sessions/daemon/worktree/resolveDoneDurability.ts
25942
- async function resolveDoneDurability(session, finalize, notify2) {
25943
- const worktree = session.worktree;
25944
- if (!worktree) {
26088
+ // src/commands/sessions/daemon/worktree/resolveCloseDurability.ts
26089
+ async function resolveCloseDurability(session, finalize, notify2) {
26090
+ const tree = treeUnderClose(session);
26091
+ if (!tree) {
25945
26092
  finalize();
25946
26093
  return;
25947
26094
  }
25948
- const durability = await checkDurability(worktree.path);
26095
+ const durability = await checkDurability(tree.path);
25949
26096
  if (!durability.durable) {
25950
- holdStopped(session, durability.reason, finalize, notify2);
26097
+ holdStopped(session, tree, durability.reason, finalize, notify2);
25951
26098
  return;
25952
26099
  }
25953
- await reapWorktree(worktree.path);
26100
+ if (tree.removable) await reapWorktree(tree.path);
25954
26101
  session.worktree = void 0;
25955
26102
  session.undurable = void 0;
25956
26103
  session.closing = void 0;
@@ -25958,16 +26105,18 @@ async function resolveDoneDurability(session, finalize, notify2) {
25958
26105
  session.gitWatcher = void 0;
25959
26106
  finalize();
25960
26107
  }
25961
- function holdStopped(session, reason, finalize, notify2) {
26108
+ function holdStopped(session, tree, reason, finalize, notify2) {
25962
26109
  if (session.undurable?.reason !== reason)
25963
- daemonLog(`session ${session.id} stopped; reap blocked: ${reason}`);
25964
- session.undurable = { reason };
26110
+ daemonLog(
26111
+ `session ${session.id} stopped; ${tree.removable ? "reap" : "close"} blocked in ${tree.path}: ${reason}`
26112
+ );
26113
+ session.undurable = { reason, removesTree: tree.removable };
25965
26114
  session.closing = void 0;
25966
26115
  setStatus2(session, "stopped");
25967
- if (!session.gitWatcher && session.worktree)
25968
- session.gitWatcher = watchGitState(session.worktree.path, () => {
26116
+ if (!session.gitWatcher)
26117
+ session.gitWatcher = watchGitState(tree.path, () => {
25969
26118
  if (session.status !== "stopped") return;
25970
- void resolveDoneDurability(session, finalize, notify2);
26119
+ void resolveCloseDurability(session, finalize, notify2);
25971
26120
  });
25972
26121
  notify2();
25973
26122
  }
@@ -25975,14 +26124,15 @@ function holdStopped(session, reason, finalize, notify2) {
25975
26124
  // src/commands/sessions/daemon/dismissSessionGated.ts
25976
26125
  function dismissSessionGated(sessions, id, notify2, discard = false) {
25977
26126
  const s = sessions.get(id);
25978
- if (!s?.worktree) {
26127
+ const tree = s && treeUnderClose(s);
26128
+ if (!s || !tree || !closeGateApplies(sessions, s)) {
25979
26129
  if (dismissSession(sessions, id)) notify2();
25980
26130
  return;
25981
26131
  }
25982
26132
  const removeCard = () => {
25983
26133
  if (dismissSession(sessions, id)) notify2();
25984
26134
  };
25985
- const discardTree = async () => {
26135
+ const discardWork = async () => {
25986
26136
  if (s.worktree) {
25987
26137
  await reapWorktree(s.worktree.path, true);
25988
26138
  s.worktree = void 0;
@@ -25991,10 +26141,10 @@ function dismissSessionGated(sessions, id, notify2, discard = false) {
25991
26141
  };
25992
26142
  s.closing = true;
25993
26143
  daemonLog(
25994
- `session ${id} closing: ${discard ? "discarding" : "checking durability of"} ${s.worktree.path}`
26144
+ `session ${id} closing: ${discard ? "discarding" : "checking durability of"} ${tree.path}`
25995
26145
  );
25996
26146
  if (s.pty) {
25997
- s.pendingDismiss = discard ? () => void discardTree() : () => void resolveDoneDurability(s, removeCard, notify2);
26147
+ s.pendingDismiss = discard ? () => void discardWork() : () => void resolveCloseDurability(s, removeCard, notify2);
25998
26148
  daemonLog(
25999
26149
  `session ${id} ${discard ? "discard" : "dismiss"} requested: killing process tree first`
26000
26150
  );
@@ -26003,8 +26153,27 @@ function dismissSessionGated(sessions, id, notify2, discard = false) {
26003
26153
  return;
26004
26154
  }
26005
26155
  notify2();
26006
- if (discard) void discardTree();
26007
- else void resolveDoneDurability(s, removeCard, notify2);
26156
+ if (discard) void discardWork();
26157
+ else void resolveCloseDurability(s, removeCard, notify2);
26158
+ }
26159
+
26160
+ // src/commands/sessions/daemon/drainSessions.ts
26161
+ function drainSessions(sessions, notify2) {
26162
+ const drained = [...sessions.values()].map((s) => ({
26163
+ id: s.id,
26164
+ name: s.name
26165
+ }));
26166
+ for (const { id } of drained) dismissSessionGated(sessions, id, notify2);
26167
+ notify2();
26168
+ daemonLog(
26169
+ drained.length > 0 ? `drained ${drained.length} session(s): ${drained.map((s) => s.name).join(", ")}` : "drained 0 sessions"
26170
+ );
26171
+ const gated = [...sessions.values()];
26172
+ if (gated.length > 0)
26173
+ daemonLog(
26174
+ `${gated.length} drained session(s) closing under the durability gate: ${gated.map((s) => s.name).join(", ")} \u2014 any holding unpushed work stay as stopped cards`
26175
+ );
26176
+ return drained.length;
26008
26177
  }
26009
26178
 
26010
26179
  // src/shared/db/recordPhaseActiveMs.ts
@@ -26419,7 +26588,7 @@ function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseFo
26419
26588
  `session ${session.id} closing: checking durability of ${session.worktree.path} before reap`
26420
26589
  );
26421
26590
  notify2();
26422
- void resolveDoneDurability(
26591
+ void resolveCloseDurability(
26423
26592
  session,
26424
26593
  () => finishStatusChange(session, "done", exitCode, deps2),
26425
26594
  notify2
@@ -26432,7 +26601,7 @@ function finishStatusChange(session, status3, exitCode, deps2) {
26432
26601
  daemonLog(`session ${session.id} status: ${session.status} -> ${status3}`);
26433
26602
  void flushPhaseActiveMs(session);
26434
26603
  setStatus2(session, status3);
26435
- if (status3 !== "waiting") session.undurable = void 0;
26604
+ if (status3 !== "stopped") session.undurable = void 0;
26436
26605
  const autoRun = shouldAutoRun(session);
26437
26606
  if (autoRun.run) {
26438
26607
  deps2.reuseForRun(session, autoRun.itemId);
@@ -26464,10 +26633,10 @@ function emitSessionOutput(session, clients, data) {
26464
26633
  }
26465
26634
 
26466
26635
  // src/commands/sessions/daemon/exitReason.ts
26467
- import { existsSync as existsSync60 } from "fs";
26636
+ import { existsSync as existsSync61 } from "fs";
26468
26637
  function exitReason(session, exitCode) {
26469
26638
  const base = `process exited with code ${exitCode}`;
26470
- if (session.cwd && !existsSync60(session.cwd))
26639
+ if (session.cwd && !existsSync61(session.cwd))
26471
26640
  return `${base}: working directory ${session.cwd} no longer exists`;
26472
26641
  return base;
26473
26642
  }
@@ -26485,7 +26654,7 @@ function handleFailedResume(session, exitCode, onStatusChange) {
26485
26654
  }
26486
26655
 
26487
26656
  // src/commands/sessions/daemon/watchActivity.ts
26488
- import { existsSync as existsSync61, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
26657
+ import { existsSync as existsSync62, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
26489
26658
  import { dirname as dirname29 } from "path";
26490
26659
 
26491
26660
  // src/commands/sessions/daemon/applyReviewPause.ts
@@ -26566,7 +26735,7 @@ function watchActivity(session, notify2, onClaudeSessionId) {
26566
26735
  if (timer) clearTimeout(timer);
26567
26736
  timer = setTimeout(read, DEBOUNCE_MS2);
26568
26737
  });
26569
- if (existsSync61(path71)) read();
26738
+ if (existsSync62(path71)) read();
26570
26739
  }
26571
26740
  function refreshActivity(session) {
26572
26741
  if (session.commandType !== "assist" || !session.cwd) return;
@@ -27203,8 +27372,8 @@ function restoreAll(spawn12, sessions) {
27203
27372
  // src/commands/sessions/daemon/worktree/rearmStoppedSessions.ts
27204
27373
  function rearmStoppedSessions(sessions, notify2) {
27205
27374
  for (const s of sessions.values()) {
27206
- if (s.status !== "stopped" || !s.worktree) continue;
27207
- void resolveDoneDurability(
27375
+ if (s.status !== "stopped" || !treeUnderClose(s)) continue;
27376
+ void resolveCloseDurability(
27208
27377
  s,
27209
27378
  () => {
27210
27379
  if (dismissSession(sessions, s.id)) notify2();
@@ -27215,23 +27384,9 @@ function rearmStoppedSessions(sessions, notify2) {
27215
27384
  }
27216
27385
 
27217
27386
  // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
27218
- import { existsSync as existsSync65 } from "fs";
27387
+ import { existsSync as existsSync66 } from "fs";
27219
27388
  import { basename as basename14 } from "path";
27220
27389
 
27221
- // src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
27222
- var DISABLED = {
27223
- enabled: false,
27224
- install: true,
27225
- copy: [".env", "settings.local.json", ".claude/settings.local.json"]
27226
- };
27227
- function worktreeConfigFor(cwd) {
27228
- try {
27229
- return loadConfigFrom(cwd).worktree ?? DISABLED;
27230
- } catch {
27231
- return DISABLED;
27232
- }
27233
- }
27234
-
27235
27390
  // src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
27236
27391
  function detectExistingWorktree(cwd) {
27237
27392
  const repoRoot = findRepoRoot(cwd) ?? cwd;
@@ -27243,17 +27398,17 @@ function detectExistingWorktree(cwd) {
27243
27398
 
27244
27399
  // src/commands/sessions/daemon/worktree/seedWorktree.ts
27245
27400
  import { execFile as execFile8 } from "child_process";
27246
- import { copyFileSync, existsSync as existsSync64, mkdirSync as mkdirSync26 } from "fs";
27401
+ import { copyFileSync, existsSync as existsSync65, mkdirSync as mkdirSync26 } from "fs";
27247
27402
  import { dirname as dirname30, join as join71 } from "path";
27248
27403
 
27249
27404
  // src/commands/sessions/daemon/worktree/resolveInstallCommand.ts
27250
- import { existsSync as existsSync63 } from "fs";
27405
+ import { existsSync as existsSync64 } from "fs";
27251
27406
  import { join as join70 } from "path";
27252
27407
  function detectInstallCommand(repoRoot) {
27253
- if (!existsSync63(join70(repoRoot, "package.json"))) return null;
27254
- if (existsSync63(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
27255
- if (existsSync63(join70(repoRoot, "yarn.lock"))) return "yarn install";
27256
- if (existsSync63(join70(repoRoot, "bun.lockb"))) return "bun install";
27408
+ if (!existsSync64(join70(repoRoot, "package.json"))) return null;
27409
+ if (existsSync64(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
27410
+ if (existsSync64(join70(repoRoot, "yarn.lock"))) return "yarn install";
27411
+ if (existsSync64(join70(repoRoot, "bun.lockb"))) return "bun install";
27257
27412
  return "npm install";
27258
27413
  }
27259
27414
  function resolveInstallCommand(repoRoot, install) {
@@ -27272,7 +27427,7 @@ function seedWorktree(worktreePath, clone, onSeeded = () => {
27272
27427
  function copyConfigFiles(worktreePath, clone, copy) {
27273
27428
  for (const rel of copy) {
27274
27429
  const src = join71(clone, rel);
27275
- if (!existsSync64(src)) continue;
27430
+ if (!existsSync65(src)) continue;
27276
27431
  const dest = join71(worktreePath, rel);
27277
27432
  try {
27278
27433
  mkdirSync26(dirname30(dest), { recursive: true });
@@ -27362,7 +27517,7 @@ async function pruneWorktrees(liveCwds) {
27362
27517
  const orphanBranches = [];
27363
27518
  for (const { path: path71, clone } of readWorktreeRegistry()) {
27364
27519
  if (accounted.has(path71)) continue;
27365
- if (!existsSync65(path71)) {
27520
+ if (!existsSync66(path71)) {
27366
27521
  clonesToPrune.add(clone);
27367
27522
  orphanBranches.push({ clone, branch: basename14(path71) });
27368
27523
  continue;
@@ -27371,7 +27526,7 @@ async function pruneWorktrees(liveCwds) {
27371
27526
  await reapWorktree(path71);
27372
27527
  }
27373
27528
  for (const clone of clonesToPrune) {
27374
- if (!existsSync65(clone)) continue;
27529
+ if (!existsSync66(clone)) continue;
27375
27530
  try {
27376
27531
  await git3(clone, ["worktree", "prune"]);
27377
27532
  daemonLog(`worktree bookkeeping pruned for clone ${clone}`);
@@ -27379,7 +27534,7 @@ async function pruneWorktrees(liveCwds) {
27379
27534
  }
27380
27535
  }
27381
27536
  for (const { clone, branch: branch2 } of orphanBranches)
27382
- if (existsSync65(clone)) await pruneStaleWorktreeBranch(clone, branch2);
27537
+ if (existsSync66(clone)) await pruneStaleWorktreeBranch(clone, branch2);
27383
27538
  }
27384
27539
  async function pruneStaleWorktreeBranch(clone, branch2) {
27385
27540
  if (!listLocalBranches(clone).includes(branch2)) return;
@@ -27544,7 +27699,7 @@ function treeSpawnContext(sessions, spawnWith, notify2, clients, onStatusChange)
27544
27699
  }
27545
27700
 
27546
27701
  // src/commands/sessions/daemon/watchTranscript.ts
27547
- import { existsSync as existsSync66, watch as watch3 } from "fs";
27702
+ import { existsSync as existsSync67, watch as watch3 } from "fs";
27548
27703
 
27549
27704
  // src/commands/sessions/daemon/reconcileTranscriptStatus.ts
27550
27705
  async function resolveTranscriptPath(session) {
@@ -27579,7 +27734,7 @@ function watchTranscript(session, onStatusChange) {
27579
27734
  if (!session.cwd || !session.claudeSessionId) return;
27580
27735
  if (session.watchedTranscriptId === session.claudeSessionId) return;
27581
27736
  const dir = projectDirForCwd(session.cwd);
27582
- if (!existsSync66(dir)) return;
27737
+ if (!existsSync67(dir)) return;
27583
27738
  if (session.transcriptWatcher) {
27584
27739
  session.transcriptWatcher.close();
27585
27740
  session.transcriptWatcher = void 0;
@@ -27632,7 +27787,7 @@ function windowsDaemonHost() {
27632
27787
  var CONNECT_TIMEOUT_MS = 2e3;
27633
27788
  var KEEPALIVE_PROBE_MS = 1e4;
27634
27789
  function connectToWindowsDaemon() {
27635
- return new Promise((resolve19, reject) => {
27790
+ return new Promise((resolve20, reject) => {
27636
27791
  const socket = net2.connect(windowsDaemonPort(), windowsDaemonHost());
27637
27792
  socket.setTimeout(CONNECT_TIMEOUT_MS);
27638
27793
  socket.once("timeout", () => {
@@ -27642,7 +27797,7 @@ function connectToWindowsDaemon() {
27642
27797
  socket.once("connect", () => {
27643
27798
  socket.setTimeout(0);
27644
27799
  socket.setKeepAlive(true, KEEPALIVE_PROBE_MS);
27645
- resolve19(socket);
27800
+ resolve20(socket);
27646
27801
  });
27647
27802
  socket.once("error", reject);
27648
27803
  });
@@ -27720,7 +27875,7 @@ async function waitForWindowsDaemon() {
27720
27875
  );
27721
27876
  }
27722
27877
  function delay2(ms) {
27723
- return new Promise((resolve19) => setTimeout(resolve19, ms));
27878
+ return new Promise((resolve20) => setTimeout(resolve20, ms));
27724
27879
  }
27725
27880
 
27726
27881
  // src/commands/sessions/daemon/defaultConnect.ts
@@ -27730,13 +27885,13 @@ async function defaultConnect() {
27730
27885
  }
27731
27886
 
27732
27887
  // src/commands/sessions/daemon/hasPersistedWindowsSessions.ts
27733
- import { existsSync as existsSync67, readFileSync as readFileSync49 } from "fs";
27888
+ import { existsSync as existsSync68, readFileSync as readFileSync49 } from "fs";
27734
27889
  import { posix } from "path";
27735
27890
  function hasPersistedWindowsSessions() {
27736
27891
  const sessionsFile = windowsSessionsFileFromWsl();
27737
27892
  if (!sessionsFile) return false;
27738
27893
  try {
27739
- if (!existsSync67(sessionsFile)) return false;
27894
+ if (!existsSync68(sessionsFile)) return false;
27740
27895
  const data = JSON.parse(readFileSync49(sessionsFile, "utf8"));
27741
27896
  return Array.isArray(data) && data.length > 0;
27742
27897
  } catch (error) {
@@ -28001,7 +28156,7 @@ async function healWindowsDaemon() {
28001
28156
  daemonLog("windows daemon: auto-heal: stale daemon stopped");
28002
28157
  }
28003
28158
  function runOnWindowsHost(command, timeoutMs) {
28004
- return new Promise((resolve19, reject) => {
28159
+ return new Promise((resolve20, reject) => {
28005
28160
  const child = spawn11("pwsh.exe", ["-Command", command], {
28006
28161
  stdio: ["ignore", "pipe", "pipe"]
28007
28162
  });
@@ -28021,7 +28176,7 @@ function runOnWindowsHost(command, timeoutMs) {
28021
28176
  });
28022
28177
  child.on("exit", (code) => {
28023
28178
  clearTimeout(timer);
28024
- if (code === 0) resolve19();
28179
+ if (code === 0) resolve20();
28025
28180
  else
28026
28181
  reject(
28027
28182
  new Error(
@@ -28324,7 +28479,7 @@ function resumeSession(id, sessionId, cwd, name) {
28324
28479
  }
28325
28480
 
28326
28481
  // src/commands/sessions/daemon/worktree/createWorktree.ts
28327
- import { existsSync as existsSync68 } from "fs";
28482
+ import { existsSync as existsSync69 } from "fs";
28328
28483
  import { basename as basename16, dirname as dirname31 } from "path";
28329
28484
 
28330
28485
  // src/commands/sessions/daemon/worktree/planAllocation.ts
@@ -28349,7 +28504,7 @@ function createWorktree(clone, root, boundTreeRoots2) {
28349
28504
  const path71 = nextWorktreePath(
28350
28505
  clone,
28351
28506
  base,
28352
- (candidate) => registered.has(candidate) || existsSync68(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
28507
+ (candidate) => registered.has(candidate) || existsSync69(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
28353
28508
  );
28354
28509
  const trunk = cloneTrunkBranch(clone);
28355
28510
  gitSync(clone, ["config", "push.default", "upstream"]);
@@ -29076,8 +29231,8 @@ function registerDaemon(program2) {
29076
29231
  "Restart the sessions daemon, resuming previously running claude sessions"
29077
29232
  ).action(restartDaemon);
29078
29233
  cmd.command("drain").description(
29079
- "Remove all sessions from the local daemon for a clean slate (does not affect the Windows daemon)"
29080
- ).action(drainDaemon);
29234
+ "Remove all sessions from the local daemon for a clean slate (does not affect the Windows daemon); prompts before closing live sessions, and a session holding unpushed work is stopped, not removed"
29235
+ ).option("--yes", "Close live sessions without confirming").action((options2) => drainDaemon(options2));
29081
29236
  }
29082
29237
 
29083
29238
  // src/commands/sessions/registerSetStatusCommand.ts
@@ -29355,16 +29510,16 @@ function buildLimitsSegment(rateLimits) {
29355
29510
 
29356
29511
  // src/commands/readGitBranch.ts
29357
29512
  import { readFileSync as readFileSync52, statSync as statSync12 } from "fs";
29358
- import { isAbsolute as isAbsolute2, join as join73, resolve as resolve17 } from "path";
29513
+ import { isAbsolute as isAbsolute3, join as join73, resolve as resolve18 } from "path";
29359
29514
  function resolveGitDir(cwd) {
29360
29515
  const dotGit = join73(cwd, ".git");
29361
- let stat2;
29516
+ let stat3;
29362
29517
  try {
29363
- stat2 = statSync12(dotGit);
29518
+ stat3 = statSync12(dotGit);
29364
29519
  } catch {
29365
29520
  return null;
29366
29521
  }
29367
- if (stat2.isDirectory()) {
29522
+ if (stat3.isDirectory()) {
29368
29523
  return dotGit;
29369
29524
  }
29370
29525
  let contents;
@@ -29378,7 +29533,7 @@ function resolveGitDir(cwd) {
29378
29533
  return null;
29379
29534
  }
29380
29535
  const gitDir = match[1].trim();
29381
- return isAbsolute2(gitDir) ? gitDir : resolve17(cwd, gitDir);
29536
+ return isAbsolute3(gitDir) ? gitDir : resolve18(cwd, gitDir);
29382
29537
  }
29383
29538
  function readGitBranch(cwd) {
29384
29539
  const gitDir = resolveGitDir(cwd);