lobstah 0.6.6 → 0.6.7

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/runner.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // packages/runner/src/index.ts
2
- import * as fs20 from "node:fs";
3
- import * as path17 from "node:path";
2
+ import * as fs21 from "node:fs";
3
+ import * as path18 from "node:path";
4
4
 
5
5
  // packages/core/dist/types.js
6
6
  var VERBS = ["working", "needs-decision", "blocked", "paused", "done", "failed"];
@@ -8,6 +8,10 @@ var TERMINAL_VERBS = ["done", "failed"];
8
8
  var WAITING_ON = ["review", "pr", "deploy", "person", "external"];
9
9
  var WAITING_ON_VERBS = ["paused", "needs-decision", "blocked"];
10
10
 
11
+ // packages/core/dist/stats.js
12
+ import * as fs2 from "node:fs";
13
+ import * as path2 from "node:path";
14
+
11
15
  // packages/core/dist/paths.js
12
16
  import * as os from "node:os";
13
17
  import * as path from "node:path";
@@ -54,6 +58,156 @@ function evidencePath(id, lane) {
54
58
  return path.join(laneDirs(lane).state, `${id}.evidence`);
55
59
  }
56
60
 
61
+ // packages/core/dist/stats.js
62
+ function statsPath() {
63
+ return path2.join(lobstahHome(), "stats.json");
64
+ }
65
+ function localDay(at = Date.now()) {
66
+ const d = new Date(at);
67
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
68
+ }
69
+ function readCatchFacts(stateDir, id) {
70
+ let last;
71
+ try {
72
+ for (const line of fs2.readFileSync(path2.join(stateDir, `${id}.status`), "utf8").split("\n")) {
73
+ if (!line.trim())
74
+ continue;
75
+ try {
76
+ last = JSON.parse(line);
77
+ } catch {
78
+ }
79
+ }
80
+ } catch {
81
+ return { done: false };
82
+ }
83
+ if (last?.verb !== "done")
84
+ return { done: false };
85
+ let trap;
86
+ try {
87
+ const to = JSON.parse(fs2.readFileSync(path2.join(stateDir, `${id}.evidence`), "utf8")).deliveredTo;
88
+ if (typeof to === "string" && to.startsWith("wt:"))
89
+ trap = to;
90
+ } catch {
91
+ }
92
+ return { done: true, ...typeof last.at === "string" ? { at: last.at } : {}, ...trap ? { trap } : {} };
93
+ }
94
+ function emptyStore(now2) {
95
+ return { version: 1, totalCatches: 0, perTrap: {}, day: localDay(now2), catchesToday: 0, counted: [] };
96
+ }
97
+ function add(store, facts, now2) {
98
+ store.totalCatches++;
99
+ if (facts.trap)
100
+ store.perTrap[facts.trap] = (store.perTrap[facts.trap] ?? 0) + 1;
101
+ const today = localDay(now2);
102
+ if (store.day !== today) {
103
+ store.day = today;
104
+ store.catchesToday = 0;
105
+ }
106
+ const at = facts.at && Number.isFinite(Date.parse(facts.at)) ? facts.at : void 0;
107
+ if ((at ? localDay(at) : today) === today)
108
+ store.catchesToday++;
109
+ }
110
+ function backfillSources() {
111
+ const live = ["work", "chore"].map((lane) => ({ dir: laneDirs(lane).state, live: true }));
112
+ const backups = readDirIfPresent(lobstahHome()).filter((name) => name.startsWith("state-backup-")).sort().map((name) => ({ dir: path2.join(lobstahHome(), name), live: false })).filter((s) => fs2.statSync(s.dir).isDirectory());
113
+ return [...live, ...backups];
114
+ }
115
+ function backfillStats(now2 = Date.now()) {
116
+ const store = emptyStore(now2);
117
+ const seen = /* @__PURE__ */ new Set();
118
+ for (const { dir, live } of backfillSources()) {
119
+ for (const file of readDirIfPresent(dir).sort()) {
120
+ if (!file.endsWith(".status"))
121
+ continue;
122
+ const id = file.slice(0, -".status".length);
123
+ if (seen.has(id))
124
+ continue;
125
+ seen.add(id);
126
+ const facts = readCatchFacts(dir, id);
127
+ if (!facts.done)
128
+ continue;
129
+ add(store, facts, now2);
130
+ if (live)
131
+ store.counted.push(id);
132
+ }
133
+ }
134
+ return store;
135
+ }
136
+ function parseStore(text) {
137
+ try {
138
+ const s = JSON.parse(text);
139
+ if (s.version !== 1 || typeof s.totalCatches !== "number" || typeof s.catchesToday !== "number" || typeof s.day !== "string")
140
+ return void 0;
141
+ return { version: 1, totalCatches: s.totalCatches, perTrap: s.perTrap ?? {}, day: s.day, catchesToday: s.catchesToday, counted: s.counted ?? [] };
142
+ } catch {
143
+ return void 0;
144
+ }
145
+ }
146
+ function writeStore(store) {
147
+ const file = statsPath();
148
+ fs2.mkdirSync(path2.dirname(file), { recursive: true });
149
+ const tmp = `${file}.tmp-${process.pid}`;
150
+ fs2.writeFileSync(tmp, JSON.stringify(store));
151
+ fs2.renameSync(tmp, file);
152
+ }
153
+ function withStatsLock(action) {
154
+ fs2.mkdirSync(lobstahHome(), { recursive: true });
155
+ const lock = `${statsPath()}.lock`;
156
+ const deadline = Date.now() + 1e4;
157
+ for (; ; ) {
158
+ try {
159
+ fs2.mkdirSync(lock);
160
+ break;
161
+ } catch (err) {
162
+ if (err.code !== "EEXIST")
163
+ throw err;
164
+ try {
165
+ if (Date.now() - fs2.statSync(lock).mtimeMs > 12e4)
166
+ fs2.rmdirSync(lock);
167
+ } catch {
168
+ }
169
+ if (Date.now() >= deadline)
170
+ throw new Error("stats store locked");
171
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
172
+ }
173
+ }
174
+ try {
175
+ return action();
176
+ } finally {
177
+ fs2.rmdirSync(lock);
178
+ }
179
+ }
180
+ function loadOrBackfill(now2) {
181
+ let text;
182
+ try {
183
+ text = fs2.readFileSync(statsPath(), "utf8");
184
+ } catch (err) {
185
+ if (err.code !== "ENOENT")
186
+ throw err;
187
+ }
188
+ const saved = text === void 0 ? void 0 : parseStore(text);
189
+ if (saved)
190
+ return saved;
191
+ if (text !== void 0)
192
+ fs2.renameSync(statsPath(), `${statsPath()}.unreadable-${now2}`);
193
+ const store = backfillStats(now2);
194
+ writeStore(store);
195
+ return store;
196
+ }
197
+ function recordCatch(id, lane, now2 = Date.now()) {
198
+ withStatsLock(() => {
199
+ const store = loadOrBackfill(now2);
200
+ if (store.counted.includes(id))
201
+ return;
202
+ const facts = readCatchFacts(laneDirs(lane).state, id);
203
+ if (!facts.done)
204
+ return;
205
+ add(store, facts, now2);
206
+ store.counted.push(id);
207
+ writeStore(store);
208
+ });
209
+ }
210
+
57
211
  // packages/core/dist/attachments.js
58
212
  function attachmentBlock(attachments) {
59
213
  if (attachments.length === 0)
@@ -89,9 +243,9 @@ var ANSWER_TYPES = {
89
243
  var ANSWER_EXTENSIONS = Object.keys(ANSWER_TYPES);
90
244
 
91
245
  // packages/core/dist/config.js
92
- import * as fs2 from "node:fs";
246
+ import * as fs3 from "node:fs";
93
247
  import * as os2 from "node:os";
94
- import * as path2 from "node:path";
248
+ import * as path3 from "node:path";
95
249
 
96
250
  // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/date.js
97
251
  var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
@@ -799,14 +953,14 @@ var DEFAULT_LIMITS = {
799
953
  releaseOnMerge: false
800
954
  };
801
955
  function configPath() {
802
- return path2.join(lobstahHome(), "config.toml");
956
+ return path3.join(lobstahHome(), "config.toml");
803
957
  }
804
958
  function expandHome(p) {
805
- return p.startsWith("~/") ? path2.join(os2.homedir(), p.slice(2)) : p;
959
+ return p.startsWith("~/") ? path3.join(os2.homedir(), p.slice(2)) : p;
806
960
  }
807
961
  function loadConfig() {
808
962
  const file = configPath();
809
- const raw = fs2.existsSync(file) ? parse(fs2.readFileSync(file, "utf8")) : {};
963
+ const raw = fs3.existsSync(file) ? parse(fs3.readFileSync(file, "utf8")) : {};
810
964
  const reposRaw = raw.repos ?? {};
811
965
  const repos = {};
812
966
  for (const [key, r] of Object.entries(reposRaw)) {
@@ -869,8 +1023,8 @@ function resolveDispatch(d, cfg) {
869
1023
  var GB = 1024 ** 3;
870
1024
 
871
1025
  // packages/core/dist/watch.js
872
- import * as fs3 from "node:fs";
873
- import * as path3 from "node:path";
1026
+ import * as fs4 from "node:fs";
1027
+ import * as path4 from "node:path";
874
1028
 
875
1029
  // packages/core/dist/gh-errors.js
876
1030
  function firstMeaningfulLine(...outputs) {
@@ -892,40 +1046,40 @@ function firstMeaningfulLine(...outputs) {
892
1046
 
893
1047
  // packages/core/dist/watch.js
894
1048
  function watchesDir() {
895
- return path3.join(lobstahHome(), "watches");
1049
+ return path4.join(lobstahHome(), "watches");
896
1050
  }
897
1051
  function slug(key) {
898
1052
  return key.replace(/[^A-Za-z0-9._-]+/g, "-");
899
1053
  }
900
1054
  function watchPath(key) {
901
- return path3.join(watchesDir(), `${slug(key)}.json`);
1055
+ return path4.join(watchesDir(), `${slug(key)}.json`);
902
1056
  }
903
1057
  function readWatch(key) {
904
1058
  try {
905
- return JSON.parse(fs3.readFileSync(watchPath(key), "utf8"));
1059
+ return JSON.parse(fs4.readFileSync(watchPath(key), "utf8"));
906
1060
  } catch {
907
1061
  return void 0;
908
1062
  }
909
1063
  }
910
1064
 
911
1065
  // packages/core/dist/inbox.js
912
- import * as fs11 from "node:fs";
913
- import * as path9 from "node:path";
1066
+ import * as fs12 from "node:fs";
1067
+ import * as path10 from "node:path";
914
1068
 
915
1069
  // packages/core/dist/soak.js
916
- import * as fs10 from "node:fs";
917
- import * as path8 from "node:path";
1070
+ import * as fs11 from "node:fs";
1071
+ import * as path9 from "node:path";
918
1072
 
919
1073
  // packages/core/dist/queue.js
920
- import * as fs6 from "node:fs";
921
- import * as path5 from "node:path";
1074
+ import * as fs7 from "node:fs";
1075
+ import * as path6 from "node:path";
922
1076
 
923
1077
  // packages/core/dist/status.js
924
- import * as fs5 from "node:fs";
1078
+ import * as fs6 from "node:fs";
925
1079
 
926
1080
  // packages/core/dist/activity.js
927
- import * as fs4 from "node:fs";
928
- import * as path4 from "node:path";
1081
+ import * as fs5 from "node:fs";
1082
+ import * as path5 from "node:path";
929
1083
  var ACTIVITY_KINDS = ["tool", "message", "thinking", "waiting"];
930
1084
  var ACTIVITY_SUMMARY_MAX = 80;
931
1085
  var SECRET_PATTERNS = [
@@ -979,7 +1133,7 @@ function relativeTarget(p, root) {
979
1133
  if (!root)
980
1134
  return baseName(p);
981
1135
  const win = isWindowsPath(p) || isWindowsPath(root);
982
- const lib = win ? path4.win32 : path4.posix;
1136
+ const lib = win ? path5.win32 : path5.posix;
983
1137
  if (!lib.isAbsolute(p))
984
1138
  return p.replace(/\\/g, "/");
985
1139
  const rel = lib.relative(win ? root : root.replace(/\\/g, "/"), p);
@@ -1048,8 +1202,8 @@ function activityFromEvent(ev, root) {
1048
1202
  }
1049
1203
  function atomicWrite(file, content) {
1050
1204
  const tmp = `${file}.tmp-${process.pid}`;
1051
- fs4.writeFileSync(tmp, content);
1052
- fs4.renameSync(tmp, file);
1205
+ fs5.writeFileSync(tmp, content);
1206
+ fs5.renameSync(tmp, file);
1053
1207
  }
1054
1208
  function writeActivity(id, lane, a) {
1055
1209
  const safe = { at: a.at, kind: a.kind, summary: redactSummary(a.summary) };
@@ -1057,7 +1211,7 @@ function writeActivity(id, lane, a) {
1057
1211
  }
1058
1212
  function readActivity(id, lane) {
1059
1213
  try {
1060
- const parsed = JSON.parse(fs4.readFileSync(activityPath(id, lane), "utf8"));
1214
+ const parsed = JSON.parse(fs5.readFileSync(activityPath(id, lane), "utf8"));
1061
1215
  if (typeof parsed.at !== "string" || typeof parsed.summary !== "string")
1062
1216
  return void 0;
1063
1217
  if (!ACTIVITY_KINDS.includes(parsed.kind))
@@ -1189,15 +1343,21 @@ function appendStatus(id, lane, verb, note, at, waiting, reported = false) {
1189
1343
  ...extra,
1190
1344
  ...reported ? { reported: true } : {}
1191
1345
  };
1192
- fs5.appendFileSync(statusPath(id, lane), `${JSON.stringify(entry)}
1346
+ fs6.appendFileSync(statusPath(id, lane), `${JSON.stringify(entry)}
1193
1347
  `);
1348
+ if (verb === "done") {
1349
+ try {
1350
+ recordCatch(id, lane);
1351
+ } catch {
1352
+ }
1353
+ }
1194
1354
  return entry;
1195
1355
  }
1196
1356
  function readStatusLog(id, lane) {
1197
1357
  const file = statusPath(id, lane);
1198
- if (!fs5.existsSync(file))
1358
+ if (!fs6.existsSync(file))
1199
1359
  return [];
1200
- return fs5.readFileSync(file, "utf8").split("\n").filter(Boolean).flatMap((line) => {
1360
+ return fs6.readFileSync(file, "utf8").split("\n").filter(Boolean).flatMap((line) => {
1201
1361
  try {
1202
1362
  const parsed = JSON.parse(line);
1203
1363
  return isVerb(parsed.verb) ? [parsed] : [];
@@ -1214,12 +1374,12 @@ function pausedWaiting(entry) {
1214
1374
  function storedDescriptor(id, lane) {
1215
1375
  const dirs = laneDirs(lane);
1216
1376
  for (const file of [
1217
- path5.join(dirs.queue, `${id}.json`),
1218
- path5.join(dirs.active, id, "descriptor.json"),
1219
- path5.join(dirs.done, id, "descriptor.json")
1377
+ path6.join(dirs.queue, `${id}.json`),
1378
+ path6.join(dirs.active, id, "descriptor.json"),
1379
+ path6.join(dirs.done, id, "descriptor.json")
1220
1380
  ]) {
1221
1381
  try {
1222
- return JSON.parse(fs6.readFileSync(file, "utf8"));
1382
+ return JSON.parse(fs7.readFileSync(file, "utf8"));
1223
1383
  } catch {
1224
1384
  }
1225
1385
  }
@@ -1230,17 +1390,17 @@ function activeIds(lane) {
1230
1390
  }
1231
1391
  function complete(id, lane) {
1232
1392
  const dirs = laneDirs(lane);
1233
- fs6.renameSync(path5.join(dirs.active, id), path5.join(dirs.done, id));
1393
+ fs7.renameSync(path6.join(dirs.active, id), path6.join(dirs.done, id));
1234
1394
  }
1235
1395
  function cancelRequested(id, lane) {
1236
- return fs6.existsSync(path5.join(laneDirs(lane).active, id, "cancel"));
1396
+ return fs7.existsSync(path6.join(laneDirs(lane).active, id, "cancel"));
1237
1397
  }
1238
1398
 
1239
1399
  // packages/core/dist/evidence.js
1240
- import * as fs7 from "node:fs";
1400
+ import * as fs8 from "node:fs";
1241
1401
  function readEvidence(id, lane) {
1242
1402
  try {
1243
- return JSON.parse(fs7.readFileSync(evidencePath(id, lane), "utf8"));
1403
+ return JSON.parse(fs8.readFileSync(evidencePath(id, lane), "utf8"));
1244
1404
  } catch {
1245
1405
  return {};
1246
1406
  }
@@ -1248,13 +1408,13 @@ function readEvidence(id, lane) {
1248
1408
  function mergeEvidence(id, lane, patch) {
1249
1409
  const file = evidencePath(id, lane);
1250
1410
  const tmp = `${file}.tmp-${process.pid}`;
1251
- fs7.writeFileSync(tmp, JSON.stringify({ ...readEvidence(id, lane), ...patch }, null, 2));
1252
- fs7.renameSync(tmp, file);
1411
+ fs8.writeFileSync(tmp, JSON.stringify({ ...readEvidence(id, lane), ...patch }, null, 2));
1412
+ fs8.renameSync(tmp, file);
1253
1413
  }
1254
1414
 
1255
1415
  // packages/core/dist/pushes.js
1256
- import * as fs8 from "node:fs";
1257
- import * as path6 from "node:path";
1416
+ import * as fs9 from "node:fs";
1417
+ import * as path7 from "node:path";
1258
1418
  var MAX_PUSHES = 20;
1259
1419
  var PUSH_HEAD = "HEAD";
1260
1420
  var VALUE_OPTIONS = /* @__PURE__ */ new Set(["-o", "--push-option", "--repo", "--receive-pack", "--exec"]);
@@ -1309,18 +1469,18 @@ function gitPushTargets(command) {
1309
1469
  return found ? [...new Set(targets)] : void 0;
1310
1470
  }
1311
1471
  function gitDirAt(dir) {
1312
- let at = path6.resolve(dir);
1472
+ let at = path7.resolve(dir);
1313
1473
  for (; ; ) {
1314
- const dotGit = path6.join(at, ".git");
1474
+ const dotGit = path7.join(at, ".git");
1315
1475
  try {
1316
- const stat = fs8.statSync(dotGit);
1476
+ const stat = fs9.statSync(dotGit);
1317
1477
  if (stat.isDirectory())
1318
1478
  return dotGit;
1319
- const m = /^gitdir:\s*(.+)$/m.exec(fs8.readFileSync(dotGit, "utf8"));
1320
- return m ? path6.resolve(at, m[1].trim()) : void 0;
1479
+ const m = /^gitdir:\s*(.+)$/m.exec(fs9.readFileSync(dotGit, "utf8"));
1480
+ return m ? path7.resolve(at, m[1].trim()) : void 0;
1321
1481
  } catch {
1322
1482
  }
1323
- const up = path6.dirname(at);
1483
+ const up = path7.dirname(at);
1324
1484
  if (up === at)
1325
1485
  return void 0;
1326
1486
  at = up;
@@ -1331,7 +1491,7 @@ function currentBranchAt(dir) {
1331
1491
  if (!gitDir)
1332
1492
  return void 0;
1333
1493
  try {
1334
- const m = /^ref:\s*refs\/heads\/(.+)$/m.exec(fs8.readFileSync(path6.join(gitDir, "HEAD"), "utf8"));
1494
+ const m = /^ref:\s*refs\/heads\/(.+)$/m.exec(fs9.readFileSync(path7.join(gitDir, "HEAD"), "utf8"));
1335
1495
  return m ? m[1].trim() : void 0;
1336
1496
  } catch {
1337
1497
  return void 0;
@@ -1350,23 +1510,23 @@ function recordPush(id, lane, branches, at = (/* @__PURE__ */ new Date()).toISOS
1350
1510
  }
1351
1511
 
1352
1512
  // packages/core/dist/worktrees.js
1353
- import * as fs9 from "node:fs";
1354
- import * as path7 from "node:path";
1513
+ import * as fs10 from "node:fs";
1514
+ import * as path8 from "node:path";
1355
1515
  var LANES = ["work", "chore"];
1356
1516
  function worktreePath(id) {
1357
- return path7.join(lobstahHome(), "worktrees", id);
1517
+ return path8.join(lobstahHome(), "worktrees", id);
1358
1518
  }
1359
1519
  function readRecord(file) {
1360
1520
  try {
1361
- const r = JSON.parse(fs9.readFileSync(file, "utf8"));
1521
+ const r = JSON.parse(fs10.readFileSync(file, "utf8"));
1362
1522
  return typeof r.path === "string" ? r : void 0;
1363
1523
  } catch {
1364
1524
  return void 0;
1365
1525
  }
1366
1526
  }
1367
1527
  function ownerOfPath(p) {
1368
- const rel = path7.relative(path7.join(lobstahHome(), "worktrees"), p);
1369
- if (!rel || rel.startsWith("..") || path7.isAbsolute(rel))
1528
+ const rel = path8.relative(path8.join(lobstahHome(), "worktrees"), p);
1529
+ if (!rel || rel.startsWith("..") || path8.isAbsolute(rel))
1370
1530
  return void 0;
1371
1531
  return rel.split(/[\\/]/)[0];
1372
1532
  }
@@ -1379,7 +1539,7 @@ function dispatchWorktree(id, lane) {
1379
1539
  }
1380
1540
  const dirs = laneDirs(l);
1381
1541
  for (const dir of [dirs.active, dirs.done]) {
1382
- const rec = readRecord(path7.join(dir, id, "worktree.json"));
1542
+ const rec = readRecord(path8.join(dir, id, "worktree.json"));
1383
1543
  if (rec) {
1384
1544
  const owner = rec.of ?? ownerOfPath(rec.path) ?? id;
1385
1545
  return { path: rec.path, owner, reused: owner !== id };
@@ -1391,7 +1551,7 @@ function dispatchWorktree(id, lane) {
1391
1551
  function laneOf(id) {
1392
1552
  for (const l of LANES) {
1393
1553
  const d = laneDirs(l);
1394
- if (fs9.existsSync(path7.join(d.queue, `${id}.json`)) || fs9.existsSync(path7.join(d.active, id)) || fs9.existsSync(path7.join(d.done, id))) {
1554
+ if (fs10.existsSync(path8.join(d.queue, `${id}.json`)) || fs10.existsSync(path8.join(d.active, id)) || fs10.existsSync(path8.join(d.done, id))) {
1395
1555
  return l;
1396
1556
  }
1397
1557
  }
@@ -1411,39 +1571,39 @@ function followUpAncestors(id, lane) {
1411
1571
  function chainWorktree(followUp) {
1412
1572
  for (const id of [followUp, ...followUpAncestors(followUp)]) {
1413
1573
  const wt = dispatchWorktree(id, laneOf(id));
1414
- if (fs9.existsSync(wt.path))
1574
+ if (fs10.existsSync(wt.path))
1415
1575
  return { ...wt, from: id };
1416
1576
  }
1417
1577
  return void 0;
1418
1578
  }
1419
1579
  function worktreeGitDir(wt) {
1420
- const dotGit = path7.join(wt, ".git");
1580
+ const dotGit = path8.join(wt, ".git");
1421
1581
  try {
1422
- const st = fs9.statSync(dotGit);
1582
+ const st = fs10.statSync(dotGit);
1423
1583
  if (st.isDirectory())
1424
1584
  return dotGit;
1425
- const m = /^gitdir:\s*(.+?)\s*$/m.exec(fs9.readFileSync(dotGit, "utf8"));
1426
- return m ? path7.resolve(wt, m[1]) : void 0;
1585
+ const m = /^gitdir:\s*(.+?)\s*$/m.exec(fs10.readFileSync(dotGit, "utf8"));
1586
+ return m ? path8.resolve(wt, m[1]) : void 0;
1427
1587
  } catch {
1428
1588
  return void 0;
1429
1589
  }
1430
1590
  }
1431
1591
  function worktreeLockFile(wt) {
1432
1592
  const gitDir = worktreeGitDir(wt);
1433
- return gitDir ? path7.join(gitDir, "lobstah.lock") : void 0;
1593
+ return gitDir ? path8.join(gitDir, "lobstah.lock") : void 0;
1434
1594
  }
1435
1595
  function readWorktreeLock(wt) {
1436
1596
  const file = worktreeLockFile(wt);
1437
1597
  if (!file)
1438
1598
  return void 0;
1439
1599
  try {
1440
- return JSON.parse(fs9.readFileSync(file, "utf8"));
1600
+ return JSON.parse(fs10.readFileSync(file, "utf8"));
1441
1601
  } catch {
1442
1602
  return void 0;
1443
1603
  }
1444
1604
  }
1445
1605
  function lockIsLive(lock) {
1446
- return fs9.existsSync(path7.join(laneDirs(lock.lane).active, lock.id));
1606
+ return fs10.existsSync(path8.join(laneDirs(lock.lane).active, lock.id));
1447
1607
  }
1448
1608
  function worktreeHolder(wt) {
1449
1609
  const lock = readWorktreeLock(wt);
@@ -1456,7 +1616,7 @@ function acquireWorktreeLock(wt, id, lane) {
1456
1616
  return { id: "(no git dir)", lane, pid: 0, at: mine.at };
1457
1617
  for (let attempt = 0; attempt < 3; attempt++) {
1458
1618
  try {
1459
- fs9.writeFileSync(file, JSON.stringify(mine, null, 2), { flag: "wx" });
1619
+ fs10.writeFileSync(file, JSON.stringify(mine, null, 2), { flag: "wx" });
1460
1620
  return void 0;
1461
1621
  } catch (err) {
1462
1622
  if (err.code !== "EEXIST")
@@ -1464,7 +1624,7 @@ function acquireWorktreeLock(wt, id, lane) {
1464
1624
  }
1465
1625
  let raw;
1466
1626
  try {
1467
- raw = fs9.readFileSync(file, "utf8");
1627
+ raw = fs10.readFileSync(file, "utf8");
1468
1628
  } catch {
1469
1629
  continue;
1470
1630
  }
@@ -1475,14 +1635,14 @@ function acquireWorktreeLock(wt, id, lane) {
1475
1635
  held2 = void 0;
1476
1636
  }
1477
1637
  if (held2?.id === id) {
1478
- fs9.writeFileSync(file, JSON.stringify(mine, null, 2));
1638
+ fs10.writeFileSync(file, JSON.stringify(mine, null, 2));
1479
1639
  return void 0;
1480
1640
  }
1481
1641
  if (held2 && lockIsLive(held2))
1482
1642
  return held2;
1483
1643
  try {
1484
- if (fs9.readFileSync(file, "utf8") === raw)
1485
- fs9.rmSync(file, { force: true });
1644
+ if (fs10.readFileSync(file, "utf8") === raw)
1645
+ fs10.rmSync(file, { force: true });
1486
1646
  } catch {
1487
1647
  }
1488
1648
  }
@@ -1494,33 +1654,33 @@ function releaseWorktreeLock(wt, id) {
1494
1654
  if (!file)
1495
1655
  return;
1496
1656
  if (readWorktreeLock(wt)?.id === id)
1497
- fs9.rmSync(file, { force: true });
1657
+ fs10.rmSync(file, { force: true });
1498
1658
  }
1499
1659
 
1500
1660
  // packages/core/dist/soak.js
1501
1661
  function regPath(trapId) {
1502
- return path8.join(soakingDir(), `${trapId}.json`);
1662
+ return path9.join(soakingDir(), `${trapId}.json`);
1503
1663
  }
1504
1664
  function readTrap(trapId) {
1505
1665
  try {
1506
- return JSON.parse(fs10.readFileSync(regPath(trapId), "utf8"));
1666
+ return JSON.parse(fs11.readFileSync(regPath(trapId), "utf8"));
1507
1667
  } catch {
1508
1668
  return void 0;
1509
1669
  }
1510
1670
  }
1511
1671
  function listTraps() {
1512
1672
  try {
1513
- return fs10.readdirSync(soakingDir()).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs10.readFileSync(path8.join(soakingDir(), f), "utf8"))).filter((r) => typeof r.trapId === "string");
1673
+ return fs11.readdirSync(soakingDir()).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs11.readFileSync(path9.join(soakingDir(), f), "utf8"))).filter((r) => typeof r.trapId === "string");
1514
1674
  } catch {
1515
1675
  return [];
1516
1676
  }
1517
1677
  }
1518
1678
  function claimPath(id, lane) {
1519
- return path8.join(laneDirs(lane).active, id, "claim.json");
1679
+ return path9.join(laneDirs(lane).active, id, "claim.json");
1520
1680
  }
1521
1681
  function readSessionClaim(id, lane) {
1522
1682
  try {
1523
- return JSON.parse(fs10.readFileSync(claimPath(id, lane), "utf8"));
1683
+ return JSON.parse(fs11.readFileSync(claimPath(id, lane), "utf8"));
1524
1684
  } catch {
1525
1685
  return void 0;
1526
1686
  }
@@ -1529,54 +1689,54 @@ function readSessionClaim(id, lane) {
1529
1689
  // packages/core/dist/inbox.js
1530
1690
  var metaName = (file) => file.replace(/\.msg$/, ".meta.json");
1531
1691
  function inboxDir(id, lane) {
1532
- return path9.join(laneDirs(lane).inbox, id);
1692
+ return path10.join(laneDirs(lane).inbox, id);
1533
1693
  }
1534
1694
  function unhandled(id, lane) {
1535
1695
  const dir = inboxDir(id, lane);
1536
- return readDirIfPresent(dir).filter((f) => f.endsWith(".msg")).sort().map((f) => ({ file: f, text: fs11.readFileSync(path9.join(dir, f), "utf8") }));
1696
+ return readDirIfPresent(dir).filter((f) => f.endsWith(".msg")).sort().map((f) => ({ file: f, text: fs12.readFileSync(path10.join(dir, f), "utf8") }));
1537
1697
  }
1538
1698
  function acknowledge(id, lane, file) {
1539
1699
  const dir = inboxDir(id, lane);
1540
- fs11.mkdirSync(path9.join(dir, "handled"), { recursive: true });
1541
- fs11.renameSync(path9.join(dir, file), path9.join(dir, "handled", file));
1700
+ fs12.mkdirSync(path10.join(dir, "handled"), { recursive: true });
1701
+ fs12.renameSync(path10.join(dir, file), path10.join(dir, "handled", file));
1542
1702
  try {
1543
- fs11.renameSync(path9.join(dir, metaName(file)), path9.join(dir, "handled", metaName(file)));
1703
+ fs12.renameSync(path10.join(dir, metaName(file)), path10.join(dir, "handled", metaName(file)));
1544
1704
  } catch {
1545
1705
  }
1546
1706
  }
1547
1707
 
1548
1708
  // packages/core/dist/events.js
1549
- import * as fs12 from "node:fs";
1709
+ import * as fs13 from "node:fs";
1550
1710
  function appendEvent(id, lane, ev) {
1551
- fs12.appendFileSync(eventsPath(id, lane), `${JSON.stringify(ev)}
1711
+ fs13.appendFileSync(eventsPath(id, lane), `${JSON.stringify(ev)}
1552
1712
  `);
1553
1713
  }
1554
1714
  function touchEvents(id, lane, at = /* @__PURE__ */ new Date()) {
1555
1715
  const file = eventsPath(id, lane);
1556
1716
  try {
1557
- fs12.utimesSync(file, at, at);
1717
+ fs13.utimesSync(file, at, at);
1558
1718
  } catch {
1559
- fs12.appendFileSync(file, "");
1560
- fs12.utimesSync(file, at, at);
1719
+ fs13.appendFileSync(file, "");
1720
+ fs13.utimesSync(file, at, at);
1561
1721
  }
1562
1722
  }
1563
1723
 
1564
1724
  // packages/core/dist/harness.js
1565
- import * as fs13 from "node:fs";
1566
- import * as path10 from "node:path";
1725
+ import * as fs14 from "node:fs";
1726
+ import * as path11 from "node:path";
1567
1727
  import { createRequire } from "node:module";
1568
1728
  function resolveOnPath(bin, pathVar = process.env.PATH) {
1569
1729
  if (!pathVar)
1570
1730
  return void 0;
1571
1731
  const names = process.platform === "win32" ? [bin, `${bin}.exe`, `${bin}.cmd`, `${bin}.bat`] : [bin];
1572
- for (const dir of pathVar.split(path10.delimiter)) {
1732
+ for (const dir of pathVar.split(path11.delimiter)) {
1573
1733
  if (!dir)
1574
1734
  continue;
1575
1735
  for (const name of names) {
1576
- const candidate = path10.join(dir, name);
1736
+ const candidate = path11.join(dir, name);
1577
1737
  try {
1578
- fs13.accessSync(candidate, fs13.constants.X_OK);
1579
- if (fs13.statSync(candidate).isFile())
1738
+ fs14.accessSync(candidate, fs14.constants.X_OK);
1739
+ if (fs14.statSync(candidate).isFile())
1580
1740
  return candidate;
1581
1741
  } catch {
1582
1742
  }
@@ -1597,8 +1757,8 @@ function codexInvocation(argv) {
1597
1757
  } catch {
1598
1758
  }
1599
1759
  for (const dir of require_.resolve.paths("@openai/codex") ?? []) {
1600
- const cli = path10.join(dir, "@openai", "codex", "bin", "codex.js");
1601
- if (fs13.existsSync(cli))
1760
+ const cli = path11.join(dir, "@openai", "codex", "bin", "codex.js");
1761
+ if (fs14.existsSync(cli))
1602
1762
  return { file: process.execPath, argv: [cli, ...argv] };
1603
1763
  }
1604
1764
  return void 0;
@@ -1677,21 +1837,21 @@ function parseUnresolvedThreads(stdout) {
1677
1837
  }
1678
1838
 
1679
1839
  // packages/core/dist/prs.js
1680
- import * as fs14 from "node:fs";
1681
- import * as path11 from "node:path";
1840
+ import * as fs15 from "node:fs";
1841
+ import * as path12 from "node:path";
1682
1842
  function prsDir() {
1683
- return path11.join(lobstahHome(), "prs");
1843
+ return path12.join(lobstahHome(), "prs");
1684
1844
  }
1685
1845
  function prRecordFile(key) {
1686
1846
  const ref = parsePrRef(key);
1687
1847
  if (!ref)
1688
1848
  throw new Error(`not a PR key: ${key}`);
1689
1849
  const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "_");
1690
- return path11.join(prsDir(), `${safe(ref.owner)}__${safe(ref.repo)}__${ref.number}.json`);
1850
+ return path12.join(prsDir(), `${safe(ref.owner)}__${safe(ref.repo)}__${ref.number}.json`);
1691
1851
  }
1692
1852
  function readPr(key) {
1693
1853
  try {
1694
- return JSON.parse(fs14.readFileSync(prRecordFile(key), "utf8"));
1854
+ return JSON.parse(fs15.readFileSync(prRecordFile(key), "utf8"));
1695
1855
  } catch {
1696
1856
  return void 0;
1697
1857
  }
@@ -1699,13 +1859,13 @@ function readPr(key) {
1699
1859
  function readPrs() {
1700
1860
  let files;
1701
1861
  try {
1702
- files = fs14.readdirSync(prsDir()).filter((f) => f.endsWith(".json"));
1862
+ files = fs15.readdirSync(prsDir()).filter((f) => f.endsWith(".json"));
1703
1863
  } catch {
1704
1864
  return [];
1705
1865
  }
1706
1866
  return files.flatMap((f) => {
1707
1867
  try {
1708
- const r = JSON.parse(fs14.readFileSync(path11.join(prsDir(), f), "utf8"));
1868
+ const r = JSON.parse(fs15.readFileSync(path12.join(prsDir(), f), "utf8"));
1709
1869
  return typeof r.key === "string" && typeof r.url === "string" ? [r] : [];
1710
1870
  } catch {
1711
1871
  return [];
@@ -1754,9 +1914,9 @@ function pushRule(branch, id = "<dispatch id>", mode = "rebase", also = []) {
1754
1914
  }
1755
1915
 
1756
1916
  // packages/core/dist/resume.js
1757
- import * as fs15 from "node:fs";
1917
+ import * as fs16 from "node:fs";
1758
1918
  import * as os3 from "node:os";
1759
- import * as path12 from "node:path";
1919
+ import * as path13 from "node:path";
1760
1920
  import { spawnSync as spawnSync2 } from "node:child_process";
1761
1921
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-([0-9a-f])[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1762
1922
  function harnessFromSessionId(sessionId) {
@@ -1767,7 +1927,7 @@ function claimOf(id, lane) {
1767
1927
  const dirs = laneDirs(lane);
1768
1928
  for (const dir of [dirs.active, dirs.done]) {
1769
1929
  try {
1770
- return JSON.parse(fs15.readFileSync(path12.join(dir, id, "claim.json"), "utf8"));
1930
+ return JSON.parse(fs16.readFileSync(path13.join(dir, id, "claim.json"), "utf8"));
1771
1931
  } catch {
1772
1932
  }
1773
1933
  }
@@ -1817,7 +1977,7 @@ function isUnresumable(error) {
1817
1977
  return error !== void 0 && UNRESUMABLE.test(error);
1818
1978
  }
1819
1979
  function worktreeProgress(worktree, trunk) {
1820
- if (!worktree || !fs15.existsSync(worktree))
1980
+ if (!worktree || !fs16.existsSync(worktree))
1821
1981
  return "No worktree progress recorded.";
1822
1982
  const git2 = (...a) => spawnSync2("git", a, { cwd: worktree, encoding: "utf8" }).stdout?.trim() ?? "";
1823
1983
  const commits = trunk ? git2("log", "--oneline", `origin/${trunk}..HEAD`) : git2("log", "--oneline", "-15");
@@ -1850,31 +2010,31 @@ function handoffNote(fromHarness, progress, why) {
1850
2010
  ${progress}`;
1851
2011
  }
1852
2012
  function codexHome(env = process.env) {
1853
- return env.CODEX_HOME ?? path12.join(os3.homedir(), ".codex");
2013
+ return env.CODEX_HOME ?? path13.join(os3.homedir(), ".codex");
1854
2014
  }
1855
2015
  function codexRolloutFile(threadId, home = codexHome()) {
1856
2016
  const suffix = `-${threadId}.jsonl`;
1857
2017
  const walk = (dir, depth) => {
1858
2018
  let entries;
1859
2019
  try {
1860
- entries = fs15.readdirSync(dir, { withFileTypes: true });
2020
+ entries = fs16.readdirSync(dir, { withFileTypes: true });
1861
2021
  } catch {
1862
2022
  return void 0;
1863
2023
  }
1864
2024
  for (const e of entries) {
1865
2025
  if (e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(suffix))
1866
- return path12.join(dir, e.name);
2026
+ return path13.join(dir, e.name);
1867
2027
  }
1868
2028
  if (depth === 0)
1869
2029
  return void 0;
1870
2030
  for (const e of entries.filter((x) => x.isDirectory()).sort((a, b) => b.name.localeCompare(a.name))) {
1871
- const hit = walk(path12.join(dir, e.name), depth - 1);
2031
+ const hit = walk(path13.join(dir, e.name), depth - 1);
1872
2032
  if (hit)
1873
2033
  return hit;
1874
2034
  }
1875
2035
  return void 0;
1876
2036
  };
1877
- return walk(path12.join(home, "sessions"), 3) ?? walk(path12.join(home, "archived_sessions"), 0);
2037
+ return walk(path13.join(home, "sessions"), 3) ?? walk(path13.join(home, "archived_sessions"), 0);
1878
2038
  }
1879
2039
  function codexDesktopThread(threadId, home = codexHome()) {
1880
2040
  const file = codexRolloutFile(threadId, home);
@@ -1882,12 +2042,12 @@ function codexDesktopThread(threadId, home = codexHome()) {
1882
2042
  return void 0;
1883
2043
  let head = "";
1884
2044
  try {
1885
- const fd = fs15.openSync(file, "r");
2045
+ const fd = fs16.openSync(file, "r");
1886
2046
  try {
1887
2047
  const buf = Buffer.alloc(4096);
1888
- head = buf.subarray(0, fs15.readSync(fd, buf, 0, buf.length, 0)).toString("utf8");
2048
+ head = buf.subarray(0, fs16.readSync(fd, buf, 0, buf.length, 0)).toString("utf8");
1889
2049
  } finally {
1890
- fs15.closeSync(fd);
2050
+ fs16.closeSync(fd);
1891
2051
  }
1892
2052
  } catch {
1893
2053
  return void 0;
@@ -1927,8 +2087,8 @@ function isFinished(id, lane) {
1927
2087
  }
1928
2088
 
1929
2089
  // packages/runner/src/run.ts
1930
- import * as fs19 from "node:fs";
1931
- import * as path16 from "node:path";
2090
+ import * as fs20 from "node:fs";
2091
+ import * as path17 from "node:path";
1932
2092
  import { execFileSync as execFileSync2 } from "node:child_process";
1933
2093
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1934
2094
 
@@ -2224,25 +2384,25 @@ function createClaudeAdapter() {
2224
2384
  }
2225
2385
 
2226
2386
  // packages/adapters/dist/codex.js
2227
- import * as fs16 from "node:fs";
2387
+ import * as fs17 from "node:fs";
2228
2388
  import * as os4 from "node:os";
2229
- import * as path13 from "node:path";
2389
+ import * as path14 from "node:path";
2230
2390
  import { spawn as spawn2 } from "node:child_process";
2231
2391
  function isolatedCodexHome() {
2232
- const dir = path13.join(lobstahHome(), "codex-home");
2233
- fs16.mkdirSync(dir, { recursive: true });
2234
- const auth = path13.join(dir, "auth.json");
2235
- const realAuth = path13.join(os4.homedir(), ".codex", "auth.json");
2236
- if (!fs16.existsSync(auth) && fs16.existsSync(realAuth)) {
2392
+ const dir = path14.join(lobstahHome(), "codex-home");
2393
+ fs17.mkdirSync(dir, { recursive: true });
2394
+ const auth = path14.join(dir, "auth.json");
2395
+ const realAuth = path14.join(os4.homedir(), ".codex", "auth.json");
2396
+ if (!fs17.existsSync(auth) && fs17.existsSync(realAuth)) {
2237
2397
  try {
2238
- fs16.symlinkSync(realAuth, auth);
2398
+ fs17.symlinkSync(realAuth, auth);
2239
2399
  } catch {
2240
- fs16.copyFileSync(realAuth, auth);
2400
+ fs17.copyFileSync(realAuth, auth);
2241
2401
  }
2242
2402
  }
2243
- const cfg = path13.join(dir, "config.toml");
2244
- if (!fs16.existsSync(cfg)) {
2245
- fs16.writeFileSync(cfg, "# lobstah-managed: auth inherited from ~/.codex, interactive config not\n");
2403
+ const cfg = path14.join(dir, "config.toml");
2404
+ if (!fs17.existsSync(cfg)) {
2405
+ fs17.writeFileSync(cfg, "# lobstah-managed: auth inherited from ~/.codex, interactive config not\n");
2246
2406
  }
2247
2407
  return dir;
2248
2408
  }
@@ -2445,8 +2605,8 @@ function loadAdapter(name) {
2445
2605
  }
2446
2606
 
2447
2607
  // packages/worktree/dist/index.js
2448
- import * as fs17 from "node:fs";
2449
- import * as path14 from "node:path";
2608
+ import * as fs18 from "node:fs";
2609
+ import * as path15 from "node:path";
2450
2610
  import { createHash } from "node:crypto";
2451
2611
  import { exec, execFile, execFileSync } from "node:child_process";
2452
2612
  import { promisify } from "node:util";
@@ -2519,7 +2679,7 @@ function pidAlive(pid) {
2519
2679
  var BUSY = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
2520
2680
  function takeRepoLock(file, mine) {
2521
2681
  try {
2522
- fs17.writeFileSync(file, mine, { flag: "wx" });
2682
+ fs18.writeFileSync(file, mine, { flag: "wx" });
2523
2683
  return { state: "held" };
2524
2684
  } catch (err) {
2525
2685
  const code = err.code ?? "";
@@ -2529,8 +2689,8 @@ function takeRepoLock(file, mine) {
2529
2689
  let raw = "";
2530
2690
  let age = 0;
2531
2691
  try {
2532
- raw = fs17.readFileSync(file, "utf8");
2533
- age = Date.now() - fs17.statSync(file).mtimeMs;
2692
+ raw = fs18.readFileSync(file, "utf8");
2693
+ age = Date.now() - fs18.statSync(file).mtimeMs;
2534
2694
  } catch {
2535
2695
  return { state: "retry" };
2536
2696
  }
@@ -2543,8 +2703,8 @@ function takeRepoLock(file, mine) {
2543
2703
  const stale = age > REPO_LOCK_WAIT_MS || (holder ? !pidAlive(holder.pid) : age > 5e3);
2544
2704
  if (stale) {
2545
2705
  try {
2546
- if (fs17.readFileSync(file, "utf8") === raw)
2547
- fs17.rmSync(file, { force: true });
2706
+ if (fs18.readFileSync(file, "utf8") === raw)
2707
+ fs18.rmSync(file, { force: true });
2548
2708
  } catch {
2549
2709
  }
2550
2710
  return { state: "retry" };
@@ -2553,13 +2713,13 @@ function takeRepoLock(file, mine) {
2553
2713
  }
2554
2714
  function releaseRepoLock(file, mine) {
2555
2715
  try {
2556
- if (fs17.readFileSync(file, "utf8") === mine)
2557
- fs17.rmSync(file, { force: true, recursive: true, maxRetries: 5 });
2716
+ if (fs18.readFileSync(file, "utf8") === mine)
2717
+ fs18.rmSync(file, { force: true, recursive: true, maxRetries: 5 });
2558
2718
  } catch {
2559
2719
  }
2560
2720
  }
2561
2721
  async function withRepoLock(common, fn) {
2562
- const file = path14.join(common, "lobstah-git.lock");
2722
+ const file = path15.join(common, "lobstah-git.lock");
2563
2723
  const mine = JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() });
2564
2724
  const deadline = Date.now() + REPO_LOCK_WAIT_MS;
2565
2725
  let held = false;
@@ -2595,7 +2755,7 @@ async function oneAtATime(cwd, fn) {
2595
2755
  }
2596
2756
  function fetchStamps(common) {
2597
2757
  try {
2598
- return JSON.parse(fs17.readFileSync(path14.join(common, "lobstah-fetch.json"), "utf8"));
2758
+ return JSON.parse(fs18.readFileSync(path15.join(common, "lobstah-fetch.json"), "utf8"));
2599
2759
  } catch {
2600
2760
  return {};
2601
2761
  }
@@ -2609,15 +2769,15 @@ async function fetchShared(cwd, remote, ref) {
2609
2769
  const started = Date.now();
2610
2770
  await gitShared(cwd, "fetch", remote, ref);
2611
2771
  const stamps = { ...fetchStamps(common), [key]: started };
2612
- fs17.writeFileSync(path14.join(common, "lobstah-fetch.json"), JSON.stringify(stamps, null, 2));
2772
+ fs18.writeFileSync(path15.join(common, "lobstah-fetch.json"), JSON.stringify(stamps, null, 2));
2613
2773
  });
2614
2774
  }
2615
2775
  async function allocate(repo, id, fromRemoteBranch = repo.trunk) {
2616
2776
  const dir = worktreePath(id);
2617
- if (fs17.existsSync(dir)) {
2777
+ if (fs18.existsSync(dir)) {
2618
2778
  throw new Error(`worktree for ${id} already exists at ${dir} \u2014 never allocate a second`);
2619
2779
  }
2620
- if (!fs17.existsSync(repo.path)) {
2780
+ if (!fs18.existsSync(repo.path)) {
2621
2781
  if (!repo.origin)
2622
2782
  throw new Error(`repo path ${repo.path} missing and no origin configured`);
2623
2783
  await run("git", ["clone", repo.origin, repo.path], { env: process.env });
@@ -2655,27 +2815,27 @@ function setupHash(repo, dir) {
2655
2815
  const h = createHash("sha256");
2656
2816
  h.update(JSON.stringify(repo.setup ?? []));
2657
2817
  for (const name of LOCKFILES) {
2658
- const file = path14.join(dir, name);
2659
- if (!fs17.existsSync(file))
2818
+ const file = path15.join(dir, name);
2819
+ if (!fs18.existsSync(file))
2660
2820
  continue;
2661
2821
  h.update(`\0${name}\0`);
2662
- h.update(fs17.readFileSync(file));
2822
+ h.update(fs18.readFileSync(file));
2663
2823
  }
2664
2824
  return h.digest("hex");
2665
2825
  }
2666
2826
  function setupRecordFile(dir) {
2667
2827
  const gitDir = worktreeGitDir(dir);
2668
- return gitDir ? path14.join(gitDir, "lobstah-setup.json") : void 0;
2828
+ return gitDir ? path15.join(gitDir, "lobstah-setup.json") : void 0;
2669
2829
  }
2670
2830
  function recordSetup(repo, dir) {
2671
2831
  const file = setupRecordFile(dir);
2672
2832
  if (file)
2673
- fs17.writeFileSync(file, JSON.stringify({ hash: setupHash(repo, dir), at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
2833
+ fs18.writeFileSync(file, JSON.stringify({ hash: setupHash(repo, dir), at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
2674
2834
  }
2675
2835
  function recordedSetupHash(dir) {
2676
2836
  const file = setupRecordFile(dir);
2677
2837
  try {
2678
- return file ? JSON.parse(fs17.readFileSync(file, "utf8")).hash : void 0;
2838
+ return file ? JSON.parse(fs18.readFileSync(file, "utf8")).hash : void 0;
2679
2839
  } catch {
2680
2840
  return void 0;
2681
2841
  }
@@ -2684,9 +2844,9 @@ var short = (s) => s.slice(0, 8);
2684
2844
  function realpath(p) {
2685
2845
  let r;
2686
2846
  try {
2687
- r = fs17.realpathSync.native(p);
2847
+ r = fs18.realpathSync.native(p);
2688
2848
  } catch {
2689
- r = path14.resolve(p);
2849
+ r = path15.resolve(p);
2690
2850
  }
2691
2851
  return process.platform === "win32" ? r.toLowerCase() : r;
2692
2852
  }
@@ -2728,8 +2888,8 @@ async function chooseWorktree(input) {
2728
2888
  for (const t of listTraps()) {
2729
2889
  if (typeof t.worktree !== "string")
2730
2890
  continue;
2731
- const rel = path14.relative(here, realpath(t.worktree));
2732
- if (!rel.startsWith("..") && !path14.isAbsolute(rel))
2891
+ const rel = path15.relative(here, realpath(t.worktree));
2892
+ if (!rel.startsWith("..") && !path15.isAbsolute(rel))
2733
2893
  return { reuse: false, reason: "origin worktree is a trap\u2019s" };
2734
2894
  }
2735
2895
  for (const l of ["work", "chore"]) {
@@ -3137,8 +3297,8 @@ function startWallClock(opts) {
3137
3297
  import { execFile as execFile2 } from "node:child_process";
3138
3298
  import { promisify as promisify2 } from "node:util";
3139
3299
  import { fileURLToPath } from "node:url";
3140
- import * as fs18 from "node:fs";
3141
- import * as path15 from "node:path";
3300
+ import * as fs19 from "node:fs";
3301
+ import * as path16 from "node:path";
3142
3302
  var exec2 = promisify2(execFile2);
3143
3303
  function firstLine(err) {
3144
3304
  const stderr = err && typeof err === "object" && "stderr" in err ? String(err.stderr).trim() : "";
@@ -3168,10 +3328,10 @@ function keepRemote(opts) {
3168
3328
  const note = (message) => mergeEvidence(id, lane, { note: message.slice(0, 300) });
3169
3329
  if (existingPrUrl) mergeEvidence(id, lane, { prUrl: existingPrUrl });
3170
3330
  const registerWatch = async (url) => {
3171
- const root = path15.resolve(path15.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
3172
- const localCli = path15.join(root, "apps", "cli", "dist", "main.js");
3331
+ const root = path16.resolve(path16.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
3332
+ const localCli = path16.join(root, "apps", "cli", "dist", "main.js");
3173
3333
  const args = ["watch", "add", url, "--for", id];
3174
- if (fs18.existsSync(localCli)) await command(process.execPath, [localCli, ...args], 15e3);
3334
+ if (fs19.existsSync(localCli)) await command(process.execPath, [localCli, ...args], 15e3);
3175
3335
  else await command("lobstah", args, 15e3);
3176
3336
  };
3177
3337
  let lastSeenHead;
@@ -3350,16 +3510,16 @@ function coldNote(cold, cwd, trunk) {
3350
3510
  }
3351
3511
  async function main(activeDir, lane, seams = {}) {
3352
3512
  const deps = { ...defaultDeps, ...seams };
3353
- const id = path16.basename(activeDir);
3513
+ const id = path17.basename(activeDir);
3354
3514
  if (isTrapCatch(id, lane)) throw new Error(`refusing headless runner for trap catch ${id}`);
3355
3515
  const status = (verb, note) => appendStatus(id, lane, verb, note);
3356
3516
  const cfg = loadConfig();
3357
- const descriptor = JSON.parse(fs19.readFileSync(path16.join(activeDir, "descriptor.json"), "utf8"));
3517
+ const descriptor = JSON.parse(fs20.readFileSync(path17.join(activeDir, "descriptor.json"), "utf8"));
3358
3518
  const resolved = resolveDispatch(descriptor, cfg);
3359
3519
  const repo = cfg.repos[descriptor.repo];
3360
- const briefFile = path16.join(activeDir, "brief.md");
3361
- if (!fs19.existsSync(briefFile)) fs19.writeFileSync(briefFile, descriptor.brief);
3362
- const brief = fs19.readFileSync(briefFile, "utf8");
3520
+ const briefFile = path17.join(activeDir, "brief.md");
3521
+ if (!fs20.existsSync(briefFile)) fs20.writeFileSync(briefFile, descriptor.brief);
3522
+ const brief = fs20.readFileSync(briefFile, "utf8");
3363
3523
  const attempts = Number(process.env.LOBSTAH_ATTEMPTS ?? "1");
3364
3524
  const wake = process.env.LOBSTAH_WAKE;
3365
3525
  const runnerInfo = {
@@ -3367,7 +3527,7 @@ async function main(activeDir, lane, seams = {}) {
3367
3527
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
3368
3528
  attempts
3369
3529
  };
3370
- fs19.writeFileSync(path16.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
3530
+ fs20.writeFileSync(path17.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
3371
3531
  if (descriptor.systemRepair && descriptor.pr?.url) {
3372
3532
  const state = deps.prState(descriptor.pr.url) ?? readPr(parsePrRef(descriptor.pr.url)?.key ?? "")?.state;
3373
3533
  if (state === "MERGED" || state === "CLOSED") {
@@ -3384,13 +3544,13 @@ async function main(activeDir, lane, seams = {}) {
3384
3544
  resolvedHarness: resolved.harness,
3385
3545
  envResume: process.env.LOBSTAH_RESUME
3386
3546
  });
3387
- const wtFile = path16.join(activeDir, "worktree.json");
3547
+ const wtFile = path17.join(activeDir, "worktree.json");
3388
3548
  let recorded;
3389
3549
  let choice;
3390
- if (fs19.existsSync(wtFile)) {
3391
- recorded = JSON.parse(fs19.readFileSync(wtFile, "utf8")).path;
3392
- if (!fs19.existsSync(recorded)) throw new Error(`recorded worktree ${recorded} is gone`);
3393
- } else if (fs19.existsSync(worktreePath(id))) {
3550
+ if (fs20.existsSync(wtFile)) {
3551
+ recorded = JSON.parse(fs20.readFileSync(wtFile, "utf8")).path;
3552
+ if (!fs20.existsSync(recorded)) throw new Error(`recorded worktree ${recorded} is gone`);
3553
+ } else if (fs20.existsSync(worktreePath(id))) {
3394
3554
  throw new Error(`unrecorded worktree already exists for ${id} \u2014 refusing to proceed`);
3395
3555
  } else if (descriptor.followUp && cfg.limits.reuseWorktree !== false) {
3396
3556
  choice = await deps.chooseWorktree({ id, lane, repoKey: descriptor.repo, repo, followUp: descriptor.followUp });
@@ -3424,13 +3584,13 @@ async function main(activeDir, lane, seams = {}) {
3424
3584
  type: "runner",
3425
3585
  data: { worktree: cwd, worktreeOf: choice.owner, setupRan }
3426
3586
  });
3427
- fs19.writeFileSync(wtFile, JSON.stringify({ path: cwd, of: choice.owner }, null, 2));
3587
+ fs20.writeFileSync(wtFile, JSON.stringify({ path: cwd, of: choice.owner }, null, 2));
3428
3588
  } else {
3429
3589
  cwd = await deps.allocate(repo, id, descriptor.systemRepair ? descriptor.pr?.headRefName : void 0);
3430
3590
  acquireWorktreeLock(cwd, id, lane);
3431
- fs19.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
3591
+ fs20.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
3432
3592
  }
3433
- const worktreeOf = JSON.parse(fs19.readFileSync(wtFile, "utf8")).of;
3593
+ const worktreeOf = JSON.parse(fs20.readFileSync(wtFile, "utf8")).of;
3434
3594
  mergeEvidence(id, lane, { worktree: cwd, worktreeOf });
3435
3595
  const existingPr = boundPr(descriptor, lane);
3436
3596
  if (existingPr) mergeEvidence(id, lane, { prUrl: existingPr.url });
@@ -3461,14 +3621,14 @@ async function main(activeDir, lane, seams = {}) {
3461
3621
  const envNudge = [process.env.LOBSTAH_NUDGE, wakeNote()].filter(Boolean).join("\n\n") || void 0;
3462
3622
  const planNudge = plan.cold && !envNudge ? coldNote(plan.cold, cwd, repo.trunk) : void 0;
3463
3623
  const promptWith = (nudge) => buildPrompt(brief, { id, nudge, attachments: descriptor.attachments, existingPr });
3464
- const binDir = path16.resolve(path16.dirname(fileURLToPath2(import.meta.url)), "..", "..", "..", "bin");
3465
- const workerPath = fs19.existsSync(path16.join(binDir, "lobstah")) ? `${binDir}${path16.delimiter}${process.env.PATH ?? ""}` : void 0;
3624
+ const binDir = path17.resolve(path17.dirname(fileURLToPath2(import.meta.url)), "..", "..", "..", "bin");
3625
+ const workerPath = fs20.existsSync(path17.join(binDir, "lobstah")) ? `${binDir}${path17.delimiter}${process.env.PATH ?? ""}` : void 0;
3466
3626
  let wallClockHit = false;
3467
3627
  let current;
3468
- const wallFile = path16.join(activeDir, "wallclock.json");
3628
+ const wallFile = path17.join(activeDir, "wallclock.json");
3469
3629
  const wallState = (() => {
3470
3630
  try {
3471
- return JSON.parse(fs19.readFileSync(wallFile, "utf8"));
3631
+ return JSON.parse(fs20.readFileSync(wallFile, "utf8"));
3472
3632
  } catch {
3473
3633
  return { elapsedMs: 0, windowMs: resolved.limits.wallClockSecs * 1e3 };
3474
3634
  }
@@ -3487,8 +3647,8 @@ async function main(activeDir, lane, seams = {}) {
3487
3647
  ) * 1e3;
3488
3648
  const persistWall = (elapsedMs, windowMs) => {
3489
3649
  const tmp = `${wallFile}.tmp-${process.pid}`;
3490
- fs19.writeFileSync(tmp, JSON.stringify({ ...wallState, elapsedMs, windowMs }));
3491
- fs19.renameSync(tmp, wallFile);
3650
+ fs20.writeFileSync(tmp, JSON.stringify({ ...wallState, elapsedMs, windowMs }));
3651
+ fs20.renameSync(tmp, wallFile);
3492
3652
  };
3493
3653
  const madeProgress = () => {
3494
3654
  const activity = readActivity(id, lane);
@@ -3738,13 +3898,13 @@ async function reapStarted(root = process.pid, graceMs) {
3738
3898
  var EXIT_LINGER_MS = 5e3;
3739
3899
  function runRunner(activeDirArg2, laneArg2) {
3740
3900
  const lane = laneArg2 === "chore" ? "chore" : "work";
3741
- const id = path17.basename(activeDirArg2);
3901
+ const id = path18.basename(activeDirArg2);
3742
3902
  main(activeDirArg2, lane, { reap: () => reapStarted(process.pid) }).catch((err) => {
3743
3903
  try {
3744
3904
  if (!isFinished(id, lane)) {
3745
3905
  appendStatus(id, lane, "failed", String(err instanceof Error ? err.message : err).slice(0, 500));
3746
3906
  }
3747
- fs20.renameSync(activeDirArg2, path17.join(laneDirs(lane).done, id));
3907
+ fs21.renameSync(activeDirArg2, path18.join(laneDirs(lane).done, id));
3748
3908
  } catch {
3749
3909
  }
3750
3910
  process.exitCode = 1;