@staff0rd/assist 0.774.0 → 0.775.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.774.0",
9
+ version: "0.775.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -546,6 +546,13 @@ var LEGACY_SESSIONS_KEYS = [
546
546
  "windowsDaemonPort",
547
547
  "windowsVersionCheck"
548
548
  ];
549
+ var LEGACY_WORKTREE_KEYS = ["watcher"];
550
+ function withoutKeys(value, keys) {
551
+ if (!value || typeof value !== "object") return value;
552
+ const rest = { ...value };
553
+ for (const key of keys) delete rest[key];
554
+ return rest;
555
+ }
549
556
  function stripLegacyConfigKeys(config) {
550
557
  const stripped = { ...config };
551
558
  const news = stripped.news;
@@ -553,12 +560,10 @@ function stripLegacyConfigKeys(config) {
553
560
  const { feeds: _feeds, ...rest } = news;
554
561
  stripped.news = rest;
555
562
  }
556
- const sessions = stripped.sessions;
557
- if (sessions && typeof sessions === "object") {
558
- const rest = { ...sessions };
559
- for (const key of LEGACY_SESSIONS_KEYS) delete rest[key];
560
- stripped.sessions = rest;
561
- }
563
+ if (stripped.sessions)
564
+ stripped.sessions = withoutKeys(stripped.sessions, LEGACY_SESSIONS_KEYS);
565
+ if (stripped.worktree)
566
+ stripped.worktree = withoutKeys(stripped.worktree, LEGACY_WORKTREE_KEYS);
562
567
  return stripped;
563
568
  }
564
569
 
@@ -906,9 +911,11 @@ var assistConfigShape = {
906
911
  })
907
912
  ).default([])
908
913
  }).optional(),
914
+ autoUpdate: z5.strictObject({
915
+ enabled: z5.boolean().default(true)
916
+ }).optional(),
909
917
  worktree: z5.strictObject({
910
918
  enabled: z5.boolean().default(false),
911
- watcher: z5.boolean().default(false),
912
919
  trunk: z5.boolean().default(false),
913
920
  includeDrafts: z5.boolean().default(false),
914
921
  root: z5.string().optional(),
@@ -10936,209 +10943,11 @@ function delay(ms) {
10936
10943
  return new Promise((resolve28) => setTimeout(resolve28, ms));
10937
10944
  }
10938
10945
 
10939
- // src/commands/sessions/daemon/originForCwd.ts
10940
- var cache = /* @__PURE__ */ new Map();
10941
- function originResolutionForCwd(cwd) {
10942
- if (!cwd) return void 0;
10943
- const cached = cache.get(cwd);
10944
- if (cached !== void 0) return { origin: cached, stable: true };
10945
- const resolved = resolveCurrentOrigin(cwd);
10946
- if (resolved.stable) cache.set(cwd, resolved.origin);
10947
- return resolved;
10948
- }
10949
- function originForCwd(cwd) {
10950
- return originResolutionForCwd(cwd)?.origin;
10951
- }
10952
-
10953
- // src/commands/sessions/daemon/repoDirExists.ts
10954
- import { existsSync as existsSync31 } from "fs";
10955
- function repoDirExists(cwd) {
10956
- return existsSync31(cwd);
10957
- }
10958
-
10959
- // src/commands/sessions/daemon/worktree/git.ts
10960
- import { execFile as execFile2, execFileSync as execFileSync2 } from "child_process";
10961
- function isWindowsPath(cwd) {
10962
- return /^[A-Za-z]:[\\/]/.test(cwd);
10963
- }
10964
- function gitInvocation(cwd, args) {
10965
- const windows = isWindowsPath(cwd);
10966
- return {
10967
- file: windows ? "git.exe" : "git",
10968
- argv: windows ? ["-C", cwd, ...args] : args,
10969
- options: {
10970
- encoding: "utf8",
10971
- windowsHide: true,
10972
- ...windows ? {} : { cwd }
10973
- }
10974
- };
10975
- }
10976
- function git(cwd, args) {
10977
- const { file, argv, options: options2 } = gitInvocation(cwd, args);
10978
- return new Promise((resolve28, reject) => {
10979
- execFile2(file, argv, options2, (error, stdout) => {
10980
- if (error) reject(error);
10981
- else resolve28(stdout.toString());
10982
- });
10983
- });
10984
- }
10985
- async function gitOrNull(cwd, args) {
10986
- try {
10987
- return (await git(cwd, args)).trim() || null;
10988
- } catch {
10989
- return null;
10990
- }
10991
- }
10992
- async function gitResult(cwd, args) {
10993
- try {
10994
- return { ok: true, out: (await git(cwd, args)).trim() };
10995
- } catch (error) {
10996
- return {
10997
- ok: false,
10998
- error: error instanceof Error ? error.message : String(error)
10999
- };
11000
- }
11001
- }
11002
- function gitSyncResult(cwd, args) {
11003
- try {
11004
- return { ok: true, out: gitSync(cwd, args).trim() };
11005
- } catch (error) {
11006
- return {
11007
- ok: false,
11008
- error: error instanceof Error ? error.message : String(error)
11009
- };
11010
- }
11011
- }
11012
- function gitSync(cwd, args) {
11013
- const { file, argv, options: options2 } = gitInvocation(cwd, args);
11014
- return execFileSync2(file, argv, {
11015
- ...options2,
11016
- stdio: ["pipe", "pipe", "pipe"]
11017
- }).toString();
11018
- }
11019
- function gitSyncOrNull(cwd, args) {
11020
- try {
11021
- return gitSync(cwd, args).trim() || null;
11022
- } catch {
11023
- return null;
11024
- }
11025
- }
11026
-
11027
- // src/commands/sessions/daemon/worktree/listWorktreePaths.ts
11028
- function listWorktreePaths(cwd) {
11029
- const out = gitSyncOrNull(cwd, ["worktree", "list", "--porcelain"]);
11030
- if (!out) return [];
11031
- return out.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice("worktree ".length).trim());
11032
- }
11033
- function mainWorktree(cwd) {
11034
- return listWorktreePaths(cwd)[0] ?? null;
11035
- }
11036
- function listLocalBranches(cwd) {
11037
- const out = gitSyncOrNull(cwd, [
11038
- "for-each-ref",
11039
- "--format=%(refname:short)",
11040
- "refs/heads"
11041
- ]);
11042
- return out ? out.split("\n").map((line) => line.trim()) : [];
11043
- }
11044
- function gitCommonDir(cwd) {
11045
- return gitSyncOrNull(cwd, [
11046
- "rev-parse",
11047
- "--path-format=absolute",
11048
- "--git-common-dir"
11049
- ]);
11050
- }
11051
-
11052
- // src/shared/loadJson.ts
11053
- import { existsSync as existsSync32, mkdirSync as mkdirSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync21 } from "fs";
11054
- import { homedir as homedir14 } from "os";
11055
- import { join as join36 } from "path";
11056
- function getStoreDir() {
11057
- return process.env.ASSIST_STORE_DIR || join36(homedir14(), ".assist");
11058
- }
11059
- function getStorePath(filename) {
11060
- return join36(getStoreDir(), filename);
11061
- }
11062
- function loadJson(filename) {
11063
- const path106 = getStorePath(filename);
11064
- if (existsSync32(path106)) {
11065
- try {
11066
- return JSON.parse(readFileSync20(path106, "utf8"));
11067
- } catch {
11068
- return {};
11069
- }
11070
- }
11071
- return {};
11072
- }
11073
- function saveJson(filename, data) {
11074
- const dir = getStoreDir();
11075
- if (!existsSync32(dir)) {
11076
- mkdirSync11(dir, { recursive: true });
11077
- }
11078
- writeFileSync21(getStorePath(filename), JSON.stringify(data, null, 2));
11079
- }
11080
-
11081
- // src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
11082
- var REGISTRY_FILE = "worktrees.json";
11083
- function readWorktreeRegistry() {
11084
- const data = loadJson(REGISTRY_FILE);
11085
- return Object.entries(data).filter(([, record]) => !record.reaped).map(([path106, record]) => ({ path: path106, ...record }));
11086
- }
11087
- function worktreeAttributionIncludingReaped(path106) {
11088
- const record = loadJson(REGISTRY_FILE)[path106];
11089
- return record ? { clone: record.clone, origin: record.origin } : void 0;
11090
- }
11091
- function recordWorktree(path106, clone, origin) {
11092
- const data = loadJson(REGISTRY_FILE);
11093
- data[path106] = { clone, origin };
11094
- saveJson(REGISTRY_FILE, data);
11095
- }
11096
- function forgetWorktree(path106) {
11097
- const data = loadJson(REGISTRY_FILE);
11098
- const record = data[path106];
11099
- if (!record || record.reaped) return;
11100
- data[path106] = { ...record, reaped: true };
11101
- saveJson(REGISTRY_FILE, data);
11102
- }
11103
-
11104
- // src/commands/sessions/daemon/repoGroupForCwd.ts
11105
- var cache2 = /* @__PURE__ */ new Map();
11106
- function repoGroupForCwd(cwd) {
11107
- if (!cwd) return void 0;
11108
- if (cache2.has(cwd)) return cache2.get(cwd);
11109
- const { group, stable } = resolveRepoGroup(cwd);
11110
- if (stable) cache2.set(cwd, group);
11111
- return group;
11112
- }
11113
- function resolveRepoGroup(cwd) {
11114
- const clone = mainWorktree(cwd);
11115
- const origin = clone ? originResolutionForCwd(clone) : void 0;
11116
- if (clone && origin)
11117
- return {
11118
- group: hostedGroup(origin.origin, clone),
11119
- stable: origin.stable
11120
- };
11121
- const reaped = worktreeAttributionIncludingReaped(cwd);
11122
- if (!reaped) return { group: void 0, stable: repoDirExists(cwd) };
11123
- const current = currentOriginOfClone(reaped.clone);
11124
- return {
11125
- group: hostedGroup(current?.origin ?? reaped.origin, reaped.clone),
11126
- stable: current?.stable === true
11127
- };
11128
- }
11129
- function currentOriginOfClone(clone) {
11130
- const main = mainWorktree(clone);
11131
- return main ? originResolutionForCwd(main) : void 0;
11132
- }
11133
- function hostedGroup(origin, clone) {
11134
- return { origin, clone };
11135
- }
11136
-
11137
10946
  // src/commands/sessions/nodes/tailscaleStatus.ts
11138
- import { execFile as execFile3 } from "child_process";
11139
- import { existsSync as existsSync33 } from "fs";
10947
+ import { execFile as execFile2 } from "child_process";
10948
+ import { existsSync as existsSync31 } from "fs";
11140
10949
  import { promisify as promisify2 } from "util";
11141
- var execFileAsync2 = promisify2(execFile3);
10950
+ var execFileAsync2 = promisify2(execFile2);
11142
10951
  var STATUS_TIMEOUT_MS = 1e4;
11143
10952
  var INSTALLED_CLI = {
11144
10953
  wsl: "/mnt/c/Program Files/Tailscale/tailscale.exe",
@@ -11148,7 +10957,7 @@ var INSTALLED_CLI = {
11148
10957
  function tailscaleCli() {
11149
10958
  const platform = detectPlatform();
11150
10959
  const installed = INSTALLED_CLI[platform];
11151
- if (installed && existsSync33(installed)) return installed;
10960
+ if (installed && existsSync31(installed)) return installed;
11152
10961
  return platform === "wsl" ? "tailscale.exe" : "tailscale";
11153
10962
  }
11154
10963
  async function tailscaleStatus() {
@@ -11207,9 +11016,9 @@ async function runningStatus(status2) {
11207
11016
  }
11208
11017
 
11209
11018
  // src/commands/sessions/nodes/runTailscale.ts
11210
- import { execFile as execFile4 } from "child_process";
11019
+ import { execFile as execFile3 } from "child_process";
11211
11020
  import { promisify as promisify3 } from "util";
11212
- var execFileAsync3 = promisify3(execFile4);
11021
+ var execFileAsync3 = promisify3(execFile3);
11213
11022
  async function runTailscale(args) {
11214
11023
  const { stdout } = await execFileAsync3(tailscaleCli(), args, {
11215
11024
  encoding: "utf8",
@@ -11241,7 +11050,7 @@ var defaultDeps = {
11241
11050
  httpsPort: tailnetHttpsPort
11242
11051
  };
11243
11052
  async function ensureTailscaleServe(port, deps2 = {}) {
11244
- const { enabled, status: status2, serveStatus, serve, sleep: sleep2, httpsPort } = {
11053
+ const { enabled, status: status2, serveStatus, serve, sleep: sleep3, httpsPort } = {
11245
11054
  ...defaultDeps,
11246
11055
  ...deps2
11247
11056
  };
@@ -11256,7 +11065,7 @@ async function ensureTailscaleServe(port, deps2 = {}) {
11256
11065
  return `serving ${plan2.target} at ${plan2.url}`;
11257
11066
  } catch (error) {
11258
11067
  if (attempt >= SERVE_WRITE_ATTEMPTS) throw error;
11259
- await sleep2(attempt * 500 + Math.random() * 500);
11068
+ await sleep3(attempt * 500 + Math.random() * 500);
11260
11069
  }
11261
11070
  }
11262
11071
  }
@@ -11426,16 +11235,16 @@ function attachSessionSocket(server, ctx) {
11426
11235
 
11427
11236
  // src/shared/createBundleHandler.ts
11428
11237
  import { createHash } from "crypto";
11429
- import { readFileSync as readFileSync21, statSync as statSync5 } from "fs";
11430
- import { dirname as dirname21, join as join37 } from "path";
11238
+ import { readFileSync as readFileSync20, statSync as statSync5 } from "fs";
11239
+ import { dirname as dirname21, join as join36 } from "path";
11431
11240
  import { fileURLToPath as fileURLToPath4 } from "url";
11432
11241
  function createBundleHandler(importMetaUrl, bundlePath, contentType = "application/javascript") {
11433
- const file = join37(dirname21(fileURLToPath4(importMetaUrl)), bundlePath);
11242
+ const file = join36(dirname21(fileURLToPath4(importMetaUrl)), bundlePath);
11434
11243
  let cache6;
11435
11244
  return (req, res) => {
11436
11245
  const mtimeMs = statSync5(file).mtimeMs;
11437
11246
  if (cache6?.mtimeMs !== mtimeMs) {
11438
- const body = readFileSync21(file, "utf8");
11247
+ const body = readFileSync20(file, "utf8");
11439
11248
  const etag = `"${createHash("sha256").update(body).digest("hex").slice(0, 16)}"`;
11440
11249
  cache6 = { body, etag, mtimeMs };
11441
11250
  }
@@ -11475,12 +11284,12 @@ function createFallbackHandler(routes3, htmlHandler2, extra) {
11475
11284
  }
11476
11285
 
11477
11286
  // src/commands/backlog/resolveRepoLocation.ts
11478
- import { existsSync as existsSync34 } from "fs";
11287
+ import { existsSync as existsSync32 } from "fs";
11479
11288
  function resolveRepoLocation(origin, knownCwd, baseDir) {
11480
11289
  if (knownCwd) return { cwd: knownCwd };
11481
11290
  const target = cloneTargetDir(origin, baseDir);
11482
11291
  if (!target) return {};
11483
- if (existsSync34(target) && getCurrentOrigin(target) === origin)
11292
+ if (existsSync32(target) && getCurrentOrigin(target) === origin)
11484
11293
  return { cwd: target };
11485
11294
  return { cloneTarget: target };
11486
11295
  }
@@ -11889,9 +11698,9 @@ function getSessionParam(req) {
11889
11698
  }
11890
11699
 
11891
11700
  // src/commands/sessions/web/execGit.ts
11892
- import { execFile as execFile5 } from "child_process";
11701
+ import { execFile as execFile4 } from "child_process";
11893
11702
  import { promisify as promisify4 } from "util";
11894
- var execFileAsync4 = promisify4(execFile5);
11703
+ var execFileAsync4 = promisify4(execFile4);
11895
11704
  async function execGit(cwd, args, opts = {}) {
11896
11705
  const { maxBuffer, allowFailure } = opts;
11897
11706
  const { file, argv } = /^[A-Za-z]:[\\/]/.test(cwd) ? { file: "git.exe", argv: ["-C", cwd, ...args] } : { file: "git", argv: args };
@@ -12080,17 +11889,17 @@ async function commitPaths(cwd, sha) {
12080
11889
  }
12081
11890
 
12082
11891
  // src/commands/sessions/web/committedPaths.ts
12083
- var cache3 = /* @__PURE__ */ new Map();
11892
+ var cache = /* @__PURE__ */ new Map();
12084
11893
  var inFlight = /* @__PURE__ */ new Map();
12085
11894
  var CACHE_TTL_MS = 1e4;
12086
11895
  async function committedPaths(cwd, sessionId) {
12087
11896
  const key = `${cwd} ${sessionId}`;
12088
- const cached = cache3.get(key);
11897
+ const cached = cache.get(key);
12089
11898
  if (cached && cached.expiresAt > Date.now()) return cached.value;
12090
11899
  const pending = inFlight.get(key);
12091
11900
  if (pending) return pending;
12092
11901
  const lookup = loadCommittedPaths(cwd, sessionId).then((value) => {
12093
- cache3.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
11902
+ cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
12094
11903
  return value;
12095
11904
  }).finally(() => inFlight.delete(key));
12096
11905
  inFlight.set(key, lookup);
@@ -12786,6 +12595,15 @@ var harnessConfigHelp = [
12786
12595
  }
12787
12596
  ];
12788
12597
 
12598
+ // src/commands/sessions/daemon/autoUpdateConfigHelp.ts
12599
+ var autoUpdateConfigHelp = [
12600
+ {
12601
+ key: "autoUpdate.enabled",
12602
+ setter: "assist config set autoUpdate.enabled false -g",
12603
+ note: "when this install is a git clone, the daemon pulls and builds it in the background as origin moves (assist watch wait --pull --build), logging each lap to ~/.assist/watchers/; a branch that cannot fast-forward starts a claude session to reconcile it and the loop resumes once it closes. Set false to stop it (default on)"
12604
+ }
12605
+ ];
12606
+
12789
12607
  // src/commands/branch/branchConfigHelp.ts
12790
12608
  var branchConfigHelp = [
12791
12609
  {
@@ -13279,11 +13097,6 @@ var sessionsConfigHelp = [
13279
13097
  setter: "assist config set worktree.enabled true -g --repo",
13280
13098
  note: "opt in per repo: spill concurrent sessions into adjacent <clone>-N worktrees (default off)"
13281
13099
  },
13282
- {
13283
- key: "worktree.watcher",
13284
- setter: "assist config set worktree.watcher true -g --repo",
13285
- note: "a backlog run also starts one starred console session in the clone running assist watch loop (no claude, relaunched after a daemon restart), so the built version stays current while the run works in its worktree; requires worktree.enabled and an auto-build run entry (default off)"
13286
- },
13287
13100
  {
13288
13101
  key: "worktree.trunk",
13289
13102
  setter: "assist config set worktree.trunk true -g --repo",
@@ -13390,6 +13203,7 @@ var verifyConfigHelp = [
13390
13203
  var configHelpEntries = [
13391
13204
  ...Object.values(rootConfigHelp).flat(),
13392
13205
  ...adviceConfigHelp,
13206
+ ...autoUpdateConfigHelp,
13393
13207
  ...backlogConfigHelp,
13394
13208
  ...backupConfigHelp,
13395
13209
  ...branchConfigHelp,
@@ -13537,26 +13351,26 @@ function handleServerRuns(req, res) {
13537
13351
  }
13538
13352
 
13539
13353
  // src/commands/sessions/web/getReviewSynthesis.ts
13540
- import { execFile as execFile6 } from "child_process";
13541
- import { readFileSync as readFileSync22 } from "fs";
13542
- import { homedir as homedir15 } from "os";
13543
- import { basename as basename11, join as join39 } from "path";
13354
+ import { execFile as execFile5 } from "child_process";
13355
+ import { readFileSync as readFileSync21 } from "fs";
13356
+ import { homedir as homedir14 } from "os";
13357
+ import { basename as basename11, join as join38 } from "path";
13544
13358
  import { promisify as promisify5 } from "util";
13545
13359
 
13546
13360
  // src/commands/sessions/web/findSynthesisForBranch.ts
13547
- import { existsSync as existsSync35, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
13548
- import { basename as basename10, dirname as dirname22, join as join38 } from "path";
13361
+ import { existsSync as existsSync33, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
13362
+ import { basename as basename10, dirname as dirname22, join as join37 } from "path";
13549
13363
  function findSynthesisForBranch(repoReviewsDir, branch2) {
13550
- const branchKeyPath = join38(repoReviewsDir, `${branch2}-`);
13364
+ const branchKeyPath = join37(repoReviewsDir, `${branch2}-`);
13551
13365
  const parent = dirname22(branchKeyPath);
13552
13366
  const branchPrefix = basename10(branchKeyPath);
13553
- if (!existsSync35(parent)) return null;
13554
- const synthesisFiles = readdirSync5(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join38(parent, name, "synthesis.md")).filter((path106) => existsSync35(path106)).map((path106) => ({ path: path106, mtime: statSync6(path106).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
13367
+ if (!existsSync33(parent)) return null;
13368
+ const synthesisFiles = readdirSync5(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join37(parent, name, "synthesis.md")).filter((path106) => existsSync33(path106)).map((path106) => ({ path: path106, mtime: statSync6(path106).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
13555
13369
  return synthesisFiles[0]?.path ?? null;
13556
13370
  }
13557
13371
 
13558
13372
  // src/commands/sessions/web/getReviewSynthesis.ts
13559
- var execFileAsync5 = promisify5(execFile6);
13373
+ var execFileAsync5 = promisify5(execFile5);
13560
13374
  function runGit2(cwd, args) {
13561
13375
  return execFileAsync5("git", args, {
13562
13376
  encoding: "utf8",
@@ -13569,8 +13383,8 @@ async function resolveSynthesisPath(cwd) {
13569
13383
  runGit2(cwd, ["rev-parse", "--show-toplevel"]),
13570
13384
  runGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"])
13571
13385
  ]);
13572
- const repoReviewsDir = join39(
13573
- homedir15(),
13386
+ const repoReviewsDir = join38(
13387
+ homedir14(),
13574
13388
  ".assist",
13575
13389
  "reviews",
13576
13390
  basename11(repoRoot2)
@@ -13586,7 +13400,7 @@ async function getReviewSynthesis(req, res) {
13586
13400
  respondJson(res, 404, { error: "No synthesis found" });
13587
13401
  return;
13588
13402
  }
13589
- respondJson(res, 200, { synthesis: readFileSync22(path106, "utf8") });
13403
+ respondJson(res, 200, { synthesis: readFileSync21(path106, "utf8") });
13590
13404
  } catch {
13591
13405
  respondJson(res, 404, { error: "No synthesis found" });
13592
13406
  }
@@ -13609,7 +13423,7 @@ var EMPTY = {
13609
13423
  defaultBranch: null,
13610
13424
  onDefaultBranch: false
13611
13425
  };
13612
- var cache4 = /* @__PURE__ */ new Map();
13426
+ var cache2 = /* @__PURE__ */ new Map();
13613
13427
  async function currentBranch2(cwd) {
13614
13428
  try {
13615
13429
  const branch2 = (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
@@ -13632,7 +13446,7 @@ async function resolve11(cwd) {
13632
13446
  }
13633
13447
  async function gitBranchInfo(cwd) {
13634
13448
  const now = Date.now();
13635
- const cached = cache4.get(cwd);
13449
+ const cached = cache2.get(cwd);
13636
13450
  if (cached && cached.expires > now) return cached.value;
13637
13451
  let value = EMPTY;
13638
13452
  try {
@@ -13640,7 +13454,7 @@ async function gitBranchInfo(cwd) {
13640
13454
  } catch {
13641
13455
  value = EMPTY;
13642
13456
  }
13643
- cache4.set(cwd, { value, expires: now + CACHE_TTL_MS2 });
13457
+ cache2.set(cwd, { value, expires: now + CACHE_TTL_MS2 });
13644
13458
  return value;
13645
13459
  }
13646
13460
 
@@ -13796,10 +13610,10 @@ function protocolMismatched(msg) {
13796
13610
  return typeof msg.protocol === "number" && msg.protocol !== PROTOCOL_VERSION;
13797
13611
  }
13798
13612
 
13799
- // src/commands/sessions/nodes/queryNodes.ts
13613
+ // src/commands/sessions/daemon/requestDaemonReply.ts
13800
13614
  import { createInterface as createInterface5 } from "readline";
13801
13615
  var QUERY_TIMEOUT_MS = 3e3;
13802
- async function queryNodes() {
13616
+ async function requestDaemonReply(request, replyType) {
13803
13617
  let socket;
13804
13618
  try {
13805
13619
  socket = await connectToDaemon();
@@ -13819,16 +13633,21 @@ async function queryNodes() {
13819
13633
  lines2.on("line", (line) => {
13820
13634
  try {
13821
13635
  const data = JSON.parse(line);
13822
- if (data.type === "nodes") finish2(data);
13636
+ if (data.type === replyType) finish2(data);
13823
13637
  } catch {
13824
13638
  }
13825
13639
  });
13826
13640
  socket.on("error", () => finish2(void 0));
13827
- socket.write(`${JSON.stringify({ type: "nodes" })}
13641
+ socket.write(`${JSON.stringify(request)}
13828
13642
  `);
13829
13643
  });
13830
13644
  }
13831
13645
 
13646
+ // src/commands/sessions/nodes/queryNodes.ts
13647
+ function queryNodes() {
13648
+ return requestDaemonReply({ type: "nodes" }, "nodes");
13649
+ }
13650
+
13832
13651
  // src/commands/sessions/web/health.ts
13833
13652
  async function health(_req, res) {
13834
13653
  const [reachable, nodes] = await Promise.all([
@@ -13859,6 +13678,35 @@ function hotkeyBindings(_req, res) {
13859
13678
  respondJson(res, 200, resolveHotkeys(loadConfig().sessions?.hotkeys));
13860
13679
  }
13861
13680
 
13681
+ // src/shared/loadJson.ts
13682
+ import { existsSync as existsSync34, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync21 } from "fs";
13683
+ import { homedir as homedir15 } from "os";
13684
+ import { join as join39 } from "path";
13685
+ function getStoreDir() {
13686
+ return process.env.ASSIST_STORE_DIR || join39(homedir15(), ".assist");
13687
+ }
13688
+ function getStorePath(filename) {
13689
+ return join39(getStoreDir(), filename);
13690
+ }
13691
+ function loadJson(filename) {
13692
+ const path106 = getStorePath(filename);
13693
+ if (existsSync34(path106)) {
13694
+ try {
13695
+ return JSON.parse(readFileSync22(path106, "utf8"));
13696
+ } catch {
13697
+ return {};
13698
+ }
13699
+ }
13700
+ return {};
13701
+ }
13702
+ function saveJson(filename, data) {
13703
+ const dir = getStoreDir();
13704
+ if (!existsSync34(dir)) {
13705
+ mkdirSync11(dir, { recursive: true });
13706
+ }
13707
+ writeFileSync21(getStorePath(filename), JSON.stringify(data, null, 2));
13708
+ }
13709
+
13862
13710
  // src/commands/sessions/web/jiraSite.ts
13863
13711
  function jiraSite(_req, res) {
13864
13712
  const config = loadJson("jira.json");
@@ -14074,11 +13922,11 @@ function newsNavConfig(_req, res) {
14074
13922
  }
14075
13923
 
14076
13924
  // src/commands/sessions/web/releases/ghJson.ts
14077
- import { execFile as execFile7 } from "child_process";
13925
+ import { execFile as execFile6 } from "child_process";
14078
13926
  import { promisify as promisify6 } from "util";
14079
- var execFileAsync6 = promisify6(execFile7);
13927
+ var execFileAsync6 = promisify6(execFile6);
14080
13928
  var CACHE_TTL_MS4 = 3e4;
14081
- var cache5 = /* @__PURE__ */ new Map();
13929
+ var cache3 = /* @__PURE__ */ new Map();
14082
13930
  function partialGraphqlData(error) {
14083
13931
  const stdout = error.stdout;
14084
13932
  if (typeof stdout !== "string") return null;
@@ -14106,12 +13954,12 @@ async function runGh(cwd, args) {
14106
13954
  }
14107
13955
  function ghJson(cwd, args) {
14108
13956
  const key = [cwd, ...args].join("\0");
14109
- const hit = cache5.get(key);
13957
+ const hit = cache3.get(key);
14110
13958
  if (hit && Date.now() - hit.at < CACHE_TTL_MS4)
14111
13959
  return hit.result;
14112
13960
  const result = runGh(cwd, args);
14113
- cache5.set(key, { at: Date.now(), result });
14114
- result.catch(() => cache5.delete(key));
13961
+ cache3.set(key, { at: Date.now(), result });
13962
+ result.catch(() => cache3.delete(key));
14115
13963
  return result;
14116
13964
  }
14117
13965
 
@@ -14637,7 +14485,7 @@ function assertProjectScope() {
14637
14485
  }
14638
14486
 
14639
14487
  // src/commands/github/issue/assignIssueToSelf.ts
14640
- import { execFileSync as execFileSync3 } from "child_process";
14488
+ import { execFileSync as execFileSync2 } from "child_process";
14641
14489
  function assignIssueToSelf(number, repo) {
14642
14490
  const args = [
14643
14491
  "issue",
@@ -14649,7 +14497,7 @@ function assignIssueToSelf(number, repo) {
14649
14497
  "@me"
14650
14498
  ];
14651
14499
  try {
14652
- execFileSync3("gh", args, {
14500
+ execFileSync2("gh", args, {
14653
14501
  stdio: ["ignore", "ignore", "pipe"],
14654
14502
  encoding: "utf8"
14655
14503
  });
@@ -14819,7 +14667,7 @@ async function nextPickup(req, res) {
14819
14667
  import path27 from "path";
14820
14668
 
14821
14669
  // src/commands/rules/readScopedRules.ts
14822
- import { existsSync as existsSync37, readFileSync as readFileSync23 } from "fs";
14670
+ import { existsSync as existsSync36, readFileSync as readFileSync23 } from "fs";
14823
14671
  import path26 from "path";
14824
14672
 
14825
14673
  // src/commands/rules/rulesSectionRange.ts
@@ -14855,24 +14703,24 @@ function parseRulesSection(content) {
14855
14703
  const lines2 = content.split(/\r?\n/);
14856
14704
  const range = rulesSectionRange(lines2);
14857
14705
  if (!range) return [];
14858
- const rules3 = [];
14706
+ const rules2 = [];
14859
14707
  for (const line of lines2.slice(range.start + 1, range.end)) {
14860
14708
  const match = RULE_BULLET.exec(line);
14861
14709
  if (!match) continue;
14862
14710
  const code = match[1].trim();
14863
14711
  const rest = match[2].trim();
14864
14712
  if (code === "" || rest === "") continue;
14865
- rules3.push({ code, ...splitTitle(rest) });
14713
+ rules2.push({ code, ...splitTitle(rest) });
14866
14714
  }
14867
- return rules3;
14715
+ return rules2;
14868
14716
  }
14869
14717
 
14870
14718
  // src/commands/rules/scopeDirectory.ts
14871
- import { existsSync as existsSync36, statSync as statSync7 } from "fs";
14719
+ import { existsSync as existsSync35, statSync as statSync7 } from "fs";
14872
14720
  import path25 from "path";
14873
14721
  function scopeDirectory(target) {
14874
14722
  const resolved = path25.resolve(target);
14875
- return existsSync36(resolved) && statSync7(resolved).isDirectory() ? resolved : path25.dirname(resolved);
14723
+ return existsSync35(resolved) && statSync7(resolved).isDirectory() ? resolved : path25.dirname(resolved);
14876
14724
  }
14877
14725
 
14878
14726
  // src/commands/rules/readScopedRules.ts
@@ -14883,7 +14731,7 @@ function scopedClaudeFiles(target) {
14883
14731
  let current = startDir;
14884
14732
  while (true) {
14885
14733
  const candidate = path26.join(current, "CLAUDE.md");
14886
- if (existsSync37(candidate)) files.push(candidate);
14734
+ if (existsSync36(candidate)) files.push(candidate);
14887
14735
  if (current === root2 || current === path26.dirname(current)) break;
14888
14736
  current = path26.dirname(current);
14889
14737
  }
@@ -15194,9 +15042,9 @@ async function openInCode(req, res) {
15194
15042
  }
15195
15043
 
15196
15044
  // src/commands/sessions/web/createCachedGhJson.ts
15197
- import { execFile as execFile8 } from "child_process";
15045
+ import { execFile as execFile7 } from "child_process";
15198
15046
  import { promisify as promisify8 } from "util";
15199
- var execFileAsync7 = promisify8(execFile8);
15047
+ var execFileAsync7 = promisify8(execFile7);
15200
15048
  var CACHE_TTL_MS5 = 3e4;
15201
15049
  function createCachedGhJson(args, parse5, fallback, options2 = {}) {
15202
15050
  const { cacheFallback = true } = options2;
@@ -15294,7 +15142,7 @@ function peerTimeoutFor(pathname) {
15294
15142
  }
15295
15143
 
15296
15144
  // src/commands/sessions/web/isQuietPoll.ts
15297
- var POLLED_PATHS = /* @__PURE__ */ new Set(["/api/git-status"]);
15145
+ var POLLED_PATHS = /* @__PURE__ */ new Set(["/api/git-status", "/api/updates"]);
15298
15146
  function isQuietPoll(pathname, status2) {
15299
15147
  return POLLED_PATHS.has(pathname) && status2 >= 200 && status2 < 300;
15300
15148
  }
@@ -15447,9 +15295,9 @@ async function previewImage(req, res) {
15447
15295
  }
15448
15296
 
15449
15297
  // src/commands/sessions/web/prStatus.ts
15450
- import { execFile as execFile9 } from "child_process";
15298
+ import { execFile as execFile8 } from "child_process";
15451
15299
  import { promisify as promisify9 } from "util";
15452
- var execFileAsync8 = promisify9(execFile9);
15300
+ var execFileAsync8 = promisify9(execFile8);
15453
15301
  function toPrSummary(parsed) {
15454
15302
  if (typeof parsed.number !== "number") return null;
15455
15303
  return {
@@ -16058,19 +15906,19 @@ async function revertDiffFile(req, res) {
16058
15906
 
16059
15907
  // src/commands/run/runCommandToCompletion.ts
16060
15908
  import { spawn as spawn4 } from "child_process";
16061
- import { existsSync as existsSync39 } from "fs";
15909
+ import { existsSync as existsSync38 } from "fs";
16062
15910
 
16063
15911
  // src/commands/run/resolveCommand.ts
16064
- import { execFileSync as execFileSync4 } from "child_process";
16065
- import { existsSync as existsSync38 } from "fs";
15912
+ import { execFileSync as execFileSync3 } from "child_process";
15913
+ import { existsSync as existsSync37 } from "fs";
16066
15914
  import { dirname as dirname23, join as join40, resolve as resolve13 } from "path";
16067
15915
  function resolveCommand(command) {
16068
15916
  if (process.platform !== "win32" || command !== "bash") return command;
16069
15917
  try {
16070
- const gitPath = execFileSync4("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
15918
+ const gitPath = execFileSync3("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
16071
15919
  const gitRoot = resolve13(dirname23(gitPath), "..");
16072
15920
  const gitBash = join40(gitRoot, "bin", "bash.exe");
16073
- if (existsSync38(gitBash)) return gitBash;
15921
+ if (existsSync37(gitBash)) return gitBash;
16074
15922
  } catch {
16075
15923
  return command;
16076
15924
  }
@@ -16080,7 +15928,7 @@ function resolveCommand(command) {
16080
15928
  // src/commands/run/runCommandToCompletion.ts
16081
15929
  function runCommandToCompletion(command, args, env, cwd, quiet) {
16082
15930
  return new Promise((resolveResult) => {
16083
- if (cwd && !existsSync39(cwd)) {
15931
+ if (cwd && !existsSync38(cwd)) {
16084
15932
  resolveResult({
16085
15933
  kind: "failed",
16086
15934
  message: `Failed to execute command: cwd ${cwd} does not exist`
@@ -17152,6 +17000,194 @@ function unsetConfig(req, res) {
17152
17000
  });
17153
17001
  }
17154
17002
 
17003
+ // src/commands/watch/resolveUpstream.ts
17004
+ import { execFileSync as execFileSync4 } from "child_process";
17005
+ function runGit3(args, cwd) {
17006
+ return execFileSync4("git", args, {
17007
+ encoding: "utf8",
17008
+ stdio: ["pipe", "pipe", "pipe"],
17009
+ cwd
17010
+ }).trim();
17011
+ }
17012
+ function resolveUpstream(cwd) {
17013
+ try {
17014
+ runGit3(["rev-parse", "--is-inside-work-tree"], cwd);
17015
+ } catch {
17016
+ throw new Error(
17017
+ "not a git repository \u2014 run assist watch wait from inside a repo"
17018
+ );
17019
+ }
17020
+ let branch2;
17021
+ try {
17022
+ branch2 = runGit3(["symbolic-ref", "--quiet", "--short", "HEAD"], cwd);
17023
+ } catch {
17024
+ throw new Error(
17025
+ "HEAD is detached \u2014 check out a branch before waiting on its upstream"
17026
+ );
17027
+ }
17028
+ try {
17029
+ return {
17030
+ branch: branch2,
17031
+ upstream: runGit3(
17032
+ ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
17033
+ cwd
17034
+ )
17035
+ };
17036
+ } catch {
17037
+ throw new Error(
17038
+ `branch "${branch2}" has no upstream \u2014 set one with: git push -u origin ${branch2}`
17039
+ );
17040
+ }
17041
+ }
17042
+
17043
+ // src/commands/watch/changedPaths.ts
17044
+ function changedPaths(from, cwd) {
17045
+ return runGit3(["diff", "--name-only", `${from}..HEAD`], cwd).split("\n").filter((line) => line.length > 0);
17046
+ }
17047
+
17048
+ // src/commands/watch/readBuiltVersion.ts
17049
+ import { join as join41 } from "path";
17050
+ function readBuiltVersion(cwd) {
17051
+ try {
17052
+ const root2 = runGit3(["rev-parse", "--show-toplevel"], cwd);
17053
+ return readPackageJson(join41(root2, "package.json")).version ?? "unknown";
17054
+ } catch {
17055
+ return "unknown";
17056
+ }
17057
+ }
17058
+
17059
+ // src/commands/watch/restartRules.ts
17060
+ var webUiPrefix = "src/commands/sessions/web/ui/";
17061
+ var sessionsPrefix = "src/commands/sessions/";
17062
+ var restartRules = [
17063
+ {
17064
+ target: "webserver",
17065
+ matches: (path106) => path106.startsWith(webUiPrefix),
17066
+ advice: "restart the web server, then hard-reload the browser tab"
17067
+ },
17068
+ {
17069
+ target: "daemon",
17070
+ matches: (path106) => path106.startsWith(sessionsPrefix) && !path106.startsWith(webUiPrefix),
17071
+ advice: "restart the daemon"
17072
+ }
17073
+ ];
17074
+
17075
+ // src/commands/watch/restartTargets.ts
17076
+ function restartTargets(paths) {
17077
+ return restartRules.filter((rule) => paths.some(rule.matches)).map((rule) => rule.target);
17078
+ }
17079
+
17080
+ // src/commands/sessions/web/updates/pendingRestarts.ts
17081
+ function targetsSince(commit2, target, changedSince2) {
17082
+ return commit2 !== void 0 && restartTargets(changedSince2(commit2)).includes(target);
17083
+ }
17084
+ function pendingRestarts(input) {
17085
+ const { daemonVersion, built, changedSince: changedSince2 } = input;
17086
+ const daemon = daemonVersion !== void 0 && (built !== "unknown" && daemonVersion !== built || targetsSince(input.daemonStartCommit, "daemon", changedSince2));
17087
+ const web3 = targetsSince(input.webStartCommit, "webserver", changedSince2);
17088
+ return [
17089
+ ...daemon ? ["daemon"] : [],
17090
+ ...web3 ? ["webserver"] : []
17091
+ ];
17092
+ }
17093
+
17094
+ // src/shared/stripAnsi.ts
17095
+ var ANSI = new RegExp(
17096
+ `${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`,
17097
+ "g"
17098
+ );
17099
+ function stripAnsi(text17) {
17100
+ return text17.replace(ANSI, "");
17101
+ }
17102
+
17103
+ // src/commands/sessions/daemon/worktree/watcherLogPath.ts
17104
+ import { createHash as createHash2 } from "crypto";
17105
+ import { homedir as homedir16 } from "os";
17106
+ import { basename as basename12, join as join42 } from "path";
17107
+
17108
+ // src/commands/sessions/daemon/worktree/canonicalTreePath.ts
17109
+ import { realpathSync as realpathSync2 } from "fs";
17110
+ import { resolve as resolve14 } from "path";
17111
+ function canonicalTreePath(path106) {
17112
+ try {
17113
+ return realpathSync2(path106);
17114
+ } catch {
17115
+ return resolve14(path106);
17116
+ }
17117
+ }
17118
+
17119
+ // src/commands/sessions/daemon/worktree/watcherLogPath.ts
17120
+ function watcherLogPath(cwd) {
17121
+ const clone = canonicalTreePath(cwd);
17122
+ const key = createHash2("sha1").update(clone).digest("hex").slice(0, 8);
17123
+ const dir = process.env.ASSIST_WATCHER_LOG_DIR ?? join42(homedir16(), ".assist", "watchers");
17124
+ return join42(dir, `${basename12(clone) || "root"}-${key}.log`);
17125
+ }
17126
+
17127
+ // src/commands/sessions/web/updates/readUpdateHistory.ts
17128
+ var HISTORY_LINES = 200;
17129
+ function readUpdateHistory(installDir) {
17130
+ return readLogTail(watcherLogPath(installDir), HISTORY_LINES * 2).flatMap((line) => line.split("\r")).map((line) => stripAnsi(line).trimEnd()).filter((line) => line.trim().length > 0).slice(-HISTORY_LINES);
17131
+ }
17132
+
17133
+ // src/commands/watch/headCommit.ts
17134
+ function headCommit3(cwd) {
17135
+ try {
17136
+ return runGit3(["rev-parse", "HEAD"], cwd);
17137
+ } catch {
17138
+ return void 0;
17139
+ }
17140
+ }
17141
+
17142
+ // src/commands/sessions/web/updates/webStartCommit.ts
17143
+ var captured;
17144
+ var webStartCommit = {
17145
+ capture: () => {
17146
+ captured = headCommit3(getInstallDir());
17147
+ },
17148
+ value: () => captured
17149
+ };
17150
+
17151
+ // src/commands/sessions/web/updates/updates.ts
17152
+ function changedSince(installDir) {
17153
+ return (commit2) => {
17154
+ try {
17155
+ return changedPaths(commit2, installDir);
17156
+ } catch {
17157
+ return [];
17158
+ }
17159
+ };
17160
+ }
17161
+ async function updates(_req, res) {
17162
+ const installDir = getInstallDir();
17163
+ const reply2 = await requestDaemonReply(
17164
+ { type: "auto-update" },
17165
+ "auto-update"
17166
+ );
17167
+ const built = readBuiltVersion(installDir);
17168
+ const status2 = {
17169
+ nodeName: resolveNodeName(),
17170
+ installDir,
17171
+ running: reply2?.version ?? ASSIST_VERSION,
17172
+ built,
17173
+ restart: pendingRestarts({
17174
+ daemonVersion: reply2?.version,
17175
+ daemonStartCommit: reply2?.startCommit,
17176
+ webStartCommit: webStartCommit.value(),
17177
+ built,
17178
+ changedSince: changedSince(installDir)
17179
+ }),
17180
+ daemonReachable: reply2 !== void 0,
17181
+ loop: reply2?.loop ?? {
17182
+ phase: "off",
17183
+ since: (/* @__PURE__ */ new Date()).toISOString(),
17184
+ reason: "daemon not reachable"
17185
+ },
17186
+ history: readUpdateHistory(installDir)
17187
+ };
17188
+ respondJson(res, 200, status2);
17189
+ }
17190
+
17155
17191
  // src/commands/sessions/web/readRequestBuffer.ts
17156
17192
  async function readRequestBuffer(req, limit) {
17157
17193
  const chunks = [];
@@ -17173,7 +17209,7 @@ async function readRequestBuffer(req, limit) {
17173
17209
 
17174
17210
  // src/commands/sessions/web/stageAttachment.ts
17175
17211
  import { mkdir as mkdir4, mkdtemp, writeFile as writeFile2 } from "fs/promises";
17176
- import { extname as extname2, join as join41 } from "path";
17212
+ import { extname as extname2, join as join43 } from "path";
17177
17213
  function extensionFromMime(contentType) {
17178
17214
  const mime = contentType.split(";")[0].trim().toLowerCase();
17179
17215
  const known = attachmentMimeTypes[mime];
@@ -17201,9 +17237,9 @@ function safeBaseName(name) {
17201
17237
  async function stageAttachment(name, contentType, body) {
17202
17238
  const ext = pickExtension(name, contentType);
17203
17239
  await mkdir4(stagedAttachmentsDir, { recursive: true });
17204
- const dir = await mkdtemp(join41(stagedAttachmentsDir, "upload-"));
17240
+ const dir = await mkdtemp(join43(stagedAttachmentsDir, "upload-"));
17205
17241
  const alt = safeBaseName(name);
17206
- const filePath = join41(dir, `${alt}.${ext}`);
17242
+ const filePath = join43(dir, `${alt}.${ext}`);
17207
17243
  await writeFile2(filePath, body);
17208
17244
  return { dir, filePath, alt };
17209
17245
  }
@@ -17251,17 +17287,17 @@ async function uploadPrImage(req, res) {
17251
17287
  import { readFile as readFile3, stat as stat4, writeFile as writeFile3 } from "fs/promises";
17252
17288
 
17253
17289
  // src/commands/sessions/web/formatWithOxfmt.ts
17254
- import { execFile as execFile10 } from "child_process";
17255
- import { existsSync as existsSync40 } from "fs";
17256
- import { dirname as dirname24, join as join42 } from "path";
17290
+ import { execFile as execFile9 } from "child_process";
17291
+ import { existsSync as existsSync39 } from "fs";
17292
+ import { dirname as dirname24, join as join44 } from "path";
17257
17293
  import { promisify as promisify10 } from "util";
17258
- var execFileAsync9 = promisify10(execFile10);
17294
+ var execFileAsync9 = promisify10(execFile9);
17259
17295
  var TIMEOUT_MS = 15e3;
17260
17296
  function findOxfmtScript(root2) {
17261
17297
  let dir = root2;
17262
17298
  for (; ; ) {
17263
- const candidate = join42(dir, "node_modules", "oxfmt", "bin", "oxfmt");
17264
- if (existsSync40(candidate)) return candidate;
17299
+ const candidate = join44(dir, "node_modules", "oxfmt", "bin", "oxfmt");
17300
+ if (existsSync39(candidate)) return candidate;
17265
17301
  const parent = dirname24(dir);
17266
17302
  if (parent === dir) return void 0;
17267
17303
  dir = parent;
@@ -17331,7 +17367,7 @@ async function writeFileContent(req, res) {
17331
17367
  }
17332
17368
 
17333
17369
  // src/commands/sessions/web/createCssHandler.ts
17334
- import { createHash as createHash2 } from "crypto";
17370
+ import { createHash as createHash3 } from "crypto";
17335
17371
  import { readFileSync as readFileSync24 } from "fs";
17336
17372
  import { createRequire } from "module";
17337
17373
  var require2 = createRequire(import.meta.url);
@@ -17341,7 +17377,7 @@ function createCssHandler(packageEntry) {
17341
17377
  if (!cache6) {
17342
17378
  const resolved = require2.resolve(packageEntry);
17343
17379
  const body = readFileSync24(resolved, "utf8");
17344
- const etag = `"${createHash2("sha256").update(body).digest("hex").slice(0, 16)}"`;
17380
+ const etag = `"${createHash3("sha256").update(body).digest("hex").slice(0, 16)}"`;
17345
17381
  cache6 = { body, etag };
17346
17382
  }
17347
17383
  const headers = { ETag: cache6.etag, "Cache-Control": "no-cache" };
@@ -17424,7 +17460,8 @@ var routes2 = {
17424
17460
  "GET /api/config": getConfig,
17425
17461
  "POST /api/config/set": setConfig,
17426
17462
  "POST /api/config/unset": unsetConfig,
17427
- "GET /api/review/synthesis": getReviewSynthesis
17463
+ "GET /api/review/synthesis": getReviewSynthesis,
17464
+ "GET /api/updates": updates
17428
17465
  };
17429
17466
  var handleRequest = createFallbackHandler(
17430
17467
  routes2,
@@ -17693,11 +17730,183 @@ function scheduleReconnect() {
17693
17730
  setTimeout(() => void connect2(), RECONNECT_MS).unref();
17694
17731
  }
17695
17732
 
17696
- // src/commands/sessions/web.ts
17733
+ // src/commands/sessions/daemon/originForCwd.ts
17734
+ var cache4 = /* @__PURE__ */ new Map();
17735
+ function originResolutionForCwd(cwd) {
17736
+ if (!cwd) return void 0;
17737
+ const cached = cache4.get(cwd);
17738
+ if (cached !== void 0) return { origin: cached, stable: true };
17739
+ const resolved = resolveCurrentOrigin(cwd);
17740
+ if (resolved.stable) cache4.set(cwd, resolved.origin);
17741
+ return resolved;
17742
+ }
17743
+ function originForCwd(cwd) {
17744
+ return originResolutionForCwd(cwd)?.origin;
17745
+ }
17746
+
17747
+ // src/commands/sessions/daemon/repoDirExists.ts
17748
+ import { existsSync as existsSync40 } from "fs";
17749
+ function repoDirExists(cwd) {
17750
+ return existsSync40(cwd);
17751
+ }
17752
+
17753
+ // src/commands/sessions/daemon/worktree/git.ts
17754
+ import { execFile as execFile10, execFileSync as execFileSync5 } from "child_process";
17755
+ function isWindowsPath(cwd) {
17756
+ return /^[A-Za-z]:[\\/]/.test(cwd);
17757
+ }
17758
+ function gitInvocation(cwd, args) {
17759
+ const windows = isWindowsPath(cwd);
17760
+ return {
17761
+ file: windows ? "git.exe" : "git",
17762
+ argv: windows ? ["-C", cwd, ...args] : args,
17763
+ options: {
17764
+ encoding: "utf8",
17765
+ windowsHide: true,
17766
+ ...windows ? {} : { cwd }
17767
+ }
17768
+ };
17769
+ }
17770
+ function git(cwd, args) {
17771
+ const { file, argv, options: options2 } = gitInvocation(cwd, args);
17772
+ return new Promise((resolve28, reject) => {
17773
+ execFile10(file, argv, options2, (error, stdout) => {
17774
+ if (error) reject(error);
17775
+ else resolve28(stdout.toString());
17776
+ });
17777
+ });
17778
+ }
17779
+ async function gitOrNull(cwd, args) {
17780
+ try {
17781
+ return (await git(cwd, args)).trim() || null;
17782
+ } catch {
17783
+ return null;
17784
+ }
17785
+ }
17786
+ async function gitResult(cwd, args) {
17787
+ try {
17788
+ return { ok: true, out: (await git(cwd, args)).trim() };
17789
+ } catch (error) {
17790
+ return {
17791
+ ok: false,
17792
+ error: error instanceof Error ? error.message : String(error)
17793
+ };
17794
+ }
17795
+ }
17796
+ function gitSyncResult(cwd, args) {
17797
+ try {
17798
+ return { ok: true, out: gitSync(cwd, args).trim() };
17799
+ } catch (error) {
17800
+ return {
17801
+ ok: false,
17802
+ error: error instanceof Error ? error.message : String(error)
17803
+ };
17804
+ }
17805
+ }
17806
+ function gitSync(cwd, args) {
17807
+ const { file, argv, options: options2 } = gitInvocation(cwd, args);
17808
+ return execFileSync5(file, argv, {
17809
+ ...options2,
17810
+ stdio: ["pipe", "pipe", "pipe"]
17811
+ }).toString();
17812
+ }
17813
+ function gitSyncOrNull(cwd, args) {
17814
+ try {
17815
+ return gitSync(cwd, args).trim() || null;
17816
+ } catch {
17817
+ return null;
17818
+ }
17819
+ }
17820
+
17821
+ // src/commands/sessions/daemon/worktree/listWorktreePaths.ts
17822
+ function listWorktreePaths(cwd) {
17823
+ const out = gitSyncOrNull(cwd, ["worktree", "list", "--porcelain"]);
17824
+ if (!out) return [];
17825
+ return out.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice("worktree ".length).trim());
17826
+ }
17827
+ function mainWorktree(cwd) {
17828
+ return listWorktreePaths(cwd)[0] ?? null;
17829
+ }
17830
+ function listLocalBranches(cwd) {
17831
+ const out = gitSyncOrNull(cwd, [
17832
+ "for-each-ref",
17833
+ "--format=%(refname:short)",
17834
+ "refs/heads"
17835
+ ]);
17836
+ return out ? out.split("\n").map((line) => line.trim()) : [];
17837
+ }
17838
+ function gitCommonDir(cwd) {
17839
+ return gitSyncOrNull(cwd, [
17840
+ "rev-parse",
17841
+ "--path-format=absolute",
17842
+ "--git-common-dir"
17843
+ ]);
17844
+ }
17845
+
17846
+ // src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
17847
+ var REGISTRY_FILE = "worktrees.json";
17848
+ function readWorktreeRegistry() {
17849
+ const data = loadJson(REGISTRY_FILE);
17850
+ return Object.entries(data).filter(([, record]) => !record.reaped).map(([path106, record]) => ({ path: path106, ...record }));
17851
+ }
17852
+ function worktreeAttributionIncludingReaped(path106) {
17853
+ const record = loadJson(REGISTRY_FILE)[path106];
17854
+ return record ? { clone: record.clone, origin: record.origin } : void 0;
17855
+ }
17856
+ function recordWorktree(path106, clone, origin) {
17857
+ const data = loadJson(REGISTRY_FILE);
17858
+ data[path106] = { clone, origin };
17859
+ saveJson(REGISTRY_FILE, data);
17860
+ }
17861
+ function forgetWorktree(path106) {
17862
+ const data = loadJson(REGISTRY_FILE);
17863
+ const record = data[path106];
17864
+ if (!record || record.reaped) return;
17865
+ data[path106] = { ...record, reaped: true };
17866
+ saveJson(REGISTRY_FILE, data);
17867
+ }
17868
+
17869
+ // src/commands/sessions/daemon/repoGroupForCwd.ts
17870
+ var cache5 = /* @__PURE__ */ new Map();
17871
+ function repoGroupForCwd(cwd) {
17872
+ if (!cwd) return void 0;
17873
+ if (cache5.has(cwd)) return cache5.get(cwd);
17874
+ const { group, stable } = resolveRepoGroup(cwd);
17875
+ if (stable) cache5.set(cwd, group);
17876
+ return group;
17877
+ }
17878
+ function resolveRepoGroup(cwd) {
17879
+ const clone = mainWorktree(cwd);
17880
+ const origin = clone ? originResolutionForCwd(clone) : void 0;
17881
+ if (clone && origin)
17882
+ return {
17883
+ group: hostedGroup(origin.origin, clone),
17884
+ stable: origin.stable
17885
+ };
17886
+ const reaped = worktreeAttributionIncludingReaped(cwd);
17887
+ if (!reaped) return { group: void 0, stable: repoDirExists(cwd) };
17888
+ const current = currentOriginOfClone(reaped.clone);
17889
+ return {
17890
+ group: hostedGroup(current?.origin ?? reaped.origin, reaped.clone),
17891
+ stable: current?.stable === true
17892
+ };
17893
+ }
17894
+ function currentOriginOfClone(clone) {
17895
+ const main = mainWorktree(clone);
17896
+ return main ? originResolutionForCwd(main) : void 0;
17897
+ }
17898
+ function hostedGroup(origin, clone) {
17899
+ return { origin, clone };
17900
+ }
17901
+
17902
+ // src/commands/sessions/repoEntryCwd.ts
17697
17903
  function repoEntryCwd(serverCwd) {
17698
17904
  return repoGroupForCwd(serverCwd)?.clone ?? serverCwd;
17699
17905
  }
17906
+
17907
+ // src/commands/sessions/web.ts
17700
17908
  async function web(options2) {
17909
+ webStartCommit.capture();
17701
17910
  const port = Number.parseInt(options2.port, 10);
17702
17911
  const server = startWebServer(
17703
17912
  "Assist",
@@ -18103,7 +18312,7 @@ function registerBranch(program2) {
18103
18312
  }
18104
18313
 
18105
18314
  // src/commands/cliHook.ts
18106
- import { basename as basename12 } from "path";
18315
+ import { basename as basename13 } from "path";
18107
18316
 
18108
18317
  // src/shared/splitCompound.ts
18109
18318
  import { parse } from "shell-quote";
@@ -18679,7 +18888,7 @@ function findTruncatedReadDenyRaw(rawCommand) {
18679
18888
  }
18680
18889
 
18681
18890
  // src/shared/isApprovedRead.ts
18682
- import { resolve as resolve15, sep as sep2 } from "path";
18891
+ import { resolve as resolve16, sep as sep2 } from "path";
18683
18892
 
18684
18893
  // src/shared/appliesFixStructure.ts
18685
18894
  var APPLY_RE = /^assist\s+github\s+issue\s+fix-structure\b.*\s--apply\b/;
@@ -18733,7 +18942,7 @@ function extractGraphqlQuery(args) {
18733
18942
 
18734
18943
  // src/shared/loadCliReads.ts
18735
18944
  import { existsSync as existsSync41, readFileSync as readFileSync26, writeFileSync as writeFileSync22 } from "fs";
18736
- import { dirname as dirname25, resolve as resolve14 } from "path";
18945
+ import { dirname as dirname25, resolve as resolve15 } from "path";
18737
18946
  import { fileURLToPath as fileURLToPath5 } from "url";
18738
18947
  var __filename3 = fileURLToPath5(import.meta.url);
18739
18948
  var __dirname4 = dirname25(__filename3);
@@ -18748,13 +18957,13 @@ var cachedReads;
18748
18957
  var cachedWrites;
18749
18958
  function getCliReadsLines() {
18750
18959
  if (!cachedReads) {
18751
- cachedReads = readLines(resolve14(packageRoot(), "allowed.cli-reads"));
18960
+ cachedReads = readLines(resolve15(packageRoot(), "allowed.cli-reads"));
18752
18961
  }
18753
18962
  return cachedReads;
18754
18963
  }
18755
18964
  function getCliWritesLines() {
18756
18965
  if (!cachedWrites) {
18757
- cachedWrites = readLines(resolve14(packageRoot(), "allowed.cli-writes"));
18966
+ cachedWrites = readLines(resolve15(packageRoot(), "allowed.cli-writes"));
18758
18967
  }
18759
18968
  return cachedWrites;
18760
18969
  }
@@ -18763,7 +18972,7 @@ function loadCliReads() {
18763
18972
  }
18764
18973
  function saveCliReads(commands) {
18765
18974
  writeFileSync22(
18766
- resolve14(packageRoot(), "allowed.cli-reads"),
18975
+ resolve15(packageRoot(), "allowed.cli-reads"),
18767
18976
  `${commands.join("\n")}
18768
18977
  `
18769
18978
  );
@@ -18789,13 +18998,13 @@ function findCliWrite(command) {
18789
18998
 
18790
18999
  // src/shared/readSettingsPerms.ts
18791
19000
  import { existsSync as existsSync42, readFileSync as readFileSync27 } from "fs";
18792
- import { homedir as homedir16 } from "os";
18793
- import { join as join43 } from "path";
19001
+ import { homedir as homedir17 } from "os";
19002
+ import { join as join45 } from "path";
18794
19003
  function readSettingsPerms(key) {
18795
19004
  const paths = [
18796
- join43(homedir16(), ".claude", "settings.json"),
18797
- join43(process.cwd(), ".claude", "settings.json"),
18798
- join43(process.cwd(), ".claude", "settings.local.json")
19005
+ join45(homedir17(), ".claude", "settings.json"),
19006
+ join45(process.cwd(), ".claude", "settings.json"),
19007
+ join45(process.cwd(), ".claude", "settings.local.json")
18799
19008
  ];
18800
19009
  const entries = [];
18801
19010
  for (const p of paths) {
@@ -18901,19 +19110,19 @@ function isCdToCwd(command) {
18901
19110
  const parts = command.split(/\s+/);
18902
19111
  if (parts[0] !== "cd" || parts.length > 2) return false;
18903
19112
  if (parts.length === 1) return false;
18904
- const resolved = resolve15(normalizeMsysPath(parts[1]));
18905
- return resolved === resolve15(process.cwd());
19113
+ const resolved = resolve16(normalizeMsysPath(parts[1]));
19114
+ return resolved === resolve16(process.cwd());
18906
19115
  }
18907
19116
  function isCdToReadAllowedDir(command) {
18908
19117
  const parts = command.split(/\s+/);
18909
19118
  if (parts[0] !== "cd" || parts.length !== 2) return void 0;
18910
- const target = resolve15(normalizeMsysPath(parts[1]));
19119
+ const target = resolve16(normalizeMsysPath(parts[1]));
18911
19120
  for (const entry of readSettingsPerms("allow")) {
18912
19121
  const m = entry.match(READ_RE);
18913
19122
  if (!m) continue;
18914
19123
  const base = globBaseDir(m[1]);
18915
19124
  if (!base) continue;
18916
- const resolved = resolve15(normalizeMsysPath(base));
19125
+ const resolved = resolve16(normalizeMsysPath(base));
18917
19126
  if (target === resolved || target.startsWith(resolved + sep2)) {
18918
19127
  return `cd to Read-allowed directory: ${entry}`;
18919
19128
  }
@@ -19018,12 +19227,12 @@ function decideCommand(toolName, rawCommand) {
19018
19227
 
19019
19228
  // src/commands/cliHook/logDeniedToolCall.ts
19020
19229
  import { mkdirSync as mkdirSync12 } from "fs";
19021
- import { homedir as homedir17 } from "os";
19022
- import { join as join44 } from "path";
19230
+ import { homedir as homedir18 } from "os";
19231
+ import { join as join46 } from "path";
19023
19232
  import Database from "better-sqlite3";
19024
19233
  var _db;
19025
19234
  function getDbDir() {
19026
- return join44(homedir17(), ".assist");
19235
+ return join46(homedir18(), ".assist");
19027
19236
  }
19028
19237
  function initSchema(db) {
19029
19238
  db.exec(`
@@ -19042,7 +19251,7 @@ function openPromptsDb(dir) {
19042
19251
  if (_db) return _db;
19043
19252
  const dbDir = dir ?? getDbDir();
19044
19253
  mkdirSync12(dbDir, { recursive: true });
19045
- const db = new Database(join44(dbDir, "assist.db"));
19254
+ const db = new Database(join46(dbDir, "assist.db"));
19046
19255
  db.pragma("journal_mode = WAL");
19047
19256
  initSchema(db);
19048
19257
  _db = db;
@@ -19113,7 +19322,7 @@ async function cliHook() {
19113
19322
  logDeniedToolCall({
19114
19323
  tool: input.toolName,
19115
19324
  command: input.command,
19116
- repo: basename12(process.cwd()),
19325
+ repo: basename13(process.cwd()),
19117
19326
  sessionId: process.env.CLAUDE_SESSION_ID,
19118
19327
  denyReason: decision.permissionDecisionReason
19119
19328
  });
@@ -19162,8 +19371,8 @@ ${reasons.join("\n")}`);
19162
19371
 
19163
19372
  // src/commands/permitCliReads.ts
19164
19373
  import { existsSync as existsSync43, mkdirSync as mkdirSync13, readFileSync as readFileSync28, writeFileSync as writeFileSync23 } from "fs";
19165
- import { homedir as homedir18 } from "os";
19166
- import { join as join45 } from "path";
19374
+ import { homedir as homedir19 } from "os";
19375
+ import { join as join47 } from "path";
19167
19376
 
19168
19377
  // src/commands/permitCliReads/assertCliExists.ts
19169
19378
  function assertCliExists(cli) {
@@ -19426,7 +19635,7 @@ function updateSettings(cli, commands) {
19426
19635
  // src/commands/permitCliReads.ts
19427
19636
  function logPath(cli) {
19428
19637
  const safeName = cli.replace(/\s+/g, "-");
19429
- return join45(homedir18(), ".assist", `cli-discover-${safeName}.log`);
19638
+ return join47(homedir19(), ".assist", `cli-discover-${safeName}.log`);
19430
19639
  }
19431
19640
  function readCache(cli) {
19432
19641
  const path106 = logPath(cli);
@@ -19434,7 +19643,7 @@ function readCache(cli) {
19434
19643
  return readFileSync28(path106, "utf8");
19435
19644
  }
19436
19645
  function writeCache(cli, output) {
19437
- const dir = join45(homedir18(), ".assist");
19646
+ const dir = join47(homedir19(), ".assist");
19438
19647
  mkdirSync13(dir, { recursive: true });
19439
19648
  writeFileSync23(logPath(cli), output);
19440
19649
  }
@@ -19598,18 +19807,18 @@ function isHashCommentFile(filePath) {
19598
19807
  }
19599
19808
 
19600
19809
  // src/commands/codeComment/getRestrictedDir.ts
19601
- import { homedir as homedir19 } from "os";
19602
- import { join as join46 } from "path";
19810
+ import { homedir as homedir20 } from "os";
19811
+ import { join as join48 } from "path";
19603
19812
  function getRestrictedDir() {
19604
- return join46(homedir19(), ".assist", "restricted");
19813
+ return join48(homedir20(), ".assist", "restricted");
19605
19814
  }
19606
19815
  function getPinStatePath(pin2) {
19607
- return join46(getRestrictedDir(), `code-comment-${pin2}.json`);
19816
+ return join48(getRestrictedDir(), `code-comment-${pin2}.json`);
19608
19817
  }
19609
19818
 
19610
19819
  // src/commands/codeComment/sweepRestrictedDir.ts
19611
19820
  import { readdirSync as readdirSync6, statSync as statSync8, unlinkSync as unlinkSync7 } from "fs";
19612
- import { join as join47 } from "path";
19821
+ import { join as join49 } from "path";
19613
19822
  var STALE_AFTER_MS = 30 * 60 * 1e3;
19614
19823
  function sweepRestrictedDir(dir = getRestrictedDir()) {
19615
19824
  let entries;
@@ -19620,7 +19829,7 @@ function sweepRestrictedDir(dir = getRestrictedDir()) {
19620
19829
  }
19621
19830
  const cutoff = Date.now() - STALE_AFTER_MS;
19622
19831
  for (const entry of entries) {
19623
- const path106 = join47(dir, entry);
19832
+ const path106 = join49(dir, entry);
19624
19833
  try {
19625
19834
  if (statSync8(path106).mtimeMs < cutoff) unlinkSync7(path106);
19626
19835
  } catch {
@@ -21274,18 +21483,18 @@ import chalk137 from "chalk";
21274
21483
 
21275
21484
  // src/commands/criteriaExtension/criteriaExtensionDir.ts
21276
21485
  import { existsSync as existsSync46 } from "fs";
21277
- import { dirname as dirname26, join as join48 } from "path";
21486
+ import { dirname as dirname26, join as join50 } from "path";
21278
21487
  import { fileURLToPath as fileURLToPath6 } from "url";
21279
21488
  var moduleDir = dirname26(fileURLToPath6(import.meta.url));
21280
21489
  function criteriaExtensionDir() {
21281
- const bundled = join48(
21490
+ const bundled = join50(
21282
21491
  moduleDir,
21283
21492
  "commands",
21284
21493
  "criteriaExtension",
21285
21494
  "criteria-extension"
21286
21495
  );
21287
21496
  if (existsSync46(bundled)) return bundled;
21288
- return join48(moduleDir, "..", "..", "..", "criteria-extension");
21497
+ return join50(moduleDir, "..", "..", "..", "criteria-extension");
21289
21498
  }
21290
21499
 
21291
21500
  // src/commands/criteriaExtension/criteriaExtension.ts
@@ -21313,24 +21522,24 @@ async function criteriaExtension() {
21313
21522
 
21314
21523
  // src/commands/criteriaExtension/printCriteriaExtensionUrl.ts
21315
21524
  import { readFile as readFile4 } from "fs/promises";
21316
- import { join as join49 } from "path";
21525
+ import { join as join51 } from "path";
21317
21526
  import chalk138 from "chalk";
21318
21527
 
21319
21528
  // src/commands/criteriaExtension/latestUpdate.ts
21320
- function latestUpdate(manifest, updates) {
21529
+ function latestUpdate(manifest, updates2) {
21321
21530
  const { id } = JSON.parse(manifest).browser_specific_settings.gecko;
21322
- return JSON.parse(updates).addons[id]?.updates.at(-1);
21531
+ return JSON.parse(updates2).addons[id]?.updates.at(-1);
21323
21532
  }
21324
21533
 
21325
21534
  // src/commands/criteriaExtension/latestUpdateLink.ts
21326
- function latestUpdateLink(manifest, updates) {
21327
- return latestUpdate(manifest, updates)?.update_link;
21535
+ function latestUpdateLink(manifest, updates2) {
21536
+ return latestUpdate(manifest, updates2)?.update_link;
21328
21537
  }
21329
21538
 
21330
21539
  // src/commands/criteriaExtension/printCriteriaExtensionUrl.ts
21331
21540
  async function printCriteriaExtensionUrl() {
21332
21541
  const manifest = await readFile4(
21333
- join49(criteriaExtensionDir(), "manifest.json"),
21542
+ join51(criteriaExtensionDir(), "manifest.json"),
21334
21543
  "utf8"
21335
21544
  );
21336
21545
  const { update_url } = JSON.parse(manifest).browser_specific_settings.gecko;
@@ -21346,16 +21555,16 @@ async function printCriteriaExtensionUrl() {
21346
21555
 
21347
21556
  // src/commands/criteriaExtension/signCriteriaExtension.ts
21348
21557
  import { rm as rm2 } from "fs/promises";
21349
- import { homedir as homedir20, tmpdir as tmpdir3 } from "os";
21350
- import { join as join55 } from "path";
21558
+ import { homedir as homedir21, tmpdir as tmpdir3 } from "os";
21559
+ import { join as join57 } from "path";
21351
21560
  import chalk140 from "chalk";
21352
21561
 
21353
21562
  // src/commands/criteriaExtension/copySignedXpi.ts
21354
21563
  import { copyFile } from "fs/promises";
21355
- import { join as join50 } from "path";
21564
+ import { join as join52 } from "path";
21356
21565
  var SIGNED_XPI_NAME = "criteria-extension.xpi";
21357
21566
  async function copySignedXpi(xpi, dir) {
21358
- const fixed2 = join50(dir, SIGNED_XPI_NAME);
21567
+ const fixed2 = join52(dir, SIGNED_XPI_NAME);
21359
21568
  if (fixed2 === xpi) return fixed2;
21360
21569
  await copyFile(xpi, fixed2);
21361
21570
  return fixed2;
@@ -21364,11 +21573,11 @@ async function copySignedXpi(xpi, dir) {
21364
21573
  // src/commands/criteriaExtension/findSignedXpi.ts
21365
21574
  import { existsSync as existsSync47 } from "fs";
21366
21575
  import { readdir } from "fs/promises";
21367
- import { join as join51 } from "path";
21576
+ import { join as join53 } from "path";
21368
21577
  async function findSignedXpi(dir) {
21369
21578
  if (!existsSync47(dir)) return null;
21370
21579
  const name = (await readdir(dir)).find((entry) => entry.endsWith(".xpi"));
21371
- return name ? join51(dir, name) : null;
21580
+ return name ? join53(dir, name) : null;
21372
21581
  }
21373
21582
 
21374
21583
  // src/commands/criteriaExtension/signedAddonInstallPath.ts
@@ -21391,10 +21600,10 @@ async function signedAddonInstallPath(xpi) {
21391
21600
 
21392
21601
  // src/commands/criteriaExtension/signPreflightProblem.ts
21393
21602
  import { existsSync as existsSync48 } from "fs";
21394
- import { join as join52 } from "path";
21603
+ import { join as join54 } from "path";
21395
21604
  var KEY_URL = "https://addons.mozilla.org/en-US/developers/addon/api/key/";
21396
21605
  function signPreflightProblem(source) {
21397
- if (!existsSync48(join52(source, "content.js")))
21606
+ if (!existsSync48(join54(source, "content.js")))
21398
21607
  return {
21399
21608
  message: `no content.js in ${source}`,
21400
21609
  hint: "run npm run build to bundle the content script"
@@ -21409,7 +21618,7 @@ function signPreflightProblem(source) {
21409
21618
 
21410
21619
  // src/commands/criteriaExtension/stageCriteriaExtension.ts
21411
21620
  import { cp as cp3, mkdir as mkdir5, readFile as readFile5, rm, writeFile as writeFile4 } from "fs/promises";
21412
- import { basename as basename13, join as join53 } from "path";
21621
+ import { basename as basename14, join as join55 } from "path";
21413
21622
 
21414
21623
  // src/commands/criteriaExtension/stampManifestVersion.ts
21415
21624
  function stampManifestVersion(manifest, version2) {
@@ -21424,9 +21633,9 @@ async function stageCriteriaExtension(source, staging, version2) {
21424
21633
  await mkdir5(staging, { recursive: true });
21425
21634
  await cp3(source, staging, {
21426
21635
  recursive: true,
21427
- filter: (path106) => basename13(path106) !== "updates.json"
21636
+ filter: (path106) => basename14(path106) !== "updates.json"
21428
21637
  });
21429
- const manifest = join53(staging, "manifest.json");
21638
+ const manifest = join55(staging, "manifest.json");
21430
21639
  const stamped = stampManifestVersion(
21431
21640
  await readFile5(manifest, "utf8"),
21432
21641
  version2
@@ -21436,13 +21645,13 @@ async function stageCriteriaExtension(source, staging, version2) {
21436
21645
 
21437
21646
  // src/commands/criteriaExtension/writeUpdateManifest.ts
21438
21647
  import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
21439
- import { join as join54 } from "path";
21648
+ import { join as join56 } from "path";
21440
21649
 
21441
21650
  // src/commands/criteriaExtension/buildUpdateManifest.ts
21442
21651
  var RELEASE_DOWNLOAD_URL = "https://github.com/staff0rd/assist/releases/download";
21443
21652
  function buildUpdateManifest(manifest, version2) {
21444
21653
  const { id } = JSON.parse(manifest).browser_specific_settings.gecko;
21445
- const updates = {
21654
+ const updates2 = {
21446
21655
  addons: {
21447
21656
  [id]: {
21448
21657
  updates: [
@@ -21454,24 +21663,24 @@ function buildUpdateManifest(manifest, version2) {
21454
21663
  }
21455
21664
  }
21456
21665
  };
21457
- return `${JSON.stringify(updates, null, " ")}
21666
+ return `${JSON.stringify(updates2, null, " ")}
21458
21667
  `;
21459
21668
  }
21460
21669
 
21461
21670
  // src/commands/criteriaExtension/writeUpdateManifest.ts
21462
21671
  async function writeUpdateManifest(source, dir, version2) {
21463
21672
  await writeFile5(
21464
- join54(dir, "updates.json"),
21673
+ join56(dir, "updates.json"),
21465
21674
  buildUpdateManifest(
21466
- await readFile6(join54(source, "manifest.json"), "utf8"),
21675
+ await readFile6(join56(source, "manifest.json"), "utf8"),
21467
21676
  version2
21468
21677
  )
21469
21678
  );
21470
21679
  }
21471
21680
 
21472
21681
  // src/commands/criteriaExtension/signCriteriaExtension.ts
21473
- var ARTIFACTS_DIR = join55(homedir20(), ".assist", "criteria-extension");
21474
- var STAGING_DIR = join55(tmpdir3(), "assist-criteria-extension");
21682
+ var ARTIFACTS_DIR = join57(homedir21(), ".assist", "criteria-extension");
21683
+ var STAGING_DIR = join57(tmpdir3(), "assist-criteria-extension");
21475
21684
  async function signCriteriaExtension() {
21476
21685
  const source = criteriaExtensionDir();
21477
21686
  const problem = signPreflightProblem(source);
@@ -21659,12 +21868,12 @@ import { unlinkSync as unlinkSync9, writeFileSync as writeFileSync26 } from "fs"
21659
21868
  import chalk143 from "chalk";
21660
21869
 
21661
21870
  // src/commands/dbMigration/getMigrationPinPath.ts
21662
- import { join as join56 } from "path";
21871
+ import { join as join58 } from "path";
21663
21872
  function getMigrationPinPath(pin2) {
21664
- return join56(getRestrictedDir(), `db-migration-pin-${pin2}.json`);
21873
+ return join58(getRestrictedDir(), `db-migration-pin-${pin2}.json`);
21665
21874
  }
21666
21875
  function getMigrationApprovalPath(migrationId) {
21667
- return join56(getRestrictedDir(), `db-migration-approval-${migrationId}.json`);
21876
+ return join58(getRestrictedDir(), `db-migration-approval-${migrationId}.json`);
21668
21877
  }
21669
21878
 
21670
21879
  // src/commands/dbMigration/readMigrationPinState.ts
@@ -21797,15 +22006,15 @@ function registerDeploy(program2) {
21797
22006
  }
21798
22007
 
21799
22008
  // src/commands/devlog/list.ts
21800
- import { execFileSync as execFileSync5 } from "child_process";
21801
- import { basename as basename15 } from "path";
22009
+ import { execFileSync as execFileSync6 } from "child_process";
22010
+ import { basename as basename16 } from "path";
21802
22011
 
21803
22012
  // src/commands/devlog/loadBlogSkipDays.ts
21804
- import { homedir as homedir21 } from "os";
21805
- import { join as join57 } from "path";
21806
- var BLOG_REPO_ROOT = join57(homedir21(), "git/blog");
22013
+ import { homedir as homedir22 } from "os";
22014
+ import { join as join59 } from "path";
22015
+ var BLOG_REPO_ROOT = join59(homedir22(), "git/blog");
21807
22016
  function loadBlogSkipDays(repoName) {
21808
- const config = loadRawYaml(join57(BLOG_REPO_ROOT, "assist.yml"));
22017
+ const config = loadRawYaml(join59(BLOG_REPO_ROOT, "assist.yml"));
21809
22018
  const devlog = config.devlog;
21810
22019
  const skip2 = devlog?.skip;
21811
22020
  return new Set(skip2?.[repoName]);
@@ -21817,13 +22026,13 @@ import chalk146 from "chalk";
21817
22026
 
21818
22027
  // src/shared/getRepoName.ts
21819
22028
  import { existsSync as existsSync51, readFileSync as readFileSync33 } from "fs";
21820
- import { basename as basename14, join as join58 } from "path";
22029
+ import { basename as basename15, join as join60 } from "path";
21821
22030
  function getRepoName() {
21822
22031
  const config = loadConfig();
21823
22032
  if (config.devlog?.name) {
21824
22033
  return config.devlog.name;
21825
22034
  }
21826
- const packageJsonPath = join58(process.cwd(), "package.json");
22035
+ const packageJsonPath = join60(process.cwd(), "package.json");
21827
22036
  if (existsSync51(packageJsonPath)) {
21828
22037
  try {
21829
22038
  const content = readFileSync33(packageJsonPath, "utf8");
@@ -21834,13 +22043,13 @@ function getRepoName() {
21834
22043
  } catch {
21835
22044
  }
21836
22045
  }
21837
- return basename14(process.cwd());
22046
+ return basename15(process.cwd());
21838
22047
  }
21839
22048
 
21840
22049
  // src/commands/devlog/loadDevlogEntries.ts
21841
22050
  import { readdirSync as readdirSync7, readFileSync as readFileSync34 } from "fs";
21842
- import { join as join59 } from "path";
21843
- var DEVLOG_DIR = join59(BLOG_REPO_ROOT, "src/content/devlog");
22051
+ import { join as join61 } from "path";
22052
+ var DEVLOG_DIR = join61(BLOG_REPO_ROOT, "src/content/devlog");
21844
22053
  function extractFrontmatter(content) {
21845
22054
  const fm = content.match(/^---\n([\s\S]*?)\n---/);
21846
22055
  return fm?.[1] ?? null;
@@ -21868,7 +22077,7 @@ function readDevlogFiles(callback) {
21868
22077
  try {
21869
22078
  const files = readdirSync7(DEVLOG_DIR).filter((f) => f.endsWith(".md"));
21870
22079
  for (const file of files) {
21871
- const content = readFileSync34(join59(DEVLOG_DIR, file), "utf8");
22080
+ const content = readFileSync34(join61(DEVLOG_DIR, file), "utf8");
21872
22081
  const parsed = parseFrontmatter(content, file);
21873
22082
  if (parsed) callback(parsed);
21874
22083
  }
@@ -21970,14 +22179,14 @@ function list2(options2) {
21970
22179
  const config = loadConfig();
21971
22180
  const days = options2.days ?? 30;
21972
22181
  const ignore3 = options2.ignore ?? config.devlog?.ignore ?? [];
21973
- const repoName = basename15(process.cwd());
22182
+ const repoName = basename16(process.cwd());
21974
22183
  const skipDays = loadBlogSkipDays(repoName);
21975
22184
  const devlogEntries = loadDevlogEntries(repoName);
21976
22185
  const args = ["log"];
21977
22186
  if (options2.reverse) args.push("--reverse");
21978
22187
  else args.push("-n", "500");
21979
22188
  args.push("--pretty=format:%ad|%h|%s", "--date=short");
21980
- const output = execFileSync5("git", args, { encoding: "utf8" });
22189
+ const output = execFileSync6("git", args, { encoding: "utf8" });
21981
22190
  const commitsByDate = parseGitLogCommits(output, ignore3);
21982
22191
  let dateCount = 0;
21983
22192
  let isFirst = true;
@@ -22000,7 +22209,7 @@ function list2(options2) {
22000
22209
  }
22001
22210
 
22002
22211
  // src/commands/devlog/getLastVersionInfo.ts
22003
- import { execFileSync as execFileSync6, execSync as execSync32 } from "child_process";
22212
+ import { execFileSync as execFileSync7, execSync as execSync32 } from "child_process";
22004
22213
  import semver from "semver";
22005
22214
  function getVersionAtCommit(hash) {
22006
22215
  try {
@@ -22019,7 +22228,7 @@ function stripToMinor(version2) {
22019
22228
  }
22020
22229
  function getLastVersionInfoFromGit() {
22021
22230
  try {
22022
- const output = execFileSync6(
22231
+ const output = execFileSync7(
22023
22232
  "git",
22024
22233
  ["log", "-1", "--pretty=format:%ad|%h", "--date=short"],
22025
22234
  {
@@ -22064,7 +22273,7 @@ function bumpVersion(version2, type) {
22064
22273
  }
22065
22274
 
22066
22275
  // src/commands/devlog/next/displayNextEntry.ts
22067
- import { execFileSync as execFileSync7 } from "child_process";
22276
+ import { execFileSync as execFileSync8 } from "child_process";
22068
22277
  import chalk149 from "chalk";
22069
22278
 
22070
22279
  // src/commands/devlog/next/displayNextEntry/displayVersion.ts
@@ -22098,7 +22307,7 @@ function findTargetDate(commitsByDate, skipDays) {
22098
22307
  return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
22099
22308
  }
22100
22309
  function fetchCommitsByDate(ignore3, lastDate) {
22101
- const output = execFileSync7(
22310
+ const output = execFileSync8(
22102
22311
  "git",
22103
22312
  ["log", "--pretty=format:%ad|%h|%s", "--date=short", "-n", "500"],
22104
22313
  { encoding: "utf8" }
@@ -22256,11 +22465,11 @@ function repos(options2) {
22256
22465
 
22257
22466
  // src/commands/devlog/skip.ts
22258
22467
  import { writeFileSync as writeFileSync29 } from "fs";
22259
- import { join as join60 } from "path";
22468
+ import { join as join62 } from "path";
22260
22469
  import chalk151 from "chalk";
22261
22470
  import { stringify as stringifyYaml3 } from "yaml";
22262
22471
  function getBlogConfigPath() {
22263
- return join60(BLOG_REPO_ROOT, "assist.yml");
22472
+ return join62(BLOG_REPO_ROOT, "assist.yml");
22264
22473
  }
22265
22474
  function skip(date) {
22266
22475
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
@@ -22322,7 +22531,7 @@ function registerDevlog(program2) {
22322
22531
 
22323
22532
  // src/commands/dotnet/checkBuildLocks.ts
22324
22533
  import { closeSync as closeSync4, openSync as openSync5, readdirSync as readdirSync8 } from "fs";
22325
- import { join as join61 } from "path";
22534
+ import { join as join63 } from "path";
22326
22535
  import chalk153 from "chalk";
22327
22536
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "packages"]);
22328
22537
  function isLockedDll(debugDir) {
@@ -22334,7 +22543,7 @@ function isLockedDll(debugDir) {
22334
22543
  }
22335
22544
  for (const file of files) {
22336
22545
  if (!file.toLowerCase().endsWith(".dll")) continue;
22337
- const dllPath = join61(debugDir, file);
22546
+ const dllPath = join63(debugDir, file);
22338
22547
  try {
22339
22548
  const fd = openSync5(dllPath, "r+");
22340
22549
  closeSync4(fd);
@@ -22352,13 +22561,13 @@ function findFirstLockedDll(dir) {
22352
22561
  return null;
22353
22562
  }
22354
22563
  if (entries.includes("bin")) {
22355
- const locked = isLockedDll(join61(dir, "bin", "Debug"));
22564
+ const locked = isLockedDll(join63(dir, "bin", "Debug"));
22356
22565
  if (locked) return locked;
22357
22566
  }
22358
22567
  for (const entry of entries) {
22359
22568
  if (SKIP_DIRS.has(entry) || entry === "bin" || entry.startsWith("."))
22360
22569
  continue;
22361
- const found = findFirstLockedDll(join61(dir, entry));
22570
+ const found = findFirstLockedDll(join63(dir, entry));
22362
22571
  if (found) return found;
22363
22572
  }
22364
22573
  return null;
@@ -22705,11 +22914,11 @@ import chalk159 from "chalk";
22705
22914
 
22706
22915
  // src/commands/dotnet/findSolution.ts
22707
22916
  import { readdirSync as readdirSync10 } from "fs";
22708
- import { dirname as dirname27, join as join62 } from "path";
22917
+ import { dirname as dirname27, join as join64 } from "path";
22709
22918
  import chalk158 from "chalk";
22710
22919
  function findSlnInDir(dir) {
22711
22920
  try {
22712
- return readdirSync10(dir).filter((f) => f.endsWith(".sln")).map((f) => join62(dir, f));
22921
+ return readdirSync10(dir).filter((f) => f.endsWith(".sln")).map((f) => join64(dir, f));
22713
22922
  } catch {
22714
22923
  return [];
22715
22924
  }
@@ -23704,7 +23913,7 @@ function aggregateCommitters(authorLists) {
23704
23913
  import { spawnSync as spawnSync6 } from "child_process";
23705
23914
  import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync30 } from "fs";
23706
23915
  import { tmpdir as tmpdir5 } from "os";
23707
- import { join as join63 } from "path";
23916
+ import { join as join65 } from "path";
23708
23917
  function buildArgs2(queryFile, vars) {
23709
23918
  const args = ["api", "graphql", "-F", `query=@${queryFile}`];
23710
23919
  for (const [key, value] of Object.entries(vars)) {
@@ -23714,7 +23923,7 @@ function buildArgs2(queryFile, vars) {
23714
23923
  return args;
23715
23924
  }
23716
23925
  function runGhGraphql(mutation, vars) {
23717
- const queryFile = join63(tmpdir5(), `gh-query-${Date.now()}.graphql`);
23926
+ const queryFile = join65(tmpdir5(), `gh-query-${Date.now()}.graphql`);
23718
23927
  writeFileSync30(queryFile, mutation);
23719
23928
  try {
23720
23929
  const result = spawnSync6("gh", buildArgs2(queryFile, vars), {
@@ -24427,7 +24636,7 @@ Anything nested below the leaf level fails the run before a single write, naming
24427
24636
  }
24428
24637
 
24429
24638
  // src/commands/github/issue/commentIssue.ts
24430
- import { execFileSync as execFileSync8 } from "child_process";
24639
+ import { execFileSync as execFileSync9 } from "child_process";
24431
24640
 
24432
24641
  // src/shared/validateProposedContent.ts
24433
24642
  function validateProposedContent(labels, title, body) {
@@ -24488,7 +24697,7 @@ async function commentIssue(numberArg, options2) {
24488
24697
  const args = ["issue", "comment", String(number), "--body", body];
24489
24698
  if (options2.repo) args.push("--repo", options2.repo);
24490
24699
  try {
24491
- execFileSync8("gh", args, { stdio: "inherit" });
24700
+ execFileSync9("gh", args, { stdio: "inherit" });
24492
24701
  } catch {
24493
24702
  process.exit(1);
24494
24703
  }
@@ -24891,8 +25100,8 @@ function attachArgs(attachments) {
24891
25100
  }
24892
25101
 
24893
25102
  // src/commands/prs/runGhWithAttachments.ts
24894
- import { execFileSync as execFileSync9 } from "child_process";
24895
- import { basename as basename16 } from "path";
25103
+ import { execFileSync as execFileSync10 } from "child_process";
25104
+ import { basename as basename17 } from "path";
24896
25105
 
24897
25106
  // src/shared/removeStagedAttachments.ts
24898
25107
  import { rmSync as rmSync4 } from "fs";
@@ -24909,7 +25118,7 @@ function removeStagedAttachments(attachments) {
24909
25118
  var PLACED_URL = /https:\/\/\S+\/(pull|issues)\/\d+/;
24910
25119
  function reportPartialFailure(attachments, stderr) {
24911
25120
  const failed3 = attachments.filter(
24912
- ({ path: path106 }) => stderr.includes(path106) || stderr.includes(basename16(path106))
25121
+ ({ path: path106 }) => stderr.includes(path106) || stderr.includes(basename17(path106))
24913
25122
  );
24914
25123
  const named = failed3.length > 0 ? failed3 : attachments;
24915
25124
  console.error(
@@ -24919,7 +25128,7 @@ function reportPartialFailure(attachments, stderr) {
24919
25128
  function runGhWithAttachments(args, attachments) {
24920
25129
  const attaching = attachments.length > 0;
24921
25130
  try {
24922
- const stdout = execFileSync9("gh", args, {
25131
+ const stdout = execFileSync10("gh", args, {
24923
25132
  encoding: "utf8",
24924
25133
  stdio: ["inherit", "pipe", attaching ? "pipe" : "inherit"]
24925
25134
  });
@@ -25030,7 +25239,7 @@ function inWebSession() {
25030
25239
  }
25031
25240
 
25032
25241
  // src/commands/github/issue/fetchIssue.ts
25033
- import { execFileSync as execFileSync10 } from "child_process";
25242
+ import { execFileSync as execFileSync11 } from "child_process";
25034
25243
  function fetchIssue2(number, repo) {
25035
25244
  const args = [
25036
25245
  "issue",
@@ -25042,7 +25251,7 @@ function fetchIssue2(number, repo) {
25042
25251
  if (repo) args.push("--repo", repo);
25043
25252
  let raw;
25044
25253
  try {
25045
- raw = execFileSync10("gh", args, { encoding: "utf8" });
25254
+ raw = execFileSync11("gh", args, { encoding: "utf8" });
25046
25255
  } catch {
25047
25256
  console.error(`Could not fetch issue #${number} with gh issue view`);
25048
25257
  process.exit(1);
@@ -25059,14 +25268,14 @@ function fetchIssue2(number, repo) {
25059
25268
  import { existsSync as existsSync56, readFileSync as readFileSync38 } from "fs";
25060
25269
 
25061
25270
  // src/commands/github/issue/issueWorkingFile.ts
25062
- import { join as join64 } from "path";
25271
+ import { join as join66 } from "path";
25063
25272
  function issueWorkingFile(slug2, number) {
25064
25273
  const [owner = "unknown", repo = "unknown"] = slug2.split("/");
25065
- const dir = join64(getStoreDir(), "github-issues", owner, repo);
25274
+ const dir = join66(getStoreDir(), "github-issues", owner, repo);
25066
25275
  return {
25067
25276
  dir,
25068
- bodyPath: join64(dir, `${number}.md`),
25069
- metaPath: join64(dir, `${number}.json`)
25277
+ bodyPath: join66(dir, `${number}.md`),
25278
+ metaPath: join66(dir, `${number}.json`)
25070
25279
  };
25071
25280
  }
25072
25281
 
@@ -25133,12 +25342,12 @@ function prepareIssueEdit(number, repo, fresh) {
25133
25342
  }
25134
25343
 
25135
25344
  // src/commands/github/issue/pushIssueBody.ts
25136
- import { execFileSync as execFileSync11 } from "child_process";
25345
+ import { execFileSync as execFileSync12 } from "child_process";
25137
25346
  function pushIssueBody(number, repo, bodyPath) {
25138
25347
  const args = ["issue", "edit", String(number), "--body-file", bodyPath];
25139
25348
  if (repo) args.push("--repo", repo);
25140
25349
  try {
25141
- execFileSync11("gh", args, { stdio: "inherit" });
25350
+ execFileSync12("gh", args, { stdio: "inherit" });
25142
25351
  } catch {
25143
25352
  process.exit(1);
25144
25353
  }
@@ -25198,12 +25407,12 @@ function setIssueParent(number, repo, parentArg) {
25198
25407
  }
25199
25408
 
25200
25409
  // src/commands/github/issue/viewIssue.ts
25201
- import { execFileSync as execFileSync12 } from "child_process";
25410
+ import { execFileSync as execFileSync13 } from "child_process";
25202
25411
  function viewIssue(number, repo) {
25203
25412
  const args = ["issue", "view", String(number)];
25204
25413
  if (repo) args.push("--repo", repo);
25205
25414
  try {
25206
- execFileSync12("gh", args, { stdio: "inherit" });
25415
+ execFileSync13("gh", args, { stdio: "inherit" });
25207
25416
  } catch {
25208
25417
  process.exit(1);
25209
25418
  }
@@ -25259,7 +25468,7 @@ function registerEditIssue(issueCommand) {
25259
25468
  }
25260
25469
 
25261
25470
  // src/commands/github/issue/editIssueComment.ts
25262
- import { execFileSync as execFileSync13 } from "child_process";
25471
+ import { execFileSync as execFileSync14 } from "child_process";
25263
25472
  var USAGE4 = "Usage: assist github issue edit-comment <comment-id> --body <body> [-R <owner>/<repo>]";
25264
25473
  async function editIssueComment(commentIdArg, options2) {
25265
25474
  const commentId = Number.parseInt(commentIdArg, 10);
@@ -25286,7 +25495,7 @@ async function editIssueComment(commentIdArg, options2) {
25286
25495
  ];
25287
25496
  let raw;
25288
25497
  try {
25289
- raw = execFileSync13("gh", args, {
25498
+ raw = execFileSync14("gh", args, {
25290
25499
  input: JSON.stringify({ body }),
25291
25500
  encoding: "utf8"
25292
25501
  });
@@ -25667,7 +25876,7 @@ function registerRefineLaunch(program2, resumeFlag) {
25667
25876
  import { randomUUID as randomUUID12 } from "crypto";
25668
25877
 
25669
25878
  // src/commands/review/checkoutPr.ts
25670
- import { execFileSync as execFileSync15 } from "child_process";
25879
+ import { execFileSync as execFileSync16 } from "child_process";
25671
25880
  import chalk170 from "chalk";
25672
25881
 
25673
25882
  // src/commands/sessions/daemon/daemonLog.ts
@@ -25693,30 +25902,19 @@ function recentDaemonLogLines(includeRelayed = true) {
25693
25902
  return ring.filter((entry) => includeRelayed || !entry.relayed).map((entry) => entry.line);
25694
25903
  }
25695
25904
 
25696
- // src/commands/sessions/daemon/worktree/canonicalTreePath.ts
25697
- import { realpathSync as realpathSync2 } from "fs";
25698
- import { resolve as resolve16 } from "path";
25699
- function canonicalTreePath(path106) {
25700
- try {
25701
- return realpathSync2(path106);
25702
- } catch {
25703
- return resolve16(path106);
25704
- }
25705
- }
25706
-
25707
25905
  // src/commands/sessions/daemon/worktree/createWorktree.ts
25708
25906
  import { existsSync as existsSync57 } from "fs";
25709
- import { basename as basename18, dirname as dirname29 } from "path";
25907
+ import { basename as basename19, dirname as dirname29 } from "path";
25710
25908
 
25711
25909
  // src/commands/sessions/daemon/worktree/planAllocation.ts
25712
- import { basename as basename17, join as join65 } from "path";
25910
+ import { basename as basename18, join as join67 } from "path";
25713
25911
  function planAllocation(clone, boundTreeRoots2) {
25714
25912
  return boundTreeRoots2.has(clone) ? "spill" : "primary";
25715
25913
  }
25716
25914
  function nextWorktreePath(clone, base, isTaken) {
25717
- const name = basename17(clone);
25915
+ const name = basename18(clone);
25718
25916
  for (let n = 2; n < 1e3; n++) {
25719
- const candidate = join65(base, `${name}-${n}`);
25917
+ const candidate = join67(base, `${name}-${n}`);
25720
25918
  if (!isTaken(candidate)) return candidate;
25721
25919
  }
25722
25920
  throw new Error(`no free worktree suffix for ${clone}`);
@@ -25752,7 +25950,7 @@ function createWorktree(clone, strategy, boundTreeRoots2, preferredPath) {
25752
25950
  const base = strategy.root ? expandTilde2(strategy.root) : dirname29(clone);
25753
25951
  const registered = new Set(listWorktreePaths(clone));
25754
25952
  const branches = new Set(listLocalBranches(clone));
25755
- const isTaken = (candidate) => registered.has(candidate) || existsSync57(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename18(candidate));
25953
+ const isTaken = (candidate) => registered.has(candidate) || existsSync57(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename19(candidate));
25756
25954
  const path106 = preferredPath && !isTaken(preferredPath) ? preferredPath : nextWorktreePath(clone, base, isTaken);
25757
25955
  const start2 = worktreeStartPoint(clone, strategy.trunk);
25758
25956
  gitSync(clone, [
@@ -25760,13 +25958,13 @@ function createWorktree(clone, strategy, boundTreeRoots2, preferredPath) {
25760
25958
  "add",
25761
25959
  start2.track ? "--track" : "--no-track",
25762
25960
  "-b",
25763
- basename18(path106),
25961
+ basename19(path106),
25764
25962
  path106,
25765
25963
  start2.ref
25766
25964
  ]);
25767
25965
  recordWorktree(path106, clone, getCurrentOrigin(clone));
25768
25966
  daemonLog(
25769
- start2.track ? `worktree allocated ${path106} (branch ${basename18(path106)} tracking ${start2.ref}) for clone ${clone}` : `worktree allocated ${path106} (branch ${basename18(path106)} off ${start2.ref}, no mainline tracking) for clone ${clone}`
25967
+ start2.track ? `worktree allocated ${path106} (branch ${basename19(path106)} tracking ${start2.ref}) for clone ${clone}` : `worktree allocated ${path106} (branch ${basename19(path106)} off ${start2.ref}, no mainline tracking) for clone ${clone}`
25770
25968
  );
25771
25969
  return path106;
25772
25970
  }
@@ -25861,7 +26059,6 @@ function wouldDisturbWorkInProgress(clone, forCheckout) {
25861
26059
  // src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
25862
26060
  var DISABLED = {
25863
26061
  enabled: false,
25864
- watcher: false,
25865
26062
  trunk: false,
25866
26063
  includeDrafts: false,
25867
26064
  install: true,
@@ -25994,7 +26191,6 @@ function toPersistedSession(session) {
25994
26191
  assistArgs: session.assistArgs,
25995
26192
  activity: session.activity,
25996
26193
  starred: session.starred,
25997
- watcher: session.watcher,
25998
26194
  divergenceEscalation: session.divergenceEscalation,
25999
26195
  design: session.design,
26000
26196
  auto: session.auto,
@@ -26070,19 +26266,19 @@ function persistedTreeRoots() {
26070
26266
 
26071
26267
  // src/commands/sessions/daemon/worktree/seedWorktree.ts
26072
26268
  import { copyFileSync, existsSync as existsSync60, mkdirSync as mkdirSync17 } from "fs";
26073
- import { dirname as dirname30, join as join67 } from "path";
26269
+ import { dirname as dirname30, join as join69 } from "path";
26074
26270
 
26075
26271
  // src/commands/sessions/daemon/worktree/runInstall.ts
26076
26272
  import { resolve as resolve17 } from "path";
26077
26273
 
26078
26274
  // src/commands/sessions/daemon/worktree/detectInstallCommand.ts
26079
26275
  import { existsSync as existsSync59 } from "fs";
26080
- import { join as join66 } from "path";
26276
+ import { join as join68 } from "path";
26081
26277
  function detectInstallCommand(dir) {
26082
- if (!existsSync59(join66(dir, "package.json"))) return null;
26083
- if (existsSync59(join66(dir, "pnpm-lock.yaml"))) return "pnpm install";
26084
- if (existsSync59(join66(dir, "yarn.lock"))) return "yarn install";
26085
- if (existsSync59(join66(dir, "bun.lockb"))) return "bun install";
26278
+ if (!existsSync59(join68(dir, "package.json"))) return null;
26279
+ if (existsSync59(join68(dir, "pnpm-lock.yaml"))) return "pnpm install";
26280
+ if (existsSync59(join68(dir, "yarn.lock"))) return "yarn install";
26281
+ if (existsSync59(join68(dir, "bun.lockb"))) return "bun install";
26086
26282
  return "npm install";
26087
26283
  }
26088
26284
 
@@ -26244,9 +26440,9 @@ function seedWorktree(worktreePath, clone, onSeeded = () => {
26244
26440
  }
26245
26441
  function copyConfigFiles(worktreePath, clone, copy) {
26246
26442
  for (const rel of copy) {
26247
- const src = join67(clone, rel);
26443
+ const src = join69(clone, rel);
26248
26444
  if (!existsSync60(src)) continue;
26249
- const dest = join67(worktreePath, rel);
26445
+ const dest = join69(worktreePath, rel);
26250
26446
  try {
26251
26447
  mkdirSync17(dirname30(dest), { recursive: true });
26252
26448
  copyFileSync(src, dest);
@@ -26287,10 +26483,10 @@ async function moveToPrCheckoutTree() {
26287
26483
  }
26288
26484
 
26289
26485
  // src/commands/review/prHeadBranch.ts
26290
- import { execFileSync as execFileSync14 } from "child_process";
26486
+ import { execFileSync as execFileSync15 } from "child_process";
26291
26487
  function prHeadBranch(number) {
26292
26488
  try {
26293
- const out = execFileSync14(
26489
+ const out = execFileSync15(
26294
26490
  "gh",
26295
26491
  ["pr", "view", number, "--json", "headRefName", "-q", ".headRefName"],
26296
26492
  { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
@@ -26352,7 +26548,7 @@ async function checkoutPr(number) {
26352
26548
  if (headRef && await moveToExistingCheckout(number, headRef)) return;
26353
26549
  await moveToPrCheckoutTree();
26354
26550
  try {
26355
- execFileSync15("gh", ["pr", "checkout", number], { stdio: "inherit" });
26551
+ execFileSync16("gh", ["pr", "checkout", number], { stdio: "inherit" });
26356
26552
  } catch {
26357
26553
  console.error(chalk170.red(`gh pr checkout ${number} failed; aborting.`));
26358
26554
  process.exit(1);
@@ -26556,7 +26752,7 @@ import chalk175 from "chalk";
26556
26752
 
26557
26753
  // src/commands/mermaid/exportFile.ts
26558
26754
  import { readFileSync as readFileSync39, writeFileSync as writeFileSync32 } from "fs";
26559
- import { basename as basename19, extname as extname3, resolve as resolve18 } from "path";
26755
+ import { basename as basename20, extname as extname3, resolve as resolve18 } from "path";
26560
26756
  import chalk174 from "chalk";
26561
26757
 
26562
26758
  // src/commands/mermaid/renderBlock.ts
@@ -26583,7 +26779,7 @@ async function renderBlock(krokiUrl, source) {
26583
26779
  async function exportFile(file, outDir, krokiUrl, onlyIndex) {
26584
26780
  const content = readFileSync39(file, "utf8");
26585
26781
  const blocks = extractMermaidBlocks(content);
26586
- const stem = basename19(file, extname3(file));
26782
+ const stem = basename20(file, extname3(file));
26587
26783
  if (onlyIndex !== void 0) {
26588
26784
  if (onlyIndex < 1 || onlyIndex > blocks.length) {
26589
26785
  console.error(
@@ -27280,15 +27476,15 @@ function createNetcapHandler(options2) {
27280
27476
  // src/commands/netcap/prepareExtensionForLoad.ts
27281
27477
  import { cp as cp4, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
27282
27478
  import { networkInterfaces } from "os";
27283
- import { join as join69 } from "path";
27479
+ import { join as join71 } from "path";
27284
27480
  import chalk178 from "chalk";
27285
27481
 
27286
27482
  // src/commands/netcap/netcapExtensionDir.ts
27287
- import { dirname as dirname32, join as join68 } from "path";
27483
+ import { dirname as dirname32, join as join70 } from "path";
27288
27484
  import { fileURLToPath as fileURLToPath7 } from "url";
27289
27485
  var moduleDir2 = dirname32(fileURLToPath7(import.meta.url));
27290
27486
  function netcapExtensionDir() {
27291
- return join68(moduleDir2, "commands", "netcap", "netcap-extension");
27487
+ return join70(moduleDir2, "commands", "netcap", "netcap-extension");
27292
27488
  }
27293
27489
 
27294
27490
  // src/commands/netcap/prepareExtensionForLoad.ts
@@ -27303,7 +27499,7 @@ function lanIPv4() {
27303
27499
  return void 0;
27304
27500
  }
27305
27501
  async function configureBackground(dir, host, port, filter) {
27306
- const file = join69(dir, "background.js");
27502
+ const file = join71(dir, "background.js");
27307
27503
  const source = await readFile7(file, "utf8");
27308
27504
  await writeFile6(
27309
27505
  file,
@@ -27343,20 +27539,20 @@ async function prepareExtensionForLoad(port, filter = "") {
27343
27539
  }
27344
27540
 
27345
27541
  // src/commands/netcap/resolveNetcapOutPath.ts
27346
- import { isAbsolute as isAbsolute5, join as join71, resolve as resolve21 } from "path";
27542
+ import { isAbsolute as isAbsolute5, join as join73, resolve as resolve21 } from "path";
27347
27543
 
27348
27544
  // src/commands/netcap/defaultCapturePath.ts
27349
- import { homedir as homedir22 } from "os";
27350
- import { join as join70 } from "path";
27545
+ import { homedir as homedir23 } from "os";
27546
+ import { join as join72 } from "path";
27351
27547
  function defaultCapturePath() {
27352
- return join70(homedir22(), ".assist", "netcap", "capture.jsonl");
27548
+ return join72(homedir23(), ".assist", "netcap", "capture.jsonl");
27353
27549
  }
27354
27550
 
27355
27551
  // src/commands/netcap/resolveNetcapOutPath.ts
27356
27552
  function resolveNetcapOutPath(out) {
27357
27553
  if (!out) return defaultCapturePath();
27358
27554
  const dir = isAbsolute5(out) ? out : resolve21(process.cwd(), out);
27359
- return join71(dir, "capture.jsonl");
27555
+ return join73(dir, "capture.jsonl");
27360
27556
  }
27361
27557
 
27362
27558
  // src/commands/netcap/netcap.ts
@@ -27403,7 +27599,7 @@ netcap stopped \u2014 captured ${count8} ${count8 === 1 ? "entry" : "entries"} t
27403
27599
 
27404
27600
  // src/commands/netcap/netcapExtract.ts
27405
27601
  import { writeFileSync as writeFileSync34 } from "fs";
27406
- import { join as join72 } from "path";
27602
+ import { join as join74 } from "path";
27407
27603
  import chalk180 from "chalk";
27408
27604
 
27409
27605
  // src/commands/netcap/extractPostsFromCapture.ts
@@ -27848,7 +28044,7 @@ function extractPostsFromCapture(captureFile) {
27848
28044
  function netcapExtract(file) {
27849
28045
  const captureFile = file ?? defaultCapturePath();
27850
28046
  const posts = extractPostsFromCapture(captureFile);
27851
- const outFile = join72(captureFile, "..", "posts.json");
28047
+ const outFile = join74(captureFile, "..", "posts.json");
27852
28048
  writeFileSync34(outFile, `${JSON.stringify(posts, null, 2)}
27853
28049
  `);
27854
28050
  console.log(
@@ -28211,18 +28407,18 @@ import { execSync as execSync41 } from "child_process";
28211
28407
  import { execSync as execSync40 } from "child_process";
28212
28408
  import { unlinkSync as unlinkSync14, writeFileSync as writeFileSync35 } from "fs";
28213
28409
  import { tmpdir as tmpdir6 } from "os";
28214
- import { join as join74 } from "path";
28410
+ import { join as join76 } from "path";
28215
28411
 
28216
28412
  // src/commands/prs/loadCommentsCache.ts
28217
28413
  import { existsSync as existsSync62, readFileSync as readFileSync43, unlinkSync as unlinkSync13 } from "fs";
28218
28414
  import { parse as parse3 } from "yaml";
28219
28415
 
28220
28416
  // src/commands/prs/commentsCachePath.ts
28221
- import { homedir as homedir23 } from "os";
28222
- import { join as join73 } from "path";
28417
+ import { homedir as homedir24 } from "os";
28418
+ import { join as join75 } from "path";
28223
28419
  function commentsCachePath(org, repo, prNumber) {
28224
- return join73(
28225
- homedir23(),
28420
+ return join75(
28421
+ homedir24(),
28226
28422
  ".assist",
28227
28423
  "pr-comments",
28228
28424
  org,
@@ -28270,7 +28466,7 @@ function replyToComment(org, repo, prNumber, commentId, message3) {
28270
28466
  // src/commands/prs/resolveCommentWithReply.ts
28271
28467
  function resolveThread(threadId) {
28272
28468
  const mutation = `mutation($threadId: ID!) { resolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } }`;
28273
- const queryFile = join74(tmpdir6(), `gh-mutation-${Date.now()}.graphql`);
28469
+ const queryFile = join76(tmpdir6(), `gh-mutation-${Date.now()}.graphql`);
28274
28470
  writeFileSync35(queryFile, mutation);
28275
28471
  try {
28276
28472
  execSync40(
@@ -28355,10 +28551,10 @@ function fixed(commentId, sha) {
28355
28551
  import { execSync as execSync42 } from "child_process";
28356
28552
  import { unlinkSync as unlinkSync15, writeFileSync as writeFileSync36 } from "fs";
28357
28553
  import { tmpdir as tmpdir7 } from "os";
28358
- import { join as join75 } from "path";
28554
+ import { join as join77 } from "path";
28359
28555
  var THREAD_QUERY = `query($owner: String!, $repo: String!, $prNumber: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $prNumber) { reviewThreads(first: 100) { nodes { id isResolved comments(first: 100) { nodes { databaseId } } } } } } }`;
28360
28556
  function fetchThreadIds(org, repo, prNumber) {
28361
- const queryFile = join75(tmpdir7(), `gh-query-${Date.now()}.graphql`);
28557
+ const queryFile = join77(tmpdir7(), `gh-query-${Date.now()}.graphql`);
28362
28558
  writeFileSync36(queryFile, THREAD_QUERY);
28363
28559
  try {
28364
28560
  const result = execSync42(
@@ -28433,10 +28629,10 @@ import { stringify as stringify3 } from "yaml";
28433
28629
 
28434
28630
  // src/commands/prs/removeStaleCommentsCaches.ts
28435
28631
  import { readdirSync as readdirSync12, unlinkSync as unlinkSync16 } from "fs";
28436
- import { join as join76 } from "path";
28632
+ import { join as join78 } from "path";
28437
28633
  var STALE_PATTERN = /^pr-\d+-comments\.yaml$/;
28438
28634
  function removeStaleCommentsCaches(cwd = process.cwd()) {
28439
- const dir = join76(cwd, ".assist");
28635
+ const dir = join78(cwd, ".assist");
28440
28636
  let entries;
28441
28637
  try {
28442
28638
  entries = readdirSync12(dir);
@@ -28444,7 +28640,7 @@ function removeStaleCommentsCaches(cwd = process.cwd()) {
28444
28640
  return;
28445
28641
  }
28446
28642
  for (const entry of entries.filter((e) => STALE_PATTERN.test(e))) {
28447
- unlinkSync16(join76(dir, entry));
28643
+ unlinkSync16(join78(dir, entry));
28448
28644
  }
28449
28645
  }
28450
28646
 
@@ -29078,7 +29274,7 @@ function buildValidatedBody(options2, usage) {
29078
29274
  }
29079
29275
 
29080
29276
  // src/commands/prs/placePr.ts
29081
- import { execFileSync as execFileSync16 } from "child_process";
29277
+ import { execFileSync as execFileSync17 } from "child_process";
29082
29278
 
29083
29279
  // src/commands/prs/buildCreateArgs.ts
29084
29280
  function buildCreateArgs(title, body, options2, attachments = []) {
@@ -29164,7 +29360,7 @@ function buildEditArgs(number, title, body, attachments = []) {
29164
29360
  // src/commands/prs/placePr.ts
29165
29361
  function hasUpstream2() {
29166
29362
  try {
29167
- execFileSync16(
29363
+ execFileSync17(
29168
29364
  "git",
29169
29365
  ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
29170
29366
  { stdio: "pipe" }
@@ -29176,7 +29372,7 @@ function hasUpstream2() {
29176
29372
  }
29177
29373
  function ensureBranchPushed() {
29178
29374
  const args = hasUpstream2() ? ["push"] : ["push", "--set-upstream", "origin", "HEAD"];
29179
- execFileSync16("git", args, { stdio: "inherit" });
29375
+ execFileSync17("git", args, { stdio: "inherit" });
29180
29376
  }
29181
29377
  async function placePr(prNumber, title, body, options2, attachments = []) {
29182
29378
  const args = prNumber !== null ? buildEditArgs(prNumber, title, body, attachments) : buildCreateArgs(title, body, options2, attachments);
@@ -29322,10 +29518,10 @@ function warn(reason4) {
29322
29518
  }
29323
29519
 
29324
29520
  // src/commands/prs/enableAutoMerge.ts
29325
- import { execFileSync as execFileSync17 } from "child_process";
29521
+ import { execFileSync as execFileSync18 } from "child_process";
29326
29522
  function enableAutoMerge() {
29327
29523
  try {
29328
- execFileSync17(
29524
+ execFileSync18(
29329
29525
  "gh",
29330
29526
  ["pr", "merge", getCurrentBranch2(), "--auto", "--squash"],
29331
29527
  { stdio: "inherit" }
@@ -31083,7 +31279,7 @@ async function notify() {
31083
31279
  import { execSync as execSync52 } from "child_process";
31084
31280
  import { existsSync as existsSync63, mkdirSync as mkdirSync21, unlinkSync as unlinkSync17, writeFileSync as writeFileSync38 } from "fs";
31085
31281
  import { tmpdir as tmpdir8 } from "os";
31086
- import { join as join77, resolve as resolve22 } from "path";
31282
+ import { join as join79, resolve as resolve22 } from "path";
31087
31283
  import chalk205 from "chalk";
31088
31284
 
31089
31285
  // src/commands/screenshot/captureWindowPs1.ts
@@ -31220,7 +31416,7 @@ function buildOutputPath(outputDir, processName) {
31220
31416
  return resolve22(outputDir, `${processName}-${timestamp5}.png`);
31221
31417
  }
31222
31418
  function runPowerShellScript(processName, outputPath) {
31223
- const scriptPath = join77(tmpdir8(), `assist-screenshot-${Date.now()}.ps1`);
31419
+ const scriptPath = join79(tmpdir8(), `assist-screenshot-${Date.now()}.ps1`);
31224
31420
  writeFileSync38(scriptPath, captureWindowPs1, "utf8");
31225
31421
  try {
31226
31422
  execSync52(
@@ -31335,9 +31531,9 @@ function buildLimitsSegment(rateLimits) {
31335
31531
 
31336
31532
  // src/commands/readGitBranch.ts
31337
31533
  import { readFileSync as readFileSync46, statSync as statSync10 } from "fs";
31338
- import { isAbsolute as isAbsolute6, join as join78, resolve as resolve23 } from "path";
31534
+ import { isAbsolute as isAbsolute6, join as join80, resolve as resolve23 } from "path";
31339
31535
  function resolveGitDir(cwd) {
31340
- const dotGit = join78(cwd, ".git");
31536
+ const dotGit = join80(cwd, ".git");
31341
31537
  let stat5;
31342
31538
  try {
31343
31539
  stat5 = statSync10(dotGit);
@@ -31367,7 +31563,7 @@ function readGitBranch(cwd) {
31367
31563
  }
31368
31564
  let head;
31369
31565
  try {
31370
- head = readFileSync46(join78(gitDir, "HEAD"), "utf8");
31566
+ head = readFileSync46(join80(gitDir, "HEAD"), "utf8");
31371
31567
  } catch {
31372
31568
  return null;
31373
31569
  }
@@ -33788,7 +33984,7 @@ function exitWith(message3) {
33788
33984
  }
33789
33985
 
33790
33986
  // src/commands/review/highLevel/proposeHighLevelGlobs.ts
33791
- import { execFileSync as execFileSync19 } from "child_process";
33987
+ import { execFileSync as execFileSync20 } from "child_process";
33792
33988
  import chalk228 from "chalk";
33793
33989
 
33794
33990
  // src/commands/review/highLevel/buildGlobProposalPrompt.ts
@@ -33836,7 +34032,7 @@ function readGlobs(value) {
33836
34032
  }
33837
34033
 
33838
34034
  // src/commands/review/highLevel/summariseRepoTree.ts
33839
- import { execFileSync as execFileSync18 } from "child_process";
34035
+ import { execFileSync as execFileSync19 } from "child_process";
33840
34036
  import { dirname as dirname36, extname as extname4 } from "path";
33841
34037
  var MAX_DIRECTORIES = 200;
33842
34038
  var MAX_EXTENSIONS_PER_DIRECTORY = 6;
@@ -33852,7 +34048,7 @@ function summariseRepoTree(cwd = process.cwd()) {
33852
34048
  }
33853
34049
  function listTrackedFiles(cwd) {
33854
34050
  try {
33855
- return execFileSync18("git", ["ls-files"], {
34051
+ return execFileSync19("git", ["ls-files"], {
33856
34052
  cwd,
33857
34053
  encoding: "utf8",
33858
34054
  maxBuffer: 64 * 1024 * 1024
@@ -33897,7 +34093,7 @@ function proposeHighLevelGlobs(cwd = process.cwd()) {
33897
34093
  }
33898
34094
  function askClaude(prompt, cwd) {
33899
34095
  try {
33900
- return execFileSync19("claude", ["-p", "--model", "sonnet", prompt], {
34096
+ return execFileSync20("claude", ["-p", "--model", "sonnet", prompt], {
33901
34097
  cwd,
33902
34098
  encoding: "utf8",
33903
34099
  timeout: 12e4,
@@ -33990,7 +34186,7 @@ function groupFilesByDirectory(files) {
33990
34186
  }
33991
34187
 
33992
34188
  // src/commands/review/highLevel/buildHighLevelTree.ts
33993
- function basename20(path106) {
34189
+ function basename21(path106) {
33994
34190
  return path106.slice(path106.lastIndexOf("/") + 1);
33995
34191
  }
33996
34192
  function sum(nodes, key) {
@@ -34006,7 +34202,7 @@ function nodesOf(at2, prefix2) {
34006
34202
  const files = at2.files.map(
34007
34203
  (file) => ({
34008
34204
  kind: "file",
34009
- name: basename20(file.path),
34205
+ name: basename21(file.path),
34010
34206
  ...file
34011
34207
  })
34012
34208
  );
@@ -34277,7 +34473,7 @@ function evaluateHighLevelChecks(subject) {
34277
34473
 
34278
34474
  // src/commands/review/highLevel/fetchHighLevelFiles.ts
34279
34475
  import { execSync as execSync55 } from "child_process";
34280
- import { createHash as createHash3 } from "crypto";
34476
+ import { createHash as createHash4 } from "crypto";
34281
34477
  var JQ = ".[] | {filename,status,additions,deletions,patch} | @json";
34282
34478
  function toStatus(status2) {
34283
34479
  if (status2 === "added") return "added";
@@ -34285,7 +34481,7 @@ function toStatus(status2) {
34285
34481
  return "modified";
34286
34482
  }
34287
34483
  function diffUrl(repo, prNumber, path106) {
34288
- const anchor = createHash3("sha256").update(path106).digest("hex");
34484
+ const anchor = createHash4("sha256").update(path106).digest("hex");
34289
34485
  return `https://github.com/${repo}/pull/${prNumber}/files#diff-${anchor}`;
34290
34486
  }
34291
34487
  function fetchHighLevelFiles(prNumber, repo) {
@@ -34326,12 +34522,12 @@ function fetchHighLevelPr(prNumber, repo) {
34326
34522
  import { existsSync as existsSync64, readFileSync as readFileSync47 } from "fs";
34327
34523
 
34328
34524
  // src/commands/review/highLevel/highLevelReviewPath.ts
34329
- import { join as join79 } from "path";
34525
+ import { join as join81 } from "path";
34330
34526
  function slug(value) {
34331
34527
  return value.replace(/[^\w.-]+/g, "-");
34332
34528
  }
34333
34529
  function highLevelReviewPath(repo, headRef, headSha) {
34334
- return join79(
34530
+ return join81(
34335
34531
  getStoreDir(),
34336
34532
  "high-level-reviews",
34337
34533
  slug(repo),
@@ -35407,18 +35603,7 @@ import { format } from "util";
35407
35603
 
35408
35604
  // src/commands/review/createReviewLogSink.ts
35409
35605
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync23 } from "fs";
35410
- import { join as join80 } from "path";
35411
-
35412
- // src/shared/stripAnsi.ts
35413
- var ANSI = new RegExp(
35414
- `${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`,
35415
- "g"
35416
- );
35417
- function stripAnsi(text17) {
35418
- return text17.replace(ANSI, "");
35419
- }
35420
-
35421
- // src/commands/review/createReviewLogSink.ts
35606
+ import { join as join82 } from "path";
35422
35607
  var LOG_FILE = "review.log";
35423
35608
  function createReviewLogSink() {
35424
35609
  let logPath2;
@@ -35435,7 +35620,7 @@ function createReviewLogSink() {
35435
35620
  },
35436
35621
  attach(reviewDir) {
35437
35622
  mkdirSync23(reviewDir, { recursive: true });
35438
- logPath2 = join80(reviewDir, LOG_FILE);
35623
+ logPath2 = join82(reviewDir, LOG_FILE);
35439
35624
  const lines2 = [
35440
35625
  "",
35441
35626
  `=== ${(/* @__PURE__ */ new Date()).toISOString()} ===`,
@@ -36516,22 +36701,22 @@ ${annotateDiffWithLineNumbers(context.diff.trimEnd())}
36516
36701
  }
36517
36702
 
36518
36703
  // src/commands/review/buildReviewPaths.ts
36519
- import { homedir as homedir24 } from "os";
36520
- import { basename as basename21, join as join81 } from "path";
36704
+ import { homedir as homedir25 } from "os";
36705
+ import { basename as basename22, join as join83 } from "path";
36521
36706
  function buildReviewPaths(repoRoot2, key) {
36522
- const reviewDir = join81(
36523
- homedir24(),
36707
+ const reviewDir = join83(
36708
+ homedir25(),
36524
36709
  ".assist",
36525
36710
  "reviews",
36526
- basename21(repoRoot2),
36711
+ basename22(repoRoot2),
36527
36712
  key
36528
36713
  );
36529
36714
  return {
36530
36715
  reviewDir,
36531
- requestPath: join81(reviewDir, "request.md"),
36532
- claudePath: join81(reviewDir, "claude.md"),
36533
- codexPath: join81(reviewDir, "codex.md"),
36534
- synthesisPath: join81(reviewDir, "synthesis.md")
36716
+ requestPath: join83(reviewDir, "request.md"),
36717
+ claudePath: join83(reviewDir, "claude.md"),
36718
+ codexPath: join83(reviewDir, "codex.md"),
36719
+ synthesisPath: join83(reviewDir, "synthesis.md")
36535
36720
  };
36536
36721
  }
36537
36722
 
@@ -36985,16 +37170,16 @@ import path87 from "path";
36985
37170
  import chalk239 from "chalk";
36986
37171
  function listRules(target, options2 = {}) {
36987
37172
  const resolved = path87.resolve(target ?? process.cwd());
36988
- const rules3 = readScopedRules(resolved);
36989
- if (rules3.length === 0) {
37173
+ const rules2 = readScopedRules(resolved);
37174
+ if (rules2.length === 0) {
36990
37175
  const label2 = path87.relative(process.cwd(), resolved) || ".";
36991
37176
  console.log(chalk239.gray(`No rules in scope for ${label2}`));
36992
37177
  return;
36993
37178
  }
36994
37179
  const base = findRepoRoot(scopeDirectory(resolved));
36995
- const width = Math.max(...rules3.map((rule) => rule.code.length));
37180
+ const width = Math.max(...rules2.map((rule) => rule.code.length));
36996
37181
  let shown;
36997
- for (const rule of rules3) {
37182
+ for (const rule of rules2) {
36998
37183
  if (rule.source !== shown) {
36999
37184
  shown = rule.source;
37000
37185
  console.log(
@@ -37512,12 +37697,12 @@ async function reviewProposedSlackMessage(target, body, workingPath) {
37512
37697
  }
37513
37698
 
37514
37699
  // src/commands/slack/slackWorkingFile.ts
37515
- import { join as join82 } from "path";
37700
+ import { join as join84 } from "path";
37516
37701
  function slackWorkingFile(channel, partIndex) {
37517
37702
  const slug2 = channel.replace(/^[#@]/, "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "channel";
37518
- const dir = join82(getStoreDir(), "slack");
37703
+ const dir = join84(getStoreDir(), "slack");
37519
37704
  const name = partIndex === void 0 ? slug2 : `${slug2}-${partIndex}`;
37520
- return { dir, bodyPath: join82(dir, `${name}.md`) };
37705
+ return { dir, bodyPath: join84(dir, `${name}.md`) };
37521
37706
  }
37522
37707
 
37523
37708
  // src/commands/slack/previewSlackThreadParts.ts
@@ -38697,20 +38882,20 @@ async function configure() {
38697
38882
 
38698
38883
  // src/commands/transcript/list.ts
38699
38884
  import { existsSync as existsSync76, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
38700
- import { join as join94 } from "path";
38885
+ import { join as join96 } from "path";
38701
38886
  function list3() {
38702
38887
  const { vttDir } = getTranscriptConfig();
38703
38888
  if (!existsSync76(vttDir)) return;
38704
38889
  for (const entry of readdirSync20(vttDir)) {
38705
38890
  if (!entry.endsWith(".vtt")) continue;
38706
- if (statSync12(join94(vttDir, entry)).isDirectory()) continue;
38891
+ if (statSync12(join96(vttDir, entry)).isDirectory()) continue;
38707
38892
  console.log(entry);
38708
38893
  }
38709
38894
  }
38710
38895
 
38711
38896
  // src/commands/transcript/move.ts
38712
38897
  import { existsSync as existsSync77, mkdirSync as mkdirSync32, renameSync as renameSync2, writeFileSync as writeFileSync50 } from "fs";
38713
- import { basename as basename25, join as join95 } from "path";
38898
+ import { basename as basename26, join as join97 } from "path";
38714
38899
 
38715
38900
  // src/commands/transcript/convertVttToMarkdown.ts
38716
38901
  function convertVttToMarkdown(inputPath) {
@@ -38720,9 +38905,9 @@ function convertVttToMarkdown(inputPath) {
38720
38905
  // src/commands/transcript/move.ts
38721
38906
  var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
38722
38907
  function archiveRawVtt(vttDir, sourcePath, filename) {
38723
- const processedDir = join95(vttDir, "processed");
38908
+ const processedDir = join97(vttDir, "processed");
38724
38909
  mkdirSync32(processedDir, { recursive: true });
38725
- renameSync2(sourcePath, join95(processedDir, filename));
38910
+ renameSync2(sourcePath, join97(processedDir, filename));
38726
38911
  }
38727
38912
  function move(file, options2) {
38728
38913
  const { date, client } = options2;
@@ -38731,27 +38916,27 @@ function move(file, options2) {
38731
38916
  process.exit(1);
38732
38917
  }
38733
38918
  const { vttDir, transcriptsDir, summaryDir } = getTranscriptConfig();
38734
- const filename = basename25(file);
38735
- const sourcePath = join95(vttDir, filename);
38919
+ const filename = basename26(file);
38920
+ const sourcePath = join97(vttDir, filename);
38736
38921
  if (!existsSync77(sourcePath)) {
38737
38922
  console.error(`Error: VTT file not found: ${sourcePath}`);
38738
38923
  process.exit(1);
38739
38924
  }
38740
- const base = basename25(filename, ".vtt").replace(/ Transcription$/, "");
38925
+ const base = basename26(filename, ".vtt").replace(/ Transcription$/, "");
38741
38926
  const outputName = `${date} ${base}.md`;
38742
- const formattedDir = join95(transcriptsDir, client);
38927
+ const formattedDir = join97(transcriptsDir, client);
38743
38928
  mkdirSync32(formattedDir, { recursive: true });
38744
- const formattedPath = join95(formattedDir, outputName);
38929
+ const formattedPath = join97(formattedDir, outputName);
38745
38930
  writeFileSync50(formattedPath, convertVttToMarkdown(sourcePath), "utf8");
38746
38931
  archiveRawVtt(vttDir, sourcePath, filename);
38747
- const summaryPath = join95(summaryDir, client, outputName);
38932
+ const summaryPath = join97(summaryDir, client, outputName);
38748
38933
  console.log(`Formatted transcript: ${formattedPath}`);
38749
38934
  console.log(`Summary target: ${summaryPath}`);
38750
38935
  }
38751
38936
 
38752
38937
  // src/commands/transcript/merge.ts
38753
38938
  import { existsSync as existsSync78, writeFileSync as writeFileSync51 } from "fs";
38754
- import { basename as basename26 } from "path";
38939
+ import { basename as basename27 } from "path";
38755
38940
 
38756
38941
  // src/commands/transcript/failTranscript.ts
38757
38942
  function failTranscript(message3) {
@@ -38897,7 +39082,7 @@ function readSource3(file) {
38897
39082
  if (!existsSync78(file)) failTranscript(`VTT file not found: ${file}`);
38898
39083
  const cues = readCleanedCues(file);
38899
39084
  if (cues.length === 0) failTranscript(`no cues found in: ${file}`);
38900
- return { path: file, name: basename26(file), cues };
39085
+ return { path: file, name: basename27(file), cues };
38901
39086
  }
38902
39087
  function wholePassages(sources) {
38903
39088
  return sources.map((source) => ({
@@ -39388,14 +39573,14 @@ function findRuleViolations(data, rule) {
39388
39573
  }
39389
39574
  return violations;
39390
39575
  }
39391
- function findForbiddenStrings(rules3, readJson) {
39392
- return rules3.flatMap((rule) => findRuleViolations(readJson(rule.file), rule));
39576
+ function findForbiddenStrings(rules2, readJson) {
39577
+ return rules2.flatMap((rule) => findRuleViolations(readJson(rule.file), rule));
39393
39578
  }
39394
39579
 
39395
39580
  // src/commands/verify/forbiddenStrings.ts
39396
39581
  function forbiddenStrings() {
39397
- const rules3 = loadConfig().forbiddenStrings ?? [];
39398
- if (rules3.length === 0) {
39582
+ const rules2 = loadConfig().forbiddenStrings ?? [];
39583
+ if (rules2.length === 0) {
39399
39584
  console.log("No forbidden-strings rules configured.");
39400
39585
  process.exit(0);
39401
39586
  }
@@ -39416,7 +39601,7 @@ function forbiddenStrings() {
39416
39601
  cache6.set(file, parsed);
39417
39602
  return parsed;
39418
39603
  };
39419
- const violations = findForbiddenStrings(rules3, readJson);
39604
+ const violations = findForbiddenStrings(rules2, readJson);
39420
39605
  if (violations.length === 0) {
39421
39606
  console.log("No forbidden strings found.");
39422
39607
  process.exit(0);
@@ -39558,7 +39743,7 @@ function listMigrationFiles(dir) {
39558
39743
 
39559
39744
  // src/commands/verify/migrations/readBaselineMigrations.ts
39560
39745
  import { execSync as execSync63 } from "child_process";
39561
- import { basename as basename27 } from "path";
39746
+ import { basename as basename28 } from "path";
39562
39747
  var MIGRATION_FILE3 = /^migration\d+[A-Za-z0-9]*\.ts$/;
39563
39748
  function readBaselineMigrations(repoRelativeDir, ref) {
39564
39749
  const baseline = /* @__PURE__ */ new Map();
@@ -39571,7 +39756,7 @@ function readBaselineMigrations(repoRelativeDir, ref) {
39571
39756
  } catch {
39572
39757
  return baseline;
39573
39758
  }
39574
- const paths = listing.split("\n").map((line) => line.trim()).filter(Boolean).filter((path106) => MIGRATION_FILE3.test(basename27(path106)));
39759
+ const paths = listing.split("\n").map((line) => line.trim()).filter(Boolean).filter((path106) => MIGRATION_FILE3.test(basename28(path106)));
39575
39760
  for (const path106 of paths) {
39576
39761
  try {
39577
39762
  const content = execSync63(`git show "${ref}:${path106}"`, {
@@ -39579,7 +39764,7 @@ function readBaselineMigrations(repoRelativeDir, ref) {
39579
39764
  maxBuffer: 16 * 1024 * 1024,
39580
39765
  stdio: ["pipe", "pipe", "pipe"]
39581
39766
  });
39582
- baseline.set(basename27(path106), content);
39767
+ baseline.set(basename28(path106), content);
39583
39768
  } catch {
39584
39769
  }
39585
39770
  }
@@ -39792,46 +39977,6 @@ function gitFailureReason(error) {
39792
39977
  return message3.trim() || "git failed without reporting a reason";
39793
39978
  }
39794
39979
 
39795
- // src/commands/watch/resolveUpstream.ts
39796
- import { execFileSync as execFileSync20 } from "child_process";
39797
- function runGit3(args, cwd) {
39798
- return execFileSync20("git", args, {
39799
- encoding: "utf8",
39800
- stdio: ["pipe", "pipe", "pipe"],
39801
- cwd
39802
- }).trim();
39803
- }
39804
- function resolveUpstream(cwd) {
39805
- try {
39806
- runGit3(["rev-parse", "--is-inside-work-tree"], cwd);
39807
- } catch {
39808
- throw new Error(
39809
- "not a git repository \u2014 run assist watch wait from inside a repo"
39810
- );
39811
- }
39812
- let branch2;
39813
- try {
39814
- branch2 = runGit3(["symbolic-ref", "--quiet", "--short", "HEAD"], cwd);
39815
- } catch {
39816
- throw new Error(
39817
- "HEAD is detached \u2014 check out a branch before waiting on its upstream"
39818
- );
39819
- }
39820
- try {
39821
- return {
39822
- branch: branch2,
39823
- upstream: runGit3(
39824
- ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
39825
- cwd
39826
- )
39827
- };
39828
- } catch {
39829
- throw new Error(
39830
- `branch "${branch2}" has no upstream \u2014 set one with: git push -u origin ${branch2}`
39831
- );
39832
- }
39833
- }
39834
-
39835
39980
  // src/commands/watch/simulatedDivergencePath.ts
39836
39981
  import { resolve as resolve25 } from "path";
39837
39982
  function simulatedDivergencePath(cwd) {
@@ -39899,22 +40044,6 @@ async function watchLoop() {
39899
40044
  }
39900
40045
  }
39901
40046
 
39902
- // src/commands/watch/changedPaths.ts
39903
- function changedPaths(from, cwd) {
39904
- return runGit3(["diff", "--name-only", `${from}..HEAD`], cwd).split("\n").filter((line) => line.length > 0);
39905
- }
39906
-
39907
- // src/commands/watch/readBuiltVersion.ts
39908
- import { join as join96 } from "path";
39909
- function readBuiltVersion(cwd) {
39910
- try {
39911
- const root2 = runGit3(["rev-parse", "--show-toplevel"], cwd);
39912
- return readPackageJson(join96(root2, "package.json")).version ?? "unknown";
39913
- } catch {
39914
- return "unknown";
39915
- }
39916
- }
39917
-
39918
40047
  // src/commands/watch/readRecentCommits.ts
39919
40048
  function readRecentCommits(count8 = 10, cwd) {
39920
40049
  const output = runGit3(
@@ -39962,24 +40091,12 @@ function renderWatchReport({
39962
40091
  }
39963
40092
 
39964
40093
  // src/commands/watch/restartAdvice.ts
39965
- var webUiPrefix = "src/commands/sessions/web/ui/";
39966
- var sessionsPrefix = "src/commands/sessions/";
39967
- var rules = [
39968
- {
39969
- matches: (path106) => path106.startsWith(webUiPrefix),
39970
- advice: "restart the web server, then hard-reload the browser tab"
39971
- },
39972
- {
39973
- matches: (path106) => path106.startsWith(sessionsPrefix) && !path106.startsWith(webUiPrefix),
39974
- advice: "restart the daemon"
39975
- }
39976
- ];
39977
40094
  function restartAdvice(paths) {
39978
- return rules.filter((rule) => paths.some(rule.matches)).map((rule) => rule.advice);
40095
+ return restartRules.filter((rule) => paths.some(rule.matches)).map((rule) => rule.advice);
39979
40096
  }
39980
40097
 
39981
40098
  // src/commands/watch/syncAdvice.ts
39982
- var rules2 = [
40099
+ var rules = [
39983
40100
  {
39984
40101
  matches: (path106) => path106.startsWith("claude/commands/"),
39985
40102
  reason: "claude/commands changed"
@@ -40006,7 +40123,7 @@ var rules2 = [
40006
40123
  }
40007
40124
  ];
40008
40125
  function syncAdvice(paths) {
40009
- return rules2.filter((rule) => paths.some(rule.matches)).map((rule) => rule.reason);
40126
+ return rules.filter((rule) => paths.some(rule.matches)).map((rule) => rule.reason);
40010
40127
  }
40011
40128
 
40012
40129
  // src/commands/watch/buildWatchReport.ts
@@ -40692,7 +40809,7 @@ async function auth() {
40692
40809
  // src/commands/roam/postRoamActivity.ts
40693
40810
  import { execFileSync as execFileSync22 } from "child_process";
40694
40811
  import { readdirSync as readdirSync22, readFileSync as readFileSync62, statSync as statSync13 } from "fs";
40695
- import { join as join97 } from "path";
40812
+ import { join as join98 } from "path";
40696
40813
  function findPortFile(roamDir) {
40697
40814
  let entries;
40698
40815
  try {
@@ -40701,7 +40818,7 @@ function findPortFile(roamDir) {
40701
40818
  return void 0;
40702
40819
  }
40703
40820
  const candidates = entries.filter((name) => /^roam-local-api(-[^.]+)?\.port$/.test(name)).map((name) => {
40704
- const path106 = join97(roamDir, name);
40821
+ const path106 = join98(roamDir, name);
40705
40822
  try {
40706
40823
  return { path: path106, mtimeMs: statSync13(path106).mtimeMs };
40707
40824
  } catch {
@@ -40718,7 +40835,7 @@ var PID_BY_APP = {
40718
40835
  function postRoamActivity(app, event) {
40719
40836
  const appData = process.env.APPDATA;
40720
40837
  if (!appData) return;
40721
- const portFile = findPortFile(join97(appData, "Roam"));
40838
+ const portFile = findPortFile(join98(appData, "Roam"));
40722
40839
  if (!portFile) return;
40723
40840
  let port;
40724
40841
  try {
@@ -40854,7 +40971,7 @@ async function run3(name, args) {
40854
40971
 
40855
40972
  // src/commands/run/add.ts
40856
40973
  import { mkdirSync as mkdirSync33, writeFileSync as writeFileSync53 } from "fs";
40857
- import { join as join98 } from "path";
40974
+ import { join as join99 } from "path";
40858
40975
 
40859
40976
  // src/commands/run/extractOption.ts
40860
40977
  function extractOption(args, flag) {
@@ -40915,7 +41032,7 @@ function saveNewRunConfig(name, command, args, cwd) {
40915
41032
  saveConfig(config);
40916
41033
  }
40917
41034
  function createCommandFile(name) {
40918
- const dir = join98(".claude", "commands");
41035
+ const dir = join99(".claude", "commands");
40919
41036
  mkdirSync33(dir, { recursive: true });
40920
41037
  const content = `---
40921
41038
  description: Run ${name}
@@ -40923,7 +41040,7 @@ description: Run ${name}
40923
41040
 
40924
41041
  Run \`assist run ${name} $ARGUMENTS 2>&1\`.
40925
41042
  `;
40926
- const filePath = join98(dir, `${name}.md`);
41043
+ const filePath = join99(dir, `${name}.md`);
40927
41044
  writeFileSync53(filePath, content);
40928
41045
  console.log(`Created command file: ${filePath}`);
40929
41046
  }
@@ -40980,7 +41097,7 @@ function link2() {
40980
41097
 
40981
41098
  // src/commands/run/remove.ts
40982
41099
  import { existsSync as existsSync82, unlinkSync as unlinkSync21 } from "fs";
40983
- import { join as join99 } from "path";
41100
+ import { join as join100 } from "path";
40984
41101
  function findRemoveIndex() {
40985
41102
  const idx = process.argv.indexOf("remove");
40986
41103
  if (idx === -1 || idx + 1 >= process.argv.length) return -1;
@@ -40995,7 +41112,7 @@ function parseRemoveName() {
40995
41112
  return process.argv[idx + 1];
40996
41113
  }
40997
41114
  function deleteCommandFile(name) {
40998
- const filePath = join99(".claude", "commands", `${name}.md`);
41115
+ const filePath = join100(".claude", "commands", `${name}.md`);
40999
41116
  if (existsSync82(filePath)) {
41000
41117
  unlinkSync21(filePath);
41001
41118
  console.log(`Deleted command file: ${filePath}`);
@@ -41405,7 +41522,6 @@ function toSessionInfo(session) {
41405
41522
  autoRun,
41406
41523
  autoAdvance,
41407
41524
  starred,
41408
- watcher,
41409
41525
  design,
41410
41526
  verifying,
41411
41527
  pendingPrPreview,
@@ -41432,7 +41548,6 @@ function toSessionInfo(session) {
41432
41548
  autoRun,
41433
41549
  autoAdvance,
41434
41550
  starred,
41435
- watcher,
41436
41551
  design,
41437
41552
  verifying,
41438
41553
  pendingPrPreview,
@@ -41988,6 +42103,376 @@ function createRunSession(id, { runName, runArgs, cwd, meta }) {
41988
42103
  };
41989
42104
  }
41990
42105
 
42106
+ // src/commands/sessions/daemon/autoUpdate/autoUpdateEnabled.ts
42107
+ function autoUpdateEnabled(installDir) {
42108
+ try {
42109
+ return loadConfigFrom(installDir).autoUpdate?.enabled ?? true;
42110
+ } catch (error) {
42111
+ daemonLog(
42112
+ `auto-update: config failed to load, keeping the default (on): ${error instanceof Error ? error.message : String(error)}`
42113
+ );
42114
+ return true;
42115
+ }
42116
+ }
42117
+
42118
+ // src/commands/sessions/daemon/autoUpdate/autoUpdateState.ts
42119
+ var loop = {
42120
+ phase: "off",
42121
+ since: (/* @__PURE__ */ new Date()).toISOString(),
42122
+ reason: "not started"
42123
+ };
42124
+ var startCommit;
42125
+ var autoUpdateState = {
42126
+ loop: () => loop,
42127
+ startCommit: () => startCommit,
42128
+ setStartCommit: (commit2) => {
42129
+ startCommit = commit2;
42130
+ },
42131
+ enter: (phase, extra = {}) => {
42132
+ loop = { phase, since: (/* @__PURE__ */ new Date()).toISOString(), ...extra };
42133
+ }
42134
+ };
42135
+
42136
+ // src/commands/sessions/daemon/autoUpdate/holdForEscalation.ts
42137
+ var ESCALATION_POLL_MS = 5e3;
42138
+ async function holdForEscalation(sessionId, deps2) {
42139
+ deps2.enter("diverged", { escalationId: sessionId });
42140
+ deps2.note(`paused while session ${sessionId} reconciles the divergence`);
42141
+ while (deps2.isLive(sessionId)) await deps2.sleep(ESCALATION_POLL_MS);
42142
+ deps2.note(`session ${sessionId} ended; resuming`);
42143
+ }
42144
+
42145
+ // src/commands/sessions/daemon/autoUpdate/runLapCapturingTail.ts
42146
+ var OUTPUT_TAIL = 16 * 1024;
42147
+ async function runLapCapturingTail(deps2) {
42148
+ let tail = "";
42149
+ const end = await deps2.runLap((text17) => {
42150
+ tail = (tail + text17).slice(-OUTPUT_TAIL);
42151
+ deps2.record(text17);
42152
+ });
42153
+ return { end, tail };
42154
+ }
42155
+
42156
+ // src/commands/sessions/daemon/autoUpdate/runAutoUpdateLoop.ts
42157
+ var DIVERGED_EXIT_CODE = 3;
42158
+ var RETRY_AFTER_FAILURE_MS = 5 * 60 * 1e3;
42159
+ async function runAutoUpdateLoop(deps2, maxLaps = Number.POSITIVE_INFINITY) {
42160
+ let held = deps2.liveEscalation();
42161
+ for (let lap = 1; lap <= maxLaps; lap++) {
42162
+ if (held) await holdForEscalation(held, deps2);
42163
+ held = void 0;
42164
+ deps2.enter("waiting");
42165
+ deps2.note(
42166
+ `lap ${lap} at ${(/* @__PURE__ */ new Date()).toLocaleString()}: assist ${WATCH_LAP_ARGS.join(" ")}`
42167
+ );
42168
+ let result;
42169
+ try {
42170
+ result = await runLapCapturingTail(deps2);
42171
+ } catch (error) {
42172
+ await backOff(
42173
+ `lap ${lap} could not start (${error instanceof Error ? error.message : String(error)})`,
42174
+ deps2
42175
+ );
42176
+ continue;
42177
+ }
42178
+ const { end, tail } = result;
42179
+ const how = end.signal ? `killed by ${end.signal}` : `exited ${end.code}`;
42180
+ if (end.code === DIVERGED_EXIT_CODE) {
42181
+ held = deps2.escalate(tail);
42182
+ deps2.note(`lap ${lap} ${how} (divergence); escalated to session ${held}`);
42183
+ continue;
42184
+ }
42185
+ if (decideLap(end, false).kind === "relaunch") {
42186
+ deps2.note(`lap ${lap} ${how}; relaunching`);
42187
+ continue;
42188
+ }
42189
+ await backOff(`lap ${lap} ${how}`, deps2);
42190
+ }
42191
+ }
42192
+ async function backOff(reason4, deps2) {
42193
+ deps2.enter("retrying", { reason: reason4 });
42194
+ deps2.note(`${reason4}; retrying in ${RETRY_AFTER_FAILURE_MS / 6e4}m`);
42195
+ await deps2.sleep(RETRY_AFTER_FAILURE_MS);
42196
+ }
42197
+
42198
+ // src/commands/sessions/daemon/autoUpdate/trimWatcherLog.ts
42199
+ import { readFileSync as readFileSync65, writeFileSync as writeFileSync54 } from "fs";
42200
+ var KEEP_TAIL = 256 * 1024;
42201
+ var MAX_LOG_SIZE = 4 * 1024 * 1024;
42202
+ function trimWatcherLog(cwd) {
42203
+ const path106 = watcherLogPath(cwd);
42204
+ let log2;
42205
+ try {
42206
+ log2 = readFileSync65(path106, "utf8");
42207
+ } catch {
42208
+ return;
42209
+ }
42210
+ if (log2.length <= MAX_LOG_SIZE) return;
42211
+ writeFileSync54(path106, log2.slice(-KEEP_TAIL));
42212
+ daemonLog(`auto-update: log ${path106} trimmed to its last ${KEEP_TAIL} chars`);
42213
+ }
42214
+
42215
+ // src/commands/sessions/daemon/autoUpdate/loopDeps.ts
42216
+ import { setTimeout as sleep2 } from "timers/promises";
42217
+
42218
+ // src/commands/sessions/daemon/watcherNote.ts
42219
+ function watcherNote(text17) {
42220
+ return `\r
42221
+ \x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
42222
+ `;
42223
+ }
42224
+
42225
+ // src/commands/sessions/daemon/worktree/appendWatcherLog.ts
42226
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
42227
+ import { dirname as dirname42 } from "path";
42228
+ function appendWatcherLog(cwd, data) {
42229
+ try {
42230
+ const path106 = watcherLogPath(cwd);
42231
+ mkdirSync34(dirname42(path106), { recursive: true });
42232
+ appendFileSync3(path106, data);
42233
+ } catch {
42234
+ }
42235
+ }
42236
+
42237
+ // src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
42238
+ function detectExistingWorktree(cwd) {
42239
+ const repoRoot2 = findRepoRoot(cwd) ?? cwd;
42240
+ if (!worktreeConfigFor(repoRoot2).enabled) return void 0;
42241
+ const clone = mainWorktree(repoRoot2);
42242
+ if (clone && clone !== repoRoot2) return { path: repoRoot2, clone };
42243
+ return void 0;
42244
+ }
42245
+
42246
+ // src/commands/sessions/daemon/worktree/bindNewWorktree.ts
42247
+ function bindNewWorktree(session, alloc, notify2, startHeld = () => {
42248
+ }) {
42249
+ if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
42250
+ return;
42251
+ session.worktree = { path: alloc.cwd, clone: alloc.clone };
42252
+ if (alloc.created)
42253
+ seedWorktree(alloc.cwd, alloc.clone, () => startHeld(session));
42254
+ notify2();
42255
+ }
42256
+ function bindRestoredWorktrees(sessions) {
42257
+ for (const session of sessions.values()) {
42258
+ if (session.worktree || !session.cwd) continue;
42259
+ const worktree = detectExistingWorktree(session.cwd);
42260
+ if (worktree) session.worktree = worktree;
42261
+ }
42262
+ }
42263
+ function bindResumedWorktree(session, cwd, notify2) {
42264
+ if (!session) return;
42265
+ const worktree = detectExistingWorktree(cwd);
42266
+ if (!worktree) return;
42267
+ session.worktree = worktree;
42268
+ notify2();
42269
+ }
42270
+
42271
+ // src/commands/sessions/daemon/worktree/boundTreeRoots.ts
42272
+ function boundTreeRoots(sessions, exclude) {
42273
+ const roots = /* @__PURE__ */ new Set();
42274
+ for (const session of sessions.values()) {
42275
+ if (session === exclude) continue;
42276
+ const root2 = treeRootOf(session);
42277
+ if (root2 && !roots.has(root2) && holdsTree(session, root2)) roots.add(root2);
42278
+ }
42279
+ return roots;
42280
+ }
42281
+ function treeRootOf(session) {
42282
+ if (!session.cwd) return void 0;
42283
+ return canonicalTreePath(findRepoRoot(session.cwd) ?? session.cwd);
42284
+ }
42285
+ function holdsTree(session, root2) {
42286
+ if (session.closing) return true;
42287
+ if (session.status !== "done" && session.status !== "error") return true;
42288
+ if (!worktreeConfigFor(root2).enabled) return true;
42289
+ const durability = checkDurabilitySync(root2);
42290
+ daemonLog(`tree ${root2} ${releaseReason(durability, session)}`);
42291
+ return !durability.durable;
42292
+ }
42293
+ function releaseReason(durability, session) {
42294
+ if (!durability.durable)
42295
+ return `still held by finished session ${session.id}: ${durability.reason}`;
42296
+ if (durability.gone)
42297
+ return `free for allocation: session ${session.id}'s directory is gone from disk \u2014 nothing to land, not landed work`;
42298
+ return `free for allocation: session ${session.id} finished and its work is landed`;
42299
+ }
42300
+
42301
+ // src/commands/sessions/daemon/worktree/allocateAndBind.ts
42302
+ function allocateAndBind(ctx, cwd, create, options2 = {}, context) {
42303
+ const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions), options2);
42304
+ const needsSeeding = alloc.kind === "worktree" && alloc.created === true;
42305
+ const id = ctx.spawnWith(
42306
+ (sid) => create(sid, alloc.cwd, needsSeeding),
42307
+ context
42308
+ );
42309
+ bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify, ctx.startHeld);
42310
+ return id;
42311
+ }
42312
+
42313
+ // src/commands/sessions/daemon/worktree/divergencePrompt.ts
42314
+ var MAX_REASON_LINES = 20;
42315
+ var MAX_REASON_CHARS = 2e3;
42316
+ var RECONCILE_STEPS = [
42317
+ "Reconcile it yourself, without discarding any commits on either side:",
42318
+ "",
42319
+ "1. Inspect `git status`, `git log --oneline @{u}..@` and `git log --oneline @..@{u}`.",
42320
+ '2. Stash any uncommitted work with `git stash push -u -m "assist divergence"`, and restore it with `git stash pop` once the branch is reconciled.',
42321
+ "3. Rebase the local commits onto the upstream with `git rebase @{u}`, resolving conflicts so both sides' changes survive. If the rebase cannot be resolved cleanly, `git rebase --abort` and `git merge @{u}` instead.",
42322
+ "4. Push the result with a plain `git push` so the branch matches its upstream again.",
42323
+ "5. Build the clone with `assist run auto-build`.",
42324
+ "6. Run /close. The daemon resumes its auto-update loop once this session closes.",
42325
+ "",
42326
+ "Never force-push, `git reset`, or otherwise discard commits. If you cannot reconcile the branch without doing so, explain what is blocking it and leave this session open for the user instead of closing it."
42327
+ ];
42328
+ var SIMULATED_STEPS = [
42329
+ "This divergence was simulated to test the escalation; there is nothing to reconcile. Run /close now. The daemon resumes its auto-update loop once this session closes."
42330
+ ];
42331
+ function divergenceReason(scrollback) {
42332
+ return stripAnsi(scrollback).split(/\r?\n|\r/).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-MAX_REASON_LINES).join("\n").slice(-MAX_REASON_CHARS);
42333
+ }
42334
+ function divergencePrompt(clone, scrollback) {
42335
+ const reason4 = divergenceReason(scrollback);
42336
+ return [
42337
+ `The daemon's auto-update loop for the assist install in ${clone} paused because \`assist watch wait --pull --build\` could not fast-forward the branch (exit 3). The tail of its output:`,
42338
+ "",
42339
+ "```",
42340
+ reason4 || "(no output captured)",
42341
+ "```",
42342
+ "",
42343
+ ...reason4.includes(simulatedDivergenceNote) ? SIMULATED_STEPS : RECONCILE_STEPS
42344
+ ].join("\n");
42345
+ }
42346
+
42347
+ // src/commands/sessions/daemon/worktree/liveEscalationIn.ts
42348
+ var ENDED = ["done", "error"];
42349
+ function liveEscalationIn(sessions, clone) {
42350
+ return [...sessions.values()].find(
42351
+ (s) => s.divergenceEscalation === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone && !ENDED.includes(s.status)
42352
+ );
42353
+ }
42354
+
42355
+ // src/commands/sessions/daemon/worktree/escalateDivergence.ts
42356
+ function escalateDivergence(ctx, cwd, output) {
42357
+ const clone = canonicalTreePath(cwd);
42358
+ const live = liveEscalationIn(ctx.sessions, clone);
42359
+ if (live) {
42360
+ daemonLog(
42361
+ `auto-update diverged in the clone ${clone}: no escalation spawned, session ${live.id} is already diagnosing it (${live.status})`
42362
+ );
42363
+ return live.id;
42364
+ }
42365
+ const prompt = divergencePrompt(clone, output);
42366
+ const id = allocateAndBind(
42367
+ ctx,
42368
+ clone,
42369
+ (sid, resolvedCwd) => ({
42370
+ ...createSession(sid, { prompt, cwd: resolvedCwd ?? clone }),
42371
+ divergenceEscalation: true
42372
+ }),
42373
+ { inPlace: true }
42374
+ );
42375
+ daemonLog(
42376
+ `auto-update diverged in the clone ${clone}: spawned escalation session ${id} to diagnose it`
42377
+ );
42378
+ return id;
42379
+ }
42380
+
42381
+ // src/commands/sessions/daemon/autoUpdate/lineRecorder.ts
42382
+ function lineRecorder(onLine) {
42383
+ let pending = "";
42384
+ return (text17) => {
42385
+ const lines2 = (pending + text17).split(/\r?\n|\r/);
42386
+ pending = lines2.pop() ?? "";
42387
+ for (const line of lines2) {
42388
+ const clean2 = stripAnsi(line).trimEnd();
42389
+ if (clean2.trim()) onLine(clean2);
42390
+ }
42391
+ };
42392
+ }
42393
+
42394
+ // src/commands/sessions/daemon/autoUpdate/runUpdateLap.ts
42395
+ import { spawn as spawn11 } from "child_process";
42396
+ function runUpdateLap(cwd, onOutput) {
42397
+ return new Promise((resolve28, reject) => {
42398
+ const { ASSIST_ACTIVITY_ID: _activity, ...env } = process.env;
42399
+ const child = spawn11(
42400
+ process.execPath,
42401
+ [process.argv[1], ...WATCH_LAP_ARGS],
42402
+ {
42403
+ cwd,
42404
+ env,
42405
+ stdio: ["ignore", "pipe", "pipe"],
42406
+ windowsHide: true
42407
+ }
42408
+ );
42409
+ const killWithDaemon = () => {
42410
+ child.kill();
42411
+ };
42412
+ process.on("exit", killWithDaemon);
42413
+ child.stdout.on("data", (chunk) => onOutput(chunk.toString()));
42414
+ child.stderr.on("data", (chunk) => onOutput(chunk.toString()));
42415
+ child.on("close", (code, signal) => {
42416
+ process.off("exit", killWithDaemon);
42417
+ resolve28({ code, signal });
42418
+ });
42419
+ child.on("error", (error) => {
42420
+ process.off("exit", killWithDaemon);
42421
+ reject(error);
42422
+ });
42423
+ });
42424
+ }
42425
+
42426
+ // src/commands/sessions/daemon/autoUpdate/loopDeps.ts
42427
+ function loopDeps(installDir, ctx) {
42428
+ const clone = canonicalTreePath(installDir);
42429
+ const logLine = lineRecorder((line) => daemonLog(`auto-update: ${line}`));
42430
+ return {
42431
+ runLap: (onOutput) => runUpdateLap(installDir, onOutput),
42432
+ record: (text17) => {
42433
+ appendWatcherLog(installDir, text17);
42434
+ logLine(text17);
42435
+ },
42436
+ note: (text17) => {
42437
+ appendWatcherLog(installDir, watcherNote(text17));
42438
+ daemonLog(`auto-update: ${text17}`);
42439
+ },
42440
+ enter: autoUpdateState.enter,
42441
+ liveEscalation: () => liveEscalationIn(ctx().sessions, clone)?.id,
42442
+ escalate: (output) => escalateDivergence(ctx(), installDir, output),
42443
+ isLive: (sessionId) => {
42444
+ const status2 = ctx().sessions.get(sessionId)?.status;
42445
+ return status2 !== void 0 && status2 !== "done" && status2 !== "error";
42446
+ },
42447
+ sleep: (ms) => sleep2(ms)
42448
+ };
42449
+ }
42450
+
42451
+ // src/commands/sessions/daemon/autoUpdate/startAutoUpdate.ts
42452
+ function stayOff(reason4) {
42453
+ autoUpdateState.enter("off", { reason: reason4 });
42454
+ daemonLog(`auto-update: off (${reason4})`);
42455
+ }
42456
+ function startAutoUpdate(ctx) {
42457
+ const installDir = getInstallDir();
42458
+ if (!isGitRepo(installDir)) {
42459
+ stayOff(`${installDir} is not a git clone`);
42460
+ return;
42461
+ }
42462
+ autoUpdateState.setStartCommit(headCommit3(installDir));
42463
+ if (!autoUpdateEnabled(installDir)) {
42464
+ stayOff("autoUpdate.enabled is false");
42465
+ return;
42466
+ }
42467
+ trimWatcherLog(installDir);
42468
+ daemonLog(`auto-update: pulling and building ${installDir} as origin moves`);
42469
+ void runAutoUpdateLoop(loopDeps(installDir, ctx)).catch(
42470
+ (error) => daemonLog(
42471
+ `auto-update: loop stopped: ${error instanceof Error ? error.message : String(error)}`
42472
+ )
42473
+ );
42474
+ }
42475
+
41991
42476
  // src/commands/sessions/daemon/setStatus.ts
41992
42477
  function setStatus2(session, newStatus) {
41993
42478
  const now = Date.now();
@@ -42010,16 +42495,16 @@ function setStatus2(session, newStatus) {
42010
42495
 
42011
42496
  // src/commands/sessions/daemon/worktree/reapWorktree.ts
42012
42497
  import { existsSync as existsSync87 } from "fs";
42013
- import { basename as basename28 } from "path";
42498
+ import { basename as basename29 } from "path";
42014
42499
 
42015
42500
  // src/commands/sessions/daemon/worktree/deleteStrandedTree.ts
42016
42501
  import { existsSync as existsSync86 } from "fs";
42017
- import { join as join102 } from "path";
42502
+ import { join as join103 } from "path";
42018
42503
 
42019
42504
  // src/commands/sessions/daemon/worktree/deleteTreeDirectly.ts
42020
42505
  import { statSync as statSync15 } from "fs";
42021
42506
  import { rm as rm3 } from "fs/promises";
42022
- import { join as join101 } from "path";
42507
+ import { join as join102 } from "path";
42023
42508
  async function deleteTreeDirectly(clone, worktreePath, why) {
42024
42509
  if (holdsAGitDirectoryRatherThanALink(worktreePath)) {
42025
42510
  const refusal = "it is a clone of its own, not a linked worktree";
@@ -42047,7 +42532,7 @@ async function deleteTreeDirectly(clone, worktreePath, why) {
42047
42532
  return { removed: true };
42048
42533
  }
42049
42534
  function holdsAGitDirectoryRatherThanALink(worktreePath) {
42050
- return statSync15(join101(worktreePath, ".git"), {
42535
+ return statSync15(join102(worktreePath, ".git"), {
42051
42536
  throwIfNoEntry: false
42052
42537
  })?.isDirectory() === true;
42053
42538
  }
@@ -42083,7 +42568,7 @@ async function deleteStrandedTree(clone, worktreePath, cause) {
42083
42568
  );
42084
42569
  }
42085
42570
  function strandedReason(worktreePath, cause) {
42086
- if (!existsSync86(join102(worktreePath, ".git")))
42571
+ if (!existsSync86(join103(worktreePath, ".git")))
42087
42572
  return "its .git link is already gone";
42088
42573
  if (/not a working tree|not a git repository/i.test(reason2(cause)))
42089
42574
  return "git no longer recognises it as a working tree";
@@ -42153,7 +42638,7 @@ async function reapWorktree(worktreePath, force = false) {
42153
42638
  const clone = owningClone(worktreePath);
42154
42639
  const removal = await removeTree(clone, worktreePath, force);
42155
42640
  if (!removal.removed) return removal;
42156
- await deleteWorktreeBranch(clone, basename28(worktreePath));
42641
+ await deleteWorktreeBranch(clone, basename29(worktreePath));
42157
42642
  forgetWorktree(worktreePath);
42158
42643
  daemonLog(`worktree ${worktreePath} reaped${force ? " (forced)" : ""}`);
42159
42644
  return removal;
@@ -42227,34 +42712,6 @@ function killPtyTree(pty2) {
42227
42712
  }
42228
42713
  }
42229
42714
 
42230
- // src/commands/sessions/daemon/worktree/liveWatcherFor.ts
42231
- function liveWatcherFor(sessions, clone) {
42232
- for (const session of sessions.values())
42233
- if (session.watcher === true && session.cwd !== void 0 && (clone === void 0 || canonicalTreePath(session.cwd) === clone) && session.status !== "stopped" && session.status !== "error")
42234
- return session;
42235
- return void 0;
42236
- }
42237
-
42238
- // src/commands/sessions/daemon/worktree/resolveClone.ts
42239
- function resolveClone(cwd) {
42240
- const repoRoot2 = findRepoRoot(cwd) ?? cwd;
42241
- return canonicalTreePath(mainWorktree(repoRoot2) ?? repoRoot2);
42242
- }
42243
-
42244
- // src/commands/sessions/daemon/worktree/orphanedWatcherFor.ts
42245
- function orphanedWatcherFor(sessions, dismissed) {
42246
- if (dismissed.watcher === true || !dismissed.cwd) return void 0;
42247
- if (!liveWatcherFor(sessions)) return void 0;
42248
- const known = dismissed.worktree?.clone ?? dismissed.releasedFromClone;
42249
- const clone = known ? canonicalTreePath(known) : resolveClone(dismissed.cwd);
42250
- const watcher = liveWatcherFor(sessions, clone);
42251
- if (!watcher) return void 0;
42252
- const kept = [...sessions.values()].some(
42253
- (s) => s.watcher !== true && s.cwd !== void 0 && resolveClone(s.cwd) === clone
42254
- );
42255
- return kept ? void 0 : { watcher, clone };
42256
- }
42257
-
42258
42715
  // src/commands/sessions/daemon/worktree/treeUnderClose.ts
42259
42716
  function treeUnderClose(session) {
42260
42717
  if (session.worktree) return { path: session.worktree.path, removable: true };
@@ -42285,13 +42742,6 @@ function dismissSession(sessions, id) {
42285
42742
  if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
42286
42743
  sessions.delete(id);
42287
42744
  daemonLog(`session ${id} dismissed (${s.name})`);
42288
- const orphan = orphanedWatcherFor(sessions, s);
42289
- if (orphan) {
42290
- daemonLog(
42291
- `reaping watcher session ${orphan.watcher.id} for the clone ${orphan.clone}: its last session ${id} was dismissed`
42292
- );
42293
- dismissSession(sessions, orphan.watcher.id);
42294
- }
42295
42745
  if (!s.worktree) return true;
42296
42746
  if (sharedWith.length > 0)
42297
42747
  daemonLog(
@@ -42526,43 +42976,6 @@ function dismissSessionGated(sessions, id, notify2, discard = false) {
42526
42976
  settle();
42527
42977
  }
42528
42978
 
42529
- // src/commands/sessions/daemon/worktree/resumeWatcherAfterEscalation.ts
42530
- var ENDED = ["done", "error"];
42531
- var endedViaDismissOrExit = /* @__PURE__ */ new WeakSet();
42532
- function resumeWatcherAfterEscalation(sessions, escalation, restart) {
42533
- if (escalation.divergenceEscalation !== true || !escalation.cwd) return;
42534
- if (endedViaDismissOrExit.has(escalation)) return;
42535
- endedViaDismissOrExit.add(escalation);
42536
- const clone = canonicalTreePath(escalation.cwd);
42537
- const watcher = [...sessions.values()].find(
42538
- (s) => s.watcher === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone
42539
- );
42540
- if (!watcher) {
42541
- daemonLog(
42542
- `escalation session ${escalation.id} ended: no watcher left in the clone ${clone} to restart`
42543
- );
42544
- return;
42545
- }
42546
- if (!ENDED.includes(watcher.status)) {
42547
- daemonLog(
42548
- `escalation session ${escalation.id} ended: watcher session ${watcher.id} in the clone ${clone} is already ${watcher.status}`
42549
- );
42550
- return;
42551
- }
42552
- daemonLog(
42553
- `escalation session ${escalation.id} ended: restarting watcher session ${watcher.id} in the clone ${clone}`
42554
- );
42555
- watcher.watcherRestartReason = `after escalation session ${escalation.id} ended`;
42556
- restart(watcher.id);
42557
- }
42558
-
42559
- // src/commands/sessions/daemon/dismissAndResumeWatcher.ts
42560
- function dismissAndResumeWatcher(sessions, id, notify2, restart) {
42561
- const session = sessions.get(id);
42562
- dismissSessionGated(sessions, id, notify2);
42563
- if (session) resumeWatcherAfterEscalation(sessions, session, restart);
42564
- }
42565
-
42566
42979
  // src/commands/sessions/daemon/drainSessions.ts
42567
42980
  function drainSessions(sessions, notify2) {
42568
42981
  const drained = [...sessions.values()].map((s) => ({
@@ -42976,37 +43389,10 @@ function startSessionTitleGeneration(session, notify2) {
42976
43389
  runSessionTitleGeneration(session, prompt, notify2, singleLineTitle(prompt));
42977
43390
  }
42978
43391
 
42979
- // src/commands/sessions/daemon/worktree/appendWatcherLog.ts
42980
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
42981
- import { dirname as dirname42 } from "path";
42982
-
42983
- // src/commands/sessions/daemon/worktree/watcherLogPath.ts
42984
- import { createHash as createHash4 } from "crypto";
42985
- import { homedir as homedir26 } from "os";
42986
- import { basename as basename29, join as join103 } from "path";
42987
- function watcherLogPath(cwd) {
42988
- const clone = canonicalTreePath(cwd);
42989
- const key = createHash4("sha1").update(clone).digest("hex").slice(0, 8);
42990
- const dir = process.env.ASSIST_WATCHER_LOG_DIR ?? join103(homedir26(), ".assist", "watchers");
42991
- return join103(dir, `${basename29(clone) || "root"}-${key}.log`);
42992
- }
42993
-
42994
- // src/commands/sessions/daemon/worktree/appendWatcherLog.ts
42995
- function appendWatcherLog(session, data) {
42996
- if (session.watcher !== true || !session.cwd) return;
42997
- try {
42998
- const path106 = watcherLogPath(session.cwd);
42999
- mkdirSync34(dirname42(path106), { recursive: true });
43000
- appendFileSync3(path106, data);
43001
- } catch {
43002
- }
43003
- }
43004
-
43005
43392
  // src/commands/sessions/daemon/emitSessionOutput.ts
43006
43393
  var MAX_SCROLLBACK = 256 * 1024;
43007
43394
  function emitSessionOutput(session, clients, data) {
43008
43395
  session.scrollback += data;
43009
- appendWatcherLog(session, data);
43010
43396
  if (session.scrollback.length > MAX_SCROLLBACK) {
43011
43397
  session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
43012
43398
  }
@@ -44077,13 +44463,6 @@ function applyUsageRecord(sessions, windows, notify2, claudeSessionId, transcrip
44077
44463
  notify2();
44078
44464
  }
44079
44465
 
44080
- // src/commands/sessions/daemon/watcherNote.ts
44081
- function watcherNote(text17) {
44082
- return `\r
44083
- \x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
44084
- `;
44085
- }
44086
-
44087
44466
  // src/commands/sessions/daemon/shouldAutoRun.ts
44088
44467
  function shouldAutoRun(session, status2 = session.status) {
44089
44468
  if (!session.autoRun) return { run: false, reason: null };
@@ -44138,7 +44517,6 @@ function shouldAutoDismiss(session, exitCode) {
44138
44517
  }
44139
44518
 
44140
44519
  // src/commands/sessions/daemon/finishStatusChange.ts
44141
- var DIVERGED_EXIT_CODE = 3;
44142
44520
  function finishStatusChange(session, status2, exitCode, deps2) {
44143
44521
  daemonLog(`session ${session.id} status: ${session.status} -> ${status2}`);
44144
44522
  void flushPhaseActiveMs(session);
@@ -44146,9 +44524,6 @@ function finishStatusChange(session, status2, exitCode, deps2) {
44146
44524
  if (status2 === "waiting")
44147
44525
  startTranscriptTitleGeneration(session, deps2.notify);
44148
44526
  if (status2 !== "stopped") session.undurable = void 0;
44149
- if (session.watcher === true && exitCode === DIVERGED_EXIT_CODE)
44150
- deps2.escalateDivergence?.(session);
44151
- if (status2 === "done" || status2 === "error") deps2.resumeWatcher?.(session);
44152
44527
  const autoRun = shouldAutoRun(session);
44153
44528
  if (autoRun.run) {
44154
44529
  deps2.reuseForRun(session, autoRun.itemId);
@@ -44194,40 +44569,6 @@ function makeStatusChangeHandler(sessions, deps2) {
44194
44569
  );
44195
44570
  }
44196
44571
 
44197
- // src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
44198
- function detectExistingWorktree(cwd) {
44199
- const repoRoot2 = findRepoRoot(cwd) ?? cwd;
44200
- if (!worktreeConfigFor(repoRoot2).enabled) return void 0;
44201
- const clone = mainWorktree(repoRoot2);
44202
- if (clone && clone !== repoRoot2) return { path: repoRoot2, clone };
44203
- return void 0;
44204
- }
44205
-
44206
- // src/commands/sessions/daemon/worktree/bindNewWorktree.ts
44207
- function bindNewWorktree(session, alloc, notify2, startHeld = () => {
44208
- }) {
44209
- if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
44210
- return;
44211
- session.worktree = { path: alloc.cwd, clone: alloc.clone };
44212
- if (alloc.created)
44213
- seedWorktree(alloc.cwd, alloc.clone, () => startHeld(session));
44214
- notify2();
44215
- }
44216
- function bindRestoredWorktrees(sessions) {
44217
- for (const session of sessions.values()) {
44218
- if (session.worktree || !session.cwd) continue;
44219
- const worktree = detectExistingWorktree(session.cwd);
44220
- if (worktree) session.worktree = worktree;
44221
- }
44222
- }
44223
- function bindResumedWorktree(session, cwd, notify2) {
44224
- if (!session) return;
44225
- const worktree = detectExistingWorktree(cwd);
44226
- if (!worktree) return;
44227
- session.worktree = worktree;
44228
- notify2();
44229
- }
44230
-
44231
44572
  // src/commands/sessions/daemon/attachReusedRun.ts
44232
44573
  function attachReusedRun(session, alloc, clients, onStatusChange, tree) {
44233
44574
  broadcast(clients, { type: "clear", sessionId: session.id });
@@ -44306,137 +44647,6 @@ function startReusedRunPty(session, assistArgs, itemId2, hold, clients, onStatus
44306
44647
  return true;
44307
44648
  }
44308
44649
 
44309
- // src/commands/sessions/daemon/createAssistSession.ts
44310
- function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
44311
- const startedAt = Date.now();
44312
- return {
44313
- id,
44314
- name: `assist ${assistArgs.join(" ")}`,
44315
- title: meta?.title,
44316
- subtitle: meta?.subtitle,
44317
- commandType: "assist",
44318
- status: "running",
44319
- startedAt,
44320
- runningMs: 0,
44321
- runningSince: startedAt,
44322
- waitingSince: null,
44323
- ...startOrHoldPty(
44324
- () => spawnPty(["assist", ...assistArgs], cwd, id),
44325
- holdPty
44326
- ),
44327
- scrollback: "",
44328
- assistArgs,
44329
- cwd
44330
- };
44331
- }
44332
-
44333
- // src/commands/sessions/daemon/worktree/seedWatcherScrollback.ts
44334
- import { readFileSync as readFileSync65, writeFileSync as writeFileSync54 } from "fs";
44335
- var SCROLLBACK_TAIL = 256 * 1024;
44336
- var MAX_LOG_SIZE = 4 * 1024 * 1024;
44337
- function readLogTail2(path106) {
44338
- let log2;
44339
- try {
44340
- log2 = readFileSync65(path106, "utf8");
44341
- } catch {
44342
- return "";
44343
- }
44344
- if (log2.length > MAX_LOG_SIZE) {
44345
- log2 = log2.slice(-SCROLLBACK_TAIL);
44346
- writeFileSync54(path106, log2);
44347
- daemonLog(`watcher log ${path106} trimmed to its last ${log2.length} chars`);
44348
- }
44349
- return log2.slice(-SCROLLBACK_TAIL);
44350
- }
44351
- function seedWatcherScrollback(cwd, event) {
44352
- const path106 = watcherLogPath(cwd);
44353
- const history = readLogTail2(path106);
44354
- const note = watcherNote(`${event} at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
44355
- appendWatcherLog({ watcher: true, cwd }, note);
44356
- daemonLog(
44357
- `watcher in ${cwd} seeded with ${history.length} chars of history from ${path106} (${event})`
44358
- );
44359
- return history + note;
44360
- }
44361
-
44362
- // src/commands/sessions/daemon/createWatcherSession.ts
44363
- var WATCHER_ARGS = ["watch", "loop"];
44364
- function createWatcherSession(id, cwd) {
44365
- return {
44366
- ...createAssistSession(id, WATCHER_ARGS, cwd),
44367
- watcher: true,
44368
- scrollback: seedWatcherScrollback(cwd, "watcher started")
44369
- };
44370
- }
44371
-
44372
- // src/commands/sessions/daemon/worktree/boundTreeRoots.ts
44373
- function boundTreeRoots(sessions, exclude) {
44374
- const roots = /* @__PURE__ */ new Set();
44375
- for (const session of sessions.values()) {
44376
- if (session === exclude) continue;
44377
- const root2 = treeRootOf(session);
44378
- if (root2 && !roots.has(root2) && holdsTree(session, root2)) roots.add(root2);
44379
- }
44380
- return roots;
44381
- }
44382
- function treeRootOf(session) {
44383
- if (!session.cwd) return void 0;
44384
- return canonicalTreePath(findRepoRoot(session.cwd) ?? session.cwd);
44385
- }
44386
- function holdsTree(session, root2) {
44387
- if (session.closing) return true;
44388
- if (session.status !== "done" && session.status !== "error") return true;
44389
- if (!worktreeConfigFor(root2).enabled) return true;
44390
- const durability = checkDurabilitySync(root2);
44391
- daemonLog(`tree ${root2} ${releaseReason(durability, session)}`);
44392
- return !durability.durable;
44393
- }
44394
- function releaseReason(durability, session) {
44395
- if (!durability.durable)
44396
- return `still held by finished session ${session.id}: ${durability.reason}`;
44397
- if (durability.gone)
44398
- return `free for allocation: session ${session.id}'s directory is gone from disk \u2014 nothing to land, not landed work`;
44399
- return `free for allocation: session ${session.id} finished and its work is landed`;
44400
- }
44401
-
44402
- // src/commands/sessions/daemon/worktree/allocateAndBind.ts
44403
- function allocateAndBind(ctx, cwd, create, options2 = {}, context) {
44404
- const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions), options2);
44405
- const needsSeeding = alloc.kind === "worktree" && alloc.created === true;
44406
- const id = ctx.spawnWith(
44407
- (sid) => create(sid, alloc.cwd, needsSeeding),
44408
- context
44409
- );
44410
- bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify, ctx.startHeld);
44411
- return id;
44412
- }
44413
-
44414
- // src/commands/sessions/daemon/worktree/ensureWatcher.ts
44415
- function ensureWatcher(ctx, cwd) {
44416
- if (!cwd) return void 0;
44417
- const repoRoot2 = findRepoRoot(cwd) ?? cwd;
44418
- const cfg = worktreeConfigFor(repoRoot2);
44419
- if (!cfg.enabled || cfg.watcher !== true) return void 0;
44420
- const clone = resolveClone(repoRoot2);
44421
- const live = liveWatcherFor(ctx.sessions, clone);
44422
- if (live) {
44423
- daemonLog(
44424
- `no watcher spawned for the clone ${clone}: session ${live.id} is already watching it (${live.status})`
44425
- );
44426
- return void 0;
44427
- }
44428
- const id = allocateAndBind(
44429
- ctx,
44430
- clone,
44431
- (sid, resolvedCwd) => createWatcherSession(sid, resolvedCwd ?? clone),
44432
- { inPlace: true }
44433
- );
44434
- daemonLog(
44435
- `spawned watcher session ${id} running assist ${WATCHER_ARGS.join(" ")} in the clone ${clone}`
44436
- );
44437
- return id;
44438
- }
44439
-
44440
44650
  // src/commands/sessions/daemon/worktree/isBacklogRunArgs.ts
44441
44651
  var ITEM_ID = /^a?\d+$/;
44442
44652
  function isBacklogRunArgs(args) {
@@ -44500,7 +44710,6 @@ function failChainedRun(session, itemId2, error, clients, tree) {
44500
44710
  // src/commands/sessions/daemon/reuseSessionForRun.ts
44501
44711
  function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
44502
44712
  const assistArgs = ["backlog", "run", String(itemId2)];
44503
- const originCwd = session.cwd;
44504
44713
  resetCardForRun(session, assistArgs);
44505
44714
  let alloc;
44506
44715
  try {
@@ -44522,13 +44731,71 @@ function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
44522
44731
  onStatusChange
44523
44732
  );
44524
44733
  if (!started) return;
44525
- if (tree) ensureWatcher(tree, originCwd);
44526
44734
  attachReusedRun(session, alloc, clients, onStatusChange, tree);
44527
44735
  daemonLog(
44528
44736
  `session ${session.id} reused for backlog run ${itemId2}: ${session.name}`
44529
44737
  );
44530
44738
  }
44531
44739
 
44740
+ // src/commands/sessions/daemon/managerStatusChangeHandler.ts
44741
+ function managerStatusChangeHandler(sessions, clients, dismiss, notify2, treeCtx) {
44742
+ const handler = makeStatusChangeHandler(sessions, {
44743
+ dismiss,
44744
+ notify: notify2,
44745
+ reuseForRun: (session, itemId2) => reuseSessionForRun(session, itemId2, clients, handler, treeCtx())
44746
+ });
44747
+ return handler;
44748
+ }
44749
+
44750
+ // src/commands/sessions/daemon/applySetStatus.ts
44751
+ function applySetStatus(sessions, id, status2, source, onStatusChange, rebind) {
44752
+ const session = sessions.get(id);
44753
+ if (!session) {
44754
+ daemonLog(
44755
+ `set-status for unknown session id=${id} status=${status2} (no live session; ignoring)`
44756
+ );
44757
+ return;
44758
+ }
44759
+ if (session.status === "stopped") {
44760
+ daemonLog(
44761
+ `set-status for stopped session id=${id} status=${status2} (held pending durable close; ignoring)`
44762
+ );
44763
+ return;
44764
+ }
44765
+ rebind?.(session);
44766
+ if (source === "permission") session.permissionActive = true;
44767
+ if (status2 === "running") session.permissionActive = false;
44768
+ onStatusChange(session, status2);
44769
+ }
44770
+
44771
+ // src/commands/sessions/daemon/rebindClaudeSession.ts
44772
+ function rebindClaudeSession(session, claudeSessionId, notify2, onStatusChange) {
44773
+ if (!claudeSessionId) return;
44774
+ if (session.claudeSessionId === claudeSessionId) return;
44775
+ daemonLog(
44776
+ `session ${session.id} claude session moved: ${session.claudeSessionId ?? "none"} -> ${claudeSessionId}`
44777
+ );
44778
+ session.claudeSessionId = claudeSessionId;
44779
+ watchTranscript(session, notify2, onStatusChange);
44780
+ }
44781
+
44782
+ // src/commands/sessions/daemon/setStatusFromHook.ts
44783
+ function setStatusFromHook(sessions, report, notify2, onStatusChange) {
44784
+ applySetStatus(
44785
+ sessions,
44786
+ report.id,
44787
+ report.status,
44788
+ report.source,
44789
+ onStatusChange,
44790
+ (session) => rebindClaudeSession(
44791
+ session,
44792
+ report.claudeSessionId,
44793
+ notify2,
44794
+ onStatusChange
44795
+ )
44796
+ );
44797
+ }
44798
+
44532
44799
  // src/commands/sessions/daemon/respawnSession.ts
44533
44800
  function respawnSession(session, respawn, status2, clients, onStatusChange) {
44534
44801
  session.gitWatcher?.close();
@@ -44537,8 +44804,7 @@ function respawnSession(session, respawn, status2, clients, onStatusChange) {
44537
44804
  session.pendingDismiss = void 0;
44538
44805
  session.closeGrace?.cancel();
44539
44806
  session.closing = void 0;
44540
- const keepHistory = session.watcher === true;
44541
- if (!keepHistory) session.scrollback = "";
44807
+ session.scrollback = "";
44542
44808
  session.startedAt = Date.now();
44543
44809
  session.runningMs = 0;
44544
44810
  session.runningSince = null;
@@ -44555,15 +44821,7 @@ function respawnSession(session, respawn, status2, clients, onStatusChange) {
44555
44821
  session.pty?.resize(session.cols, session.rows);
44556
44822
  } catch {
44557
44823
  }
44558
- if (keepHistory) {
44559
- const reason4 = session.watcherRestartReason;
44560
- session.watcherRestartReason = void 0;
44561
- emitSessionOutput(
44562
- session,
44563
- clients,
44564
- watcherNote(reason4 ? `watcher restarted ${reason4}` : "watcher restarted")
44565
- );
44566
- } else broadcast(clients, { type: "clear", sessionId: session.id });
44824
+ broadcast(clients, { type: "clear", sessionId: session.id });
44567
44825
  wirePtyEvents(session, clients, onStatusChange);
44568
44826
  }
44569
44827
 
@@ -44734,144 +44992,6 @@ function restartManagedSession(sessions, id, clients, onStatusChange) {
44734
44992
  return { ok: true };
44735
44993
  }
44736
44994
 
44737
- // src/commands/sessions/daemon/worktree/divergencePrompt.ts
44738
- var MAX_REASON_LINES = 20;
44739
- var MAX_REASON_CHARS = 2e3;
44740
- var RECONCILE_STEPS = [
44741
- "Reconcile it yourself, without discarding any commits on either side:",
44742
- "",
44743
- "1. Inspect `git status`, `git log --oneline @{u}..@` and `git log --oneline @..@{u}`.",
44744
- '2. Stash any uncommitted work with `git stash push -u -m "assist divergence"`, and restore it with `git stash pop` once the branch is reconciled.',
44745
- "3. Rebase the local commits onto the upstream with `git rebase @{u}`, resolving conflicts so both sides' changes survive. If the rebase cannot be resolved cleanly, `git rebase --abort` and `git merge @{u}` instead.",
44746
- "4. Push the result with a plain `git push` so the branch matches its upstream again.",
44747
- "5. Build the clone with `assist run auto-build`.",
44748
- "6. Run /close. The daemon restarts the watcher once this session closes.",
44749
- "",
44750
- "Never force-push, `git reset`, or otherwise discard commits. If you cannot reconcile the branch without doing so, explain what is blocking it and leave this session open for the user instead of closing it."
44751
- ];
44752
- var SIMULATED_STEPS = [
44753
- "This divergence was simulated to test the escalation; there is nothing to reconcile. Run /close now. The daemon restarts the watcher once this session closes."
44754
- ];
44755
- function divergenceReason(scrollback) {
44756
- return stripAnsi(scrollback).split(/\r?\n|\r/).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-MAX_REASON_LINES).join("\n").slice(-MAX_REASON_CHARS);
44757
- }
44758
- function divergencePrompt(clone, scrollback) {
44759
- const reason4 = divergenceReason(scrollback);
44760
- return [
44761
- `The watcher in ${clone} stopped because \`assist watch wait --pull --build\` could not fast-forward the branch (exit 3). The tail of its output:`,
44762
- "",
44763
- "```",
44764
- reason4 || "(no output captured)",
44765
- "```",
44766
- "",
44767
- ...reason4.includes(simulatedDivergenceNote) ? SIMULATED_STEPS : RECONCILE_STEPS
44768
- ].join("\n");
44769
- }
44770
-
44771
- // src/commands/sessions/daemon/worktree/escalateDivergence.ts
44772
- var ENDED2 = ["done", "error", "stopped"];
44773
- function escalateDivergence(ctx, watcher) {
44774
- if (!watcher.cwd) return void 0;
44775
- const clone = canonicalTreePath(watcher.cwd);
44776
- const live = [...ctx.sessions.values()].find(
44777
- (s) => s.divergenceEscalation === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone && !ENDED2.includes(s.status)
44778
- );
44779
- if (live) {
44780
- daemonLog(
44781
- `watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: no escalation spawned, session ${live.id} is already diagnosing it (${live.status})`
44782
- );
44783
- return void 0;
44784
- }
44785
- const prompt = divergencePrompt(clone, watcher.scrollback);
44786
- const id = allocateAndBind(
44787
- ctx,
44788
- clone,
44789
- (sid, resolvedCwd) => ({
44790
- ...createSession(sid, { prompt, cwd: resolvedCwd ?? clone }),
44791
- divergenceEscalation: true
44792
- }),
44793
- { inPlace: true }
44794
- );
44795
- daemonLog(
44796
- `watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: spawned escalation session ${id} to diagnose it`
44797
- );
44798
- return id;
44799
- }
44800
-
44801
- // src/commands/sessions/daemon/managerStatusChangeHandler.ts
44802
- function managerStatusChangeHandler(sessions, clients, dismiss, notify2, treeCtx) {
44803
- const handler = makeStatusChangeHandler(sessions, {
44804
- dismiss,
44805
- notify: notify2,
44806
- reuseForRun: (session, itemId2) => reuseSessionForRun(session, itemId2, clients, handler, treeCtx()),
44807
- escalateDivergence: (watcher) => {
44808
- const id = escalateDivergence(treeCtx(), watcher);
44809
- if (id)
44810
- emitSessionOutput(
44811
- watcher,
44812
- clients,
44813
- watcherNote(
44814
- `divergence escalated to session ${id}; the watcher restarts once it ends`
44815
- )
44816
- );
44817
- },
44818
- resumeWatcher: (escalation) => resumeWatcherAfterEscalation(sessions, escalation, (watcherId) => {
44819
- if (restartManagedSession(sessions, watcherId, clients, handler).ok)
44820
- notify2();
44821
- })
44822
- });
44823
- return handler;
44824
- }
44825
-
44826
- // src/commands/sessions/daemon/applySetStatus.ts
44827
- function applySetStatus(sessions, id, status2, source, onStatusChange, rebind) {
44828
- const session = sessions.get(id);
44829
- if (!session) {
44830
- daemonLog(
44831
- `set-status for unknown session id=${id} status=${status2} (no live session; ignoring)`
44832
- );
44833
- return;
44834
- }
44835
- if (session.status === "stopped") {
44836
- daemonLog(
44837
- `set-status for stopped session id=${id} status=${status2} (held pending durable close; ignoring)`
44838
- );
44839
- return;
44840
- }
44841
- rebind?.(session);
44842
- if (source === "permission") session.permissionActive = true;
44843
- if (status2 === "running") session.permissionActive = false;
44844
- onStatusChange(session, status2);
44845
- }
44846
-
44847
- // src/commands/sessions/daemon/rebindClaudeSession.ts
44848
- function rebindClaudeSession(session, claudeSessionId, notify2, onStatusChange) {
44849
- if (!claudeSessionId) return;
44850
- if (session.claudeSessionId === claudeSessionId) return;
44851
- daemonLog(
44852
- `session ${session.id} claude session moved: ${session.claudeSessionId ?? "none"} -> ${claudeSessionId}`
44853
- );
44854
- session.claudeSessionId = claudeSessionId;
44855
- watchTranscript(session, notify2, onStatusChange);
44856
- }
44857
-
44858
- // src/commands/sessions/daemon/setStatusFromHook.ts
44859
- function setStatusFromHook(sessions, report, notify2, onStatusChange) {
44860
- applySetStatus(
44861
- sessions,
44862
- report.id,
44863
- report.status,
44864
- report.source,
44865
- onStatusChange,
44866
- (session) => rebindClaudeSession(
44867
- session,
44868
- report.claudeSessionId,
44869
- notify2,
44870
- onStatusChange
44871
- )
44872
- );
44873
- }
44874
-
44875
44995
  // src/commands/sessions/daemon/restartAndNotify.ts
44876
44996
  function restartAndNotify(sessions, id, clients, onStatusChange, notify2) {
44877
44997
  const result = restartManagedSession(sessions, id, clients, onStatusChange);
@@ -44913,8 +45033,7 @@ function restoreBase(id, persisted) {
44913
45033
  cwd: persisted.cwd,
44914
45034
  assistArgs: persisted.assistArgs,
44915
45035
  initialPrompt: persisted.initialPrompt,
44916
- starred: persisted.watcher ? false : persisted.starred,
44917
- watcher: persisted.watcher,
45036
+ starred: persisted.starred,
44918
45037
  divergenceEscalation: persisted.divergenceEscalation,
44919
45038
  design: persisted.design,
44920
45039
  auto: persisted.auto,
@@ -44974,12 +45093,19 @@ function underTempRoot(path106) {
44974
45093
  );
44975
45094
  }
44976
45095
 
44977
- // src/commands/sessions/daemon/dropTempRootedSessions.ts
44978
- function dropTempRootedSessions(persisted) {
45096
+ // src/commands/sessions/daemon/dropUnrestorableSessions.ts
45097
+ function droppedReason(entry) {
45098
+ if (underTempRoot(entry.cwd)) return "lies outside any project root";
45099
+ if (entry.watcher === true)
45100
+ return "is a retired watcher session (the daemon now runs the assist auto-update loop itself)";
45101
+ return void 0;
45102
+ }
45103
+ function dropUnrestorableSessions(persisted) {
44979
45104
  return persisted.filter((entry) => {
44980
- if (!underTempRoot(entry.cwd)) return true;
45105
+ const reason4 = droppedReason(entry);
45106
+ if (!reason4) return true;
44981
45107
  daemonLog(
44982
- `persisted session ${describePersistedSession(entry)} lies outside any project root; dropped rather than restored`
45108
+ `persisted session ${describePersistedSession(entry)} ${reason4}; dropped rather than restored`
44983
45109
  );
44984
45110
  return false;
44985
45111
  });
@@ -45304,25 +45430,6 @@ function isUpdate(persisted) {
45304
45430
  return persisted.commandType === "assist" && persisted.assistArgs?.[0] === "update";
45305
45431
  }
45306
45432
 
45307
- // src/commands/sessions/daemon/migrateClaudeWatcher.ts
45308
- function migrateClaudeWatcher(id, persisted) {
45309
- if (!persisted.watcher || persisted.commandType !== "claude")
45310
- return persisted;
45311
- daemonLog(
45312
- `migrating claude /watch watcher session ${id} to assist ${WATCHER_ARGS.join(" ")}`
45313
- );
45314
- return {
45315
- ...persisted,
45316
- name: `assist ${WATCHER_ARGS.join(" ")}`,
45317
- commandType: "assist",
45318
- assistArgs: WATCHER_ARGS,
45319
- initialPrompt: void 0,
45320
- claudeSessionId: void 0,
45321
- harness: void 0,
45322
- harnessSessionId: void 0
45323
- };
45324
- }
45325
-
45326
45433
  // src/commands/sessions/daemon/needsWrapperRelaunch.ts
45327
45434
  var LAUNCH_COMMANDS = ["draft", "feat", "bug", "refine"];
45328
45435
  function needsWrapperRelaunch(persisted) {
@@ -45335,36 +45442,11 @@ function isOnceLaunch(persisted) {
45335
45442
  return persisted.commandType === "assist" && !!persisted.assistArgs && LAUNCH_COMMANDS.includes(persisted.assistArgs[0]) && persisted.assistArgs.includes("--once");
45336
45443
  }
45337
45444
 
45338
- // src/commands/sessions/daemon/relaunchConsoleWatcher.ts
45339
- function isConsoleWatcher(persisted) {
45340
- return persisted.watcher === true && persisted.commandType === "assist" && !!persisted.assistArgs;
45341
- }
45342
- function relaunchConsoleWatcher(id, persisted, base) {
45343
- const command = ["assist", ...persisted.assistArgs];
45344
- daemonLog(
45345
- `relaunching watcher session ${id} running ${command.join(" ")} in ${persisted.cwd ?? "(no cwd)"}`
45346
- );
45347
- const session = runningSession(
45348
- base,
45349
- persisted,
45350
- spawnPty(command, persisted.cwd, id)
45351
- );
45352
- if (persisted.cwd)
45353
- session.scrollback = seedWatcherScrollback(
45354
- persisted.cwd,
45355
- "daemon restarted; watcher relaunched"
45356
- );
45357
- return session;
45358
- }
45359
-
45360
45445
  // src/commands/sessions/daemon/restoreSession.ts
45361
- function restoreSession(id, stored) {
45362
- const persisted = migrateClaudeWatcher(id, stored);
45446
+ function restoreSession(id, persisted) {
45363
45447
  const base = restoreBase(id, persisted);
45364
45448
  if (persisted.status === "stopped") return stoppedSession(base, persisted);
45365
45449
  if (isUpdate(persisted)) return updatedSession(id, persisted);
45366
- if (isConsoleWatcher(persisted))
45367
- return relaunchConsoleWatcher(id, persisted, base);
45368
45450
  const status2 = deriveRestoreStatus(persisted);
45369
45451
  if (needsWrapperRelaunch(persisted)) {
45370
45452
  const resumesWrittenConversation = hasTranscriptOnDisk(persisted);
@@ -45418,7 +45500,7 @@ function logRestoreError(persisted, error) {
45418
45500
 
45419
45501
  // src/commands/sessions/daemon/restoreAll.ts
45420
45502
  function restoreAll(spawner, sessions) {
45421
- const persisted = dropTempRootedSessions(loadPersistedSessions());
45503
+ const persisted = dropUnrestorableSessions(loadPersistedSessions());
45422
45504
  const cap2 = sessionLimits.maxRestore();
45423
45505
  const restoredIds = /* @__PURE__ */ new Map();
45424
45506
  const names = persisted.slice(0, cap2).map((entry) => {
@@ -45674,10 +45756,10 @@ function restoreAllSessions(spawner, sessions, notify2) {
45674
45756
 
45675
45757
  // src/commands/sessions/daemon/retrySession.ts
45676
45758
  function retrySession(session, clients, onStatusChange) {
45677
- const spawn11 = respawnThunk(session);
45678
- if (!spawn11) return false;
45759
+ const spawn12 = respawnThunk(session);
45760
+ if (!spawn12) return false;
45679
45761
  const respawn = () => {
45680
- respawnSession(session, spawn11, "running", clients, onStatusChange);
45762
+ respawnSession(session, spawn12, "running", clients, onStatusChange);
45681
45763
  daemonLog(`session ${session.id} retried: ${session.name}`);
45682
45764
  };
45683
45765
  const pty2 = session.pty;
@@ -46706,6 +46788,30 @@ function addAgentToStream(ctx, targetId, request) {
46706
46788
  };
46707
46789
  }
46708
46790
 
46791
+ // src/commands/sessions/daemon/createAssistSession.ts
46792
+ function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
46793
+ const startedAt = Date.now();
46794
+ return {
46795
+ id,
46796
+ name: `assist ${assistArgs.join(" ")}`,
46797
+ title: meta?.title,
46798
+ subtitle: meta?.subtitle,
46799
+ commandType: "assist",
46800
+ status: "running",
46801
+ startedAt,
46802
+ runningMs: 0,
46803
+ runningSince: startedAt,
46804
+ waitingSince: null,
46805
+ ...startOrHoldPty(
46806
+ () => spawnPty(["assist", ...assistArgs], cwd, id),
46807
+ holdPty
46808
+ ),
46809
+ scrollback: "",
46810
+ assistArgs,
46811
+ cwd
46812
+ };
46813
+ }
46814
+
46709
46815
  // src/commands/sessions/daemon/worktree/spawnInTree.ts
46710
46816
  function spawnInTree(ctx, { prompt, cwd, design, auto, harness, inPlace }, context) {
46711
46817
  return allocateAndBind(
@@ -46724,7 +46830,7 @@ function spawnInTree(ctx, { prompt, cwd, design, auto, harness, inPlace }, conte
46724
46830
  );
46725
46831
  }
46726
46832
  function spawnAssistInTree(ctx, assistArgs, cwd, meta, context) {
46727
- const id = allocateAndBind(
46833
+ return allocateAndBind(
46728
46834
  ctx,
46729
46835
  cwd,
46730
46836
  (sid, resolvedCwd, holdUntilSeeded) => createAssistSession(sid, assistArgs, resolvedCwd, meta, holdUntilSeeded),
@@ -46737,8 +46843,6 @@ function spawnAssistInTree(ctx, assistArgs, cwd, meta, context) {
46737
46843
  },
46738
46844
  context
46739
46845
  );
46740
- if (isBacklogRunArgs(assistArgs)) ensureWatcher(ctx, cwd);
46741
- return id;
46742
46846
  }
46743
46847
 
46744
46848
  // src/commands/sessions/daemon/worktree/resumeInTree.ts
@@ -46965,13 +47069,11 @@ var SessionManager = class {
46965
47069
  );
46966
47070
  }
46967
47071
  dismissSession = (id) => {
46968
- dismissAndResumeWatcher(
46969
- this.sessions,
46970
- id,
46971
- this.notify,
46972
- (watcherId) => this.restart(watcherId)
46973
- );
47072
+ dismissSessionGated(this.sessions, id, this.notify);
46974
47073
  };
47074
+ startAutoUpdate() {
47075
+ startAutoUpdate(() => this.treeCtx());
47076
+ }
46975
47077
  discardSession = (id) => {
46976
47078
  dismissSessionGated(this.sessions, id, this.notify, true);
46977
47079
  };
@@ -47036,6 +47138,16 @@ function exitAfterFlush(code) {
47036
47138
  // src/commands/sessions/daemon/handleConnection.ts
47037
47139
  import { createInterface as createInterface10 } from "readline";
47038
47140
 
47141
+ // src/commands/sessions/daemon/autoUpdate/autoUpdateReply.ts
47142
+ function autoUpdateReply() {
47143
+ return {
47144
+ type: "auto-update",
47145
+ version: ASSIST_VERSION,
47146
+ startCommit: autoUpdateState.startCommit(),
47147
+ loop: autoUpdateState.loop()
47148
+ };
47149
+ }
47150
+
47039
47151
  // src/commands/sessions/daemon/handleHelloRequest.ts
47040
47152
  var handleHelloRequest = (client, m, d) => {
47041
47153
  sendTo(client, buildHello());
@@ -47049,10 +47161,10 @@ var handleHelloRequest = (client, m, d) => {
47049
47161
  };
47050
47162
 
47051
47163
  // src/commands/sessions/daemon/creator.ts
47052
- function creator(isNew, spawn11) {
47164
+ function creator(isNew, spawn12) {
47053
47165
  return (client, m, d) => {
47054
47166
  if (m.links.route(client, d)) return;
47055
- const result = spawn11(m, d);
47167
+ const result = spawn12(m, d);
47056
47168
  if (typeof result !== "string") {
47057
47169
  sendTo(client, { type: "error", message: result.error });
47058
47170
  return;
@@ -47561,6 +47673,7 @@ var messageHandlers = {
47561
47673
  hello: handleHelloRequest,
47562
47674
  output: handleOutputRequest,
47563
47675
  nodes: (client, m) => m.links.sendNodes(client),
47676
+ "auto-update": (client) => sendTo(client, autoUpdateReply()),
47564
47677
  "reload-links": (client, m) => {
47565
47678
  daemonLog("reload-links received");
47566
47679
  m.links.reload();
@@ -47718,6 +47831,7 @@ function onListening(manager, checkAutoExit) {
47718
47831
  );
47719
47832
  daemonLog(`listening on ${daemonPaths.socket}`);
47720
47833
  manager.links.reload();
47834
+ manager.startAutoUpdate();
47721
47835
  checkAutoExit(manager.isIdle());
47722
47836
  }
47723
47837
  function cleanupOwnedFiles() {
@@ -47820,6 +47934,7 @@ function registerDaemon(program2) {
47820
47934
  cmd.command("drain").description(
47821
47935
  "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"
47822
47936
  ).option("--yes", "Close live sessions without confirming").action((options2) => drainDaemon(options2));
47937
+ configHelp(cmd, autoUpdateConfigHelp);
47823
47938
  }
47824
47939
 
47825
47940
  // src/commands/sessions/closeSession.ts
@@ -48080,14 +48195,14 @@ async function linkStatus(name, query = queryNodes) {
48080
48195
  return (await query())?.links.find((l) => l.name === name);
48081
48196
  }
48082
48197
  async function awaitLinkReturn(name, options2, deps2 = {}) {
48083
- const { query = queryNodes, sleep: sleep2 = wait, now = Date.now } = deps2;
48198
+ const { query = queryNodes, sleep: sleep3 = wait, now = Date.now } = deps2;
48084
48199
  const deadline = now() + options2.timeoutMs;
48085
48200
  let sawDown = options2.sawDown;
48086
48201
  while (now() < deadline) {
48087
48202
  const link3 = await linkStatus(name, query);
48088
48203
  if (link3?.state !== "connected") sawDown = true;
48089
48204
  else if (sawDown) return link3;
48090
- await sleep2(POLL_MS4);
48205
+ await sleep3(POLL_MS4);
48091
48206
  }
48092
48207
  return void 0;
48093
48208
  }