@kynver-app/runtime 0.1.120 → 0.1.122

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.
@@ -317,23 +317,23 @@ function isWslHost() {
317
317
  function observeWslHostDisk(options = {}) {
318
318
  const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
319
319
  if (!wsl) return null;
320
- const path24 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
320
+ const path26 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
321
321
  const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
322
322
  const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
323
323
  const statfs = options.statfs ?? statfsSync;
324
324
  let stats;
325
325
  try {
326
- stats = statfs(path24);
326
+ stats = statfs(path26);
327
327
  } catch (error) {
328
328
  return {
329
329
  ok: false,
330
- path: path24,
330
+ path: path26,
331
331
  freeBytes: 0,
332
332
  totalBytes: 0,
333
333
  usedPercent: 100,
334
334
  warnBelowBytes,
335
335
  criticalBelowBytes,
336
- reason: `Windows host disk probe failed at ${path24}: ${error.message}`,
336
+ reason: `Windows host disk probe failed at ${path26}: ${error.message}`,
337
337
  probeError: error.message
338
338
  };
339
339
  }
@@ -347,11 +347,11 @@ function observeWslHostDisk(options = {}) {
347
347
  let reason = null;
348
348
  if (!ok) {
349
349
  const tag = criticalFree ? "critical" : "warning";
350
- reason = `Windows host disk ${path24} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
350
+ reason = `Windows host disk ${path26} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
351
351
  }
352
352
  return {
353
353
  ok,
354
- path: path24,
354
+ path: path26,
355
355
  freeBytes,
356
356
  totalBytes,
357
357
  usedPercent,
@@ -377,12 +377,12 @@ var init_wsl_host = __esm({
377
377
  // src/disk-gate.ts
378
378
  import { statfsSync as statfsSync2 } from "node:fs";
379
379
  function observeRunnerDiskGate(input = {}) {
380
- const path24 = input.diskPath?.trim() || "/";
380
+ const path26 = input.diskPath?.trim() || "/";
381
381
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
382
382
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
383
383
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
384
384
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
385
- const stats = statfsSync2(path24);
385
+ const stats = statfsSync2(path26);
386
386
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
387
387
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
388
388
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -405,7 +405,7 @@ function observeRunnerDiskGate(input = {}) {
405
405
  }
406
406
  return {
407
407
  ok,
408
- path: path24,
408
+ path: path26,
409
409
  freeBytes,
410
410
  totalBytes,
411
411
  usedPercent,
@@ -1917,11 +1917,11 @@ var init_paths = __esm({
1917
1917
 
1918
1918
  // src/cleanup.ts
1919
1919
  init_paths();
1920
- import path23 from "node:path";
1920
+ import path25 from "node:path";
1921
1921
 
1922
1922
  // src/cleanup-guards.ts
1923
1923
  init_landing_gate();
1924
- import path8 from "node:path";
1924
+ import path11 from "node:path";
1925
1925
 
1926
1926
  // src/cleanup-index-status.ts
1927
1927
  init_git();
@@ -2126,6 +2126,148 @@ function finalizeStaleRuns() {
2126
2126
  // src/cleanup-run-liveness.ts
2127
2127
  init_status();
2128
2128
 
2129
+ // src/cleanup-worker-harness-live.ts
2130
+ init_heartbeat();
2131
+
2132
+ // src/run-metadata-retention.ts
2133
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync as readdirSync4, renameSync, statSync as statSync3 } from "node:fs";
2134
+ import path9 from "node:path";
2135
+
2136
+ // src/default-repo.ts
2137
+ init_config();
2138
+ init_default_repo_discovery();
2139
+ init_path_values();
2140
+
2141
+ // src/run-metadata-retention.ts
2142
+ init_heartbeat();
2143
+ init_paths();
2144
+ init_run_store();
2145
+
2146
+ // src/worker-metadata-paths.ts
2147
+ init_paths();
2148
+ init_util();
2149
+ import path8 from "node:path";
2150
+ var NESTED_RUNS = `${path8.sep}runs${path8.sep}runs${path8.sep}`;
2151
+ function workerArtifactPaths(workerDir) {
2152
+ return {
2153
+ workerJsonPath: path8.join(workerDir, "worker.json"),
2154
+ stdoutPath: path8.join(workerDir, "stdout.jsonl"),
2155
+ stderrPath: path8.join(workerDir, "stderr.log"),
2156
+ heartbeatPath: path8.join(workerDir, "heartbeat.jsonl"),
2157
+ lastStatusPath: path8.join(workerDir, "last-status.json")
2158
+ };
2159
+ }
2160
+
2161
+ // src/run-metadata-retention.ts
2162
+ init_util();
2163
+ var RUN_METADATA_ACTIVE_SIGNAL_MS = 15 * 60 * 1e3;
2164
+ var TERMINAL_WORKER_ARCHIVE_AGE_MS = 60 * 60 * 1e3;
2165
+ function isHarnessRunMetadataPath(targetPath, harnessRoot) {
2166
+ const resolved = path9.resolve(targetPath);
2167
+ const runsDir = path9.resolve(harnessRunsDir(harnessRoot));
2168
+ const rel = path9.relative(runsDir, resolved);
2169
+ return rel !== ".." && !rel.startsWith("..") && !path9.isAbsolute(rel);
2170
+ }
2171
+ function listRunDirIds(runsDir) {
2172
+ if (!existsSync9(runsDir)) return [];
2173
+ try {
2174
+ return readdirSync4(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "runs").map((entry) => entry.name);
2175
+ } catch {
2176
+ return [];
2177
+ }
2178
+ }
2179
+ function listWorkerNamesOnDisk(runDir2) {
2180
+ const workersDir = path9.join(runDir2, "workers");
2181
+ if (!existsSync9(workersDir)) return [];
2182
+ try {
2183
+ return readdirSync4(workersDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
2184
+ } catch {
2185
+ return [];
2186
+ }
2187
+ }
2188
+ function pathRecentlyTouched(target, now, windowMs) {
2189
+ if (!existsSync9(target)) return false;
2190
+ try {
2191
+ const age = now - statSync3(target).mtimeMs;
2192
+ return Number.isFinite(age) && age >= 0 && age < windowMs;
2193
+ } catch {
2194
+ return false;
2195
+ }
2196
+ }
2197
+ function workerDirHasActiveRetentionSignals(workerDir, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
2198
+ if (!existsSync9(workerDir)) return false;
2199
+ const artifacts = workerArtifactPaths(workerDir);
2200
+ const worker = readJson(
2201
+ artifacts.workerJsonPath,
2202
+ void 0
2203
+ );
2204
+ if (worker?.status === "running" && isPidAlive(worker.pid)) return true;
2205
+ const heartbeat = parseHeartbeat(artifacts.heartbeatPath);
2206
+ if (heartbeat.lastHeartbeatAt) {
2207
+ const age = now - Date.parse(heartbeat.lastHeartbeatAt);
2208
+ if (Number.isFinite(age) && age >= 0 && age < windowMs) return true;
2209
+ }
2210
+ if (pathRecentlyTouched(artifacts.stdoutPath, now, windowMs)) return true;
2211
+ if (pathRecentlyTouched(artifacts.heartbeatPath, now, windowMs)) return true;
2212
+ return false;
2213
+ }
2214
+ function runDirHasActiveRetentionSignals(runDir2, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
2215
+ for (const name of listWorkerNamesOnDisk(runDir2)) {
2216
+ if (workerDirHasActiveRetentionSignals(path9.join(runDir2, "workers", name), now, windowMs)) {
2217
+ return true;
2218
+ }
2219
+ }
2220
+ return false;
2221
+ }
2222
+ function collectFilesystemLiveRunKeys(harnessRoot, now = Date.now()) {
2223
+ const keys = /* @__PURE__ */ new Set();
2224
+ const runsDir = harnessRunsDir(harnessRoot);
2225
+ for (const runId of listRunDirIds(runsDir)) {
2226
+ const runDir2 = path9.join(runsDir, runId);
2227
+ if (runDirHasActiveRetentionSignals(runDir2, now)) {
2228
+ keys.add(`${harnessRoot}\0${runId}`);
2229
+ }
2230
+ }
2231
+ return keys;
2232
+ }
2233
+
2234
+ // src/cleanup-worker-harness-live.ts
2235
+ init_util();
2236
+ function heartbeatContentIsFresh(worker, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
2237
+ const heartbeat = parseHeartbeat(worker.heartbeatPath);
2238
+ if (!heartbeat.lastHeartbeatAt) return false;
2239
+ const age = now - Date.parse(heartbeat.lastHeartbeatAt);
2240
+ return Number.isFinite(age) && age >= 0 && age < windowMs;
2241
+ }
2242
+ function isHarnessWorkerHarnessLive(worker, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
2243
+ if (isPidAlive(worker.pid)) return true;
2244
+ return heartbeatContentIsFresh(worker, now, windowMs);
2245
+ }
2246
+
2247
+ // src/cleanup-run-liveness.ts
2248
+ function deriveRunTerminal(indexed, ctx) {
2249
+ if (ctx) return ctx.runTerminalCache.derive(indexed.run);
2250
+ return deriveTerminalRunStatus(indexed.run);
2251
+ }
2252
+ function isWorkerProcessLive(indexed, now = Date.now()) {
2253
+ return isHarnessWorkerHarnessLive(indexed.worker, now);
2254
+ }
2255
+ function isRunStaleActive(indexed, ctx) {
2256
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
2257
+ return deriveRunTerminal(indexed, ctx) !== null;
2258
+ }
2259
+ function runBlocksWorktreeRemoval(indexed, ctx, now = Date.now()) {
2260
+ if (isWorkerProcessLive(indexed, now)) return true;
2261
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
2262
+ if (isRunStaleActive(indexed, ctx)) return false;
2263
+ const status = indexedWorktreeStatus(indexed);
2264
+ if (!isFinishedWorkerStatus(status)) {
2265
+ if (!isWorkerProcessLive(indexed, now)) return false;
2266
+ return true;
2267
+ }
2268
+ return false;
2269
+ }
2270
+
2129
2271
  // src/cleanup-completion-blocker.ts
2130
2272
  init_landing_gate();
2131
2273
  init_status();
@@ -2147,51 +2289,80 @@ function completionBlockerBlocksWorktreeRemoval(indexed, status) {
2147
2289
  return false;
2148
2290
  }
2149
2291
 
2150
- // src/cleanup-run-liveness.ts
2292
+ // src/cleanup-salvage-evidence.ts
2293
+ init_git();
2151
2294
  init_util();
2152
- var TERMINAL_WORKER_JSON_STATUSES = /* @__PURE__ */ new Set([
2153
- "done",
2154
- "exited",
2155
- "blocked",
2156
- "failed",
2157
- "abandoned"
2158
- ]);
2159
- function deriveRunTerminal(indexed, ctx) {
2160
- if (ctx) return ctx.runTerminalCache.derive(indexed.run);
2161
- return deriveTerminalRunStatus(indexed.run);
2162
- }
2163
- function isWorkerProcessLive(indexed) {
2164
- if (isPidAlive(indexed.worker.pid)) return true;
2165
- if (typeof indexed.worker.completionReportedAt === "string" && indexed.worker.completionReportedAt.trim().length > 0) {
2166
- return false;
2167
- }
2168
- const workerStatus = indexed.worker.status;
2169
- if (workerStatus && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus)) return false;
2170
- if (!indexed.worker.pid) {
2171
- if (workerStatus !== "running") return false;
2172
- return indexedWorktreeStatus(indexed).alive;
2173
- }
2174
- return false;
2175
- }
2176
- function isRunStaleActive(indexed, ctx) {
2177
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
2178
- return deriveRunTerminal(indexed, ctx) !== null;
2179
- }
2180
- function runBlocksWorktreeRemoval(indexed, ctx) {
2181
- if (isWorkerProcessLive(indexed)) return true;
2182
- const workerStatus = indexed.worker.status;
2183
- if (workerStatus && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus) && !indexed.worker.completionBlocker) {
2184
- return false;
2185
- }
2186
- const status = indexedWorktreeStatus(indexed);
2187
- if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
2188
- if (isFinishedWorkerStatus(status)) return false;
2189
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
2190
- if (isRunStaleActive(indexed, ctx)) return false;
2191
- return deriveRunTerminal(indexed, ctx) === null;
2295
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
2296
+ import path10 from "node:path";
2297
+ function salvageEvidenceDir(harnessRoot, runId, workerName) {
2298
+ return path10.join(harnessRoot, "salvage", safeSlug(runId), safeSlug(workerName));
2299
+ }
2300
+ function hasSalvageEvidence(input) {
2301
+ return existsSync10(path10.join(salvageEvidenceDir(input.harnessRoot, input.runId, input.workerName), "evidence.json"));
2302
+ }
2303
+ function writeWorktreeSalvageEvidence(input) {
2304
+ const dir = salvageEvidenceDir(input.harnessRoot, input.indexed.runId, input.indexed.workerName);
2305
+ mkdirSync4(dir, { recursive: true });
2306
+ const patchPath = path10.join(dir, "salvage.patch");
2307
+ let wrotePatch = false;
2308
+ if (existsSync10(input.indexed.worktreePath)) {
2309
+ const diff = gitCapture(input.indexed.worktreePath, ["diff", "HEAD"]);
2310
+ const staged = gitCapture(input.indexed.worktreePath, ["diff", "--cached"]);
2311
+ const patch = [diff.stdout, staged.stdout].filter((chunk) => chunk.trim()).join("\n");
2312
+ if (patch.trim()) {
2313
+ writeFileSync3(patchPath, patch.endsWith("\n") ? patch : `${patch}
2314
+ `);
2315
+ wrotePatch = true;
2316
+ }
2317
+ }
2318
+ const record = {
2319
+ capturedAt: new Date(input.now ?? Date.now()).toISOString(),
2320
+ skipReason: input.skipReason,
2321
+ runId: input.indexed.runId,
2322
+ workerName: input.indexed.workerName,
2323
+ worktreePath: input.indexed.worktreePath,
2324
+ taskId: input.indexed.worker.taskId,
2325
+ agentOsId: input.indexed.worker.agentOsId,
2326
+ branch: input.indexed.worker.branch,
2327
+ completionBlocker: input.indexed.worker.completionBlocker,
2328
+ finalResult: input.status.finalResult,
2329
+ changedFiles: input.status.changedFiles,
2330
+ gitAncestry: input.status.gitAncestry,
2331
+ prUrl: input.status.prUrl ?? null,
2332
+ ...wrotePatch ? { patchPath } : {}
2333
+ };
2334
+ writeFileSync3(path10.join(dir, "evidence.json"), `${JSON.stringify(record, null, 2)}
2335
+ `);
2336
+ return record;
2192
2337
  }
2193
2338
 
2194
2339
  // src/cleanup-guards.ts
2340
+ var SALVAGE_ELIGIBLE_SKIP_REASONS = /* @__PURE__ */ new Set([
2341
+ "completion_blocked",
2342
+ "dirty_worktree",
2343
+ "pr_or_unmerged_commits",
2344
+ "landing_blocked"
2345
+ ]);
2346
+ function maybeSalvageBlockedWorktree(input) {
2347
+ if (!SALVAGE_ELIGIBLE_SKIP_REASONS.has(input.skipReason)) return false;
2348
+ if (isWorkerProcessLive(input.indexed, input.now)) return false;
2349
+ if (hasSalvageEvidence({
2350
+ harnessRoot: input.harnessRoot,
2351
+ runId: input.indexed.runId,
2352
+ workerName: input.indexed.workerName
2353
+ })) {
2354
+ return true;
2355
+ }
2356
+ if (!input.writeSalvageEvidence) return false;
2357
+ writeWorktreeSalvageEvidence({
2358
+ harnessRoot: input.harnessRoot,
2359
+ indexed: input.indexed,
2360
+ skipReason: input.skipReason,
2361
+ status: input.status,
2362
+ now: input.now
2363
+ });
2364
+ return true;
2365
+ }
2195
2366
  function effectiveWorktreeAgeMs(input) {
2196
2367
  const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
2197
2368
  if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
@@ -2206,8 +2377,18 @@ function effectiveWorktreeAgeMs(input) {
2206
2377
  }
2207
2378
  return worktreesAgeMs;
2208
2379
  }
2380
+ function resolveHarnessRoot2(input, indexed) {
2381
+ if (input.harnessRoot?.trim()) return path11.resolve(input.harnessRoot);
2382
+ const workerDir = indexed.worker.workerDir;
2383
+ if (!workerDir) return null;
2384
+ const runsMarker = `${path11.sep}runs${path11.sep}`;
2385
+ const idx = workerDir.indexOf(runsMarker);
2386
+ if (idx < 0) return null;
2387
+ return workerDir.slice(0, idx + runsMarker.length - 1);
2388
+ }
2209
2389
  function skipWorktreeRemoval(input) {
2210
2390
  const { indexed, includeOrphans, worktreesAgeMs, ageMs, orphanSafety, worktreeRemovalGuard } = input;
2391
+ const now = input.now ?? Date.now();
2211
2392
  if (!indexed) {
2212
2393
  if (!includeOrphans) return "orphan_without_flag";
2213
2394
  return orphanSafety ?? null;
@@ -2215,25 +2396,55 @@ function skipWorktreeRemoval(input) {
2215
2396
  const ageThresholdMs = effectiveWorktreeAgeMs(input);
2216
2397
  if (worktreesAgeMs <= 0 && !includeOrphans && ageThresholdMs <= 0) return "worktrees_disabled";
2217
2398
  if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
2218
- if (isWorkerProcessLive(indexed)) return "active_worker";
2399
+ if (isWorkerProcessLive(indexed, now)) return "active_worker";
2400
+ const status = resolveWorktreeGuardStatus(indexed, input.liveness);
2401
+ const salvageHarnessRoot = resolveHarnessRoot2(input, indexed);
2402
+ const salvageOrBlock = (skipReason) => {
2403
+ if (salvageHarnessRoot && maybeSalvageBlockedWorktree({
2404
+ indexed,
2405
+ harnessRoot: salvageHarnessRoot,
2406
+ skipReason,
2407
+ status,
2408
+ now,
2409
+ writeSalvageEvidence: input.writeSalvageEvidence === true
2410
+ })) {
2411
+ return null;
2412
+ }
2413
+ return skipReason;
2414
+ };
2415
+ if (completionBlockerBlocksWorktreeRemoval(indexed, status)) {
2416
+ const blocked = salvageOrBlock("completion_blocked");
2417
+ if (blocked) return blocked;
2418
+ }
2219
2419
  if (indexedWorktreeHasMaterialChanges(indexed, input.liveness?.gitStatusCache)) {
2220
- return "dirty_worktree";
2420
+ const blocked = salvageOrBlock("dirty_worktree");
2421
+ if (blocked) return blocked;
2221
2422
  }
2222
2423
  const ahead = input.liveness?.gitRevCache?.countAheadOfMain(input.worktreePath);
2223
- if (ahead !== null && ahead !== void 0 && ahead > 0) return "pr_or_unmerged_commits";
2224
- const status = resolveWorktreeGuardStatus(indexed, input.liveness);
2225
- if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
2226
- if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
2424
+ if (ahead !== null && ahead !== void 0 && ahead > 0) {
2425
+ const blocked = salvageOrBlock("pr_or_unmerged_commits");
2426
+ if (blocked) return blocked;
2427
+ }
2428
+ if (runBlocksWorktreeRemoval(indexed, input.liveness, now)) return "run_still_active";
2227
2429
  if (!isFinishedWorkerStatus(status)) return "run_still_active";
2228
- if (isPrOrUnmergedWork(status)) return "pr_or_unmerged_commits";
2229
- if (materialWorktreeChanges(status.changedFiles).length > 0) return "dirty_worktree";
2430
+ if (isPrOrUnmergedWork(status)) {
2431
+ const blocked = salvageOrBlock("pr_or_unmerged_commits");
2432
+ if (blocked) return blocked;
2433
+ }
2434
+ if (materialWorktreeChanges(status.changedFiles).length > 0) {
2435
+ const blocked = salvageOrBlock("dirty_worktree");
2436
+ if (blocked) return blocked;
2437
+ }
2230
2438
  const landing = assessWorkerLanding({
2231
2439
  finalResult: status.finalResult,
2232
2440
  changedFiles: status.changedFiles,
2233
2441
  gitAncestry: status.gitAncestry,
2234
2442
  prUrl: prUrlFromFinalResult(status.finalResult)
2235
2443
  });
2236
- if (landing.blocked) return "landing_blocked";
2444
+ if (landing.blocked) {
2445
+ const blocked = salvageOrBlock("landing_blocked");
2446
+ if (blocked) return blocked;
2447
+ }
2237
2448
  if (worktreeRemovalGuard && input.worktreePath) {
2238
2449
  const overlay = worktreeRemovalGuard({
2239
2450
  worktreePath: input.worktreePath,
@@ -2250,7 +2461,7 @@ function skipWorktreeRemoval(input) {
2250
2461
  function skipDependencyCacheRemoval(input) {
2251
2462
  const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
2252
2463
  if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
2253
- if (activeWorktreePaths.has(path8.resolve(worktreePath))) return "active_worker";
2464
+ if (activeWorktreePaths.has(path11.resolve(worktreePath))) return "active_worker";
2254
2465
  if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
2255
2466
  if (indexed && indexedWorktreeHasMaterialChanges(indexed, input.gitStatusCache)) {
2256
2467
  return "dirty_worktree";
@@ -2279,11 +2490,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
2279
2490
  function collectPreservedLivePaths(actions, skips) {
2280
2491
  const out = [];
2281
2492
  const seen = /* @__PURE__ */ new Set();
2282
- const push = (path24, reason, detail) => {
2283
- const key = `${path24}\0${reason}`;
2493
+ const push = (path26, reason, detail) => {
2494
+ const key = `${path26}\0${reason}`;
2284
2495
  if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
2285
2496
  seen.add(key);
2286
- out.push({ path: path24, reason, ...detail ? { detail } : {} });
2497
+ out.push({ path: path26, reason, ...detail ? { detail } : {} });
2287
2498
  };
2288
2499
  for (const skip of skips) {
2289
2500
  if (!LIVE_SKIP_REASONS.has(skip.reason)) continue;
@@ -2298,134 +2509,16 @@ function collectPreservedLivePaths(actions, skips) {
2298
2509
  }
2299
2510
 
2300
2511
  // src/cleanup-run-directory.ts
2301
- import { existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
2302
- import path12 from "node:path";
2512
+ import { existsSync as existsSync11, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
2513
+ import path13 from "node:path";
2303
2514
 
2304
2515
  // src/cleanup-active-worktrees.ts
2305
2516
  init_run_store();
2306
2517
  init_paths();
2307
- import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
2308
- import path11 from "node:path";
2309
-
2310
- // src/run-metadata-retention.ts
2311
- import { existsSync as existsSync9, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
2312
- import path10 from "node:path";
2313
-
2314
- // src/default-repo.ts
2315
- init_config();
2316
- init_default_repo_discovery();
2317
- init_path_values();
2318
-
2319
- // src/run-metadata-retention.ts
2320
- init_heartbeat();
2321
- init_paths();
2322
- init_run_store();
2323
-
2324
- // src/worker-metadata-paths.ts
2325
- init_paths();
2326
- init_util();
2327
- import path9 from "node:path";
2328
- var NESTED_RUNS = `${path9.sep}runs${path9.sep}runs${path9.sep}`;
2329
- function workerArtifactPaths(workerDir) {
2330
- return {
2331
- workerJsonPath: path9.join(workerDir, "worker.json"),
2332
- stdoutPath: path9.join(workerDir, "stdout.jsonl"),
2333
- stderrPath: path9.join(workerDir, "stderr.log"),
2334
- heartbeatPath: path9.join(workerDir, "heartbeat.jsonl"),
2335
- lastStatusPath: path9.join(workerDir, "last-status.json")
2336
- };
2337
- }
2338
-
2339
- // src/run-metadata-retention.ts
2340
- init_util();
2341
- var RUN_METADATA_ACTIVE_SIGNAL_MS = 15 * 60 * 1e3;
2342
- function isHarnessRunMetadataPath(targetPath, harnessRoot) {
2343
- const resolved = path10.resolve(targetPath);
2344
- const runsDir = path10.resolve(harnessRunsDir(harnessRoot));
2345
- const rel = path10.relative(runsDir, resolved);
2346
- return rel !== ".." && !rel.startsWith("..") && !path10.isAbsolute(rel);
2347
- }
2348
- function listRunDirIds(runsDir) {
2349
- if (!existsSync9(runsDir)) return [];
2350
- try {
2351
- return readdirSync4(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "runs").map((entry) => entry.name);
2352
- } catch {
2353
- return [];
2354
- }
2355
- }
2356
- function listWorkerNamesOnDisk(runDir2) {
2357
- const workersDir = path10.join(runDir2, "workers");
2358
- if (!existsSync9(workersDir)) return [];
2359
- try {
2360
- return readdirSync4(workersDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
2361
- } catch {
2362
- return [];
2363
- }
2364
- }
2365
- function pathRecentlyTouched(target, now, windowMs) {
2366
- if (!existsSync9(target)) return false;
2367
- try {
2368
- const age = now - statSync3(target).mtimeMs;
2369
- return Number.isFinite(age) && age >= 0 && age < windowMs;
2370
- } catch {
2371
- return false;
2372
- }
2373
- }
2374
- function workerDirHasActiveRetentionSignals(workerDir, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
2375
- if (!existsSync9(workerDir)) return false;
2376
- const artifacts = workerArtifactPaths(workerDir);
2377
- const worker = readJson(
2378
- artifacts.workerJsonPath,
2379
- void 0
2380
- );
2381
- if (worker?.status === "running" && isPidAlive(worker.pid)) return true;
2382
- const heartbeat = parseHeartbeat(artifacts.heartbeatPath);
2383
- if (heartbeat.lastHeartbeatAt) {
2384
- const age = now - Date.parse(heartbeat.lastHeartbeatAt);
2385
- if (Number.isFinite(age) && age >= 0 && age < windowMs) return true;
2386
- }
2387
- if (pathRecentlyTouched(artifacts.stdoutPath, now, windowMs)) return true;
2388
- if (pathRecentlyTouched(artifacts.heartbeatPath, now, windowMs)) return true;
2389
- return false;
2390
- }
2391
- function runDirHasActiveRetentionSignals(runDir2, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
2392
- for (const name of listWorkerNamesOnDisk(runDir2)) {
2393
- if (workerDirHasActiveRetentionSignals(path10.join(runDir2, "workers", name), now, windowMs)) {
2394
- return true;
2395
- }
2396
- }
2397
- return false;
2398
- }
2399
- function collectFilesystemLiveRunKeys(harnessRoot, now = Date.now()) {
2400
- const keys = /* @__PURE__ */ new Set();
2401
- const runsDir = harnessRunsDir(harnessRoot);
2402
- for (const runId of listRunDirIds(runsDir)) {
2403
- const runDir2 = path10.join(runsDir, runId);
2404
- if (runDirHasActiveRetentionSignals(runDir2, now)) {
2405
- keys.add(`${harnessRoot}\0${runId}`);
2406
- }
2407
- }
2408
- return keys;
2409
- }
2410
-
2411
- // src/cleanup-active-worktrees.ts
2518
+ import path12 from "node:path";
2412
2519
  init_util();
2413
- function workerHasRecentHarnessActivity(worker, now) {
2414
- const paths = [worker.heartbeatPath, worker.stdoutPath, worker.stderrPath];
2415
- for (const target of paths) {
2416
- if (!existsSync10(target)) continue;
2417
- try {
2418
- const age = now - statSync4(target).mtimeMs;
2419
- if (Number.isFinite(age) && age >= 0 && age < RUN_METADATA_ACTIVE_SIGNAL_MS) return true;
2420
- } catch {
2421
- }
2422
- }
2423
- return false;
2424
- }
2425
2520
  function isActiveHarnessWorker2(worker, now) {
2426
- if (isPidAlive(worker.pid)) return true;
2427
- if (worker.status === "running" && workerHasRecentHarnessActivity(worker, now)) return true;
2428
- return false;
2521
+ return isHarnessWorkerHarnessLive(worker, now);
2429
2522
  }
2430
2523
  function collectActiveWorktreeGuards(harnessRoots, now = Date.now()) {
2431
2524
  const activeWorktreePaths = /* @__PURE__ */ new Set();
@@ -2435,11 +2528,11 @@ function collectActiveWorktreeGuards(harnessRoots, now = Date.now()) {
2435
2528
  let runHasLive = false;
2436
2529
  for (const name of Object.keys(run.workers || {})) {
2437
2530
  const worker = readJson(
2438
- path11.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
2531
+ path12.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
2439
2532
  void 0
2440
2533
  );
2441
2534
  if (!worker?.worktreePath) continue;
2442
- const worktreePath = path11.resolve(worker.worktreePath);
2535
+ const worktreePath = path12.resolve(worker.worktreePath);
2443
2536
  if (!isActiveHarnessWorker2(worker, now)) continue;
2444
2537
  runHasLive = true;
2445
2538
  activeWorktreePaths.add(worktreePath);
@@ -2461,20 +2554,20 @@ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
2461
2554
  init_util();
2462
2555
  function pathAgeMs(target, now) {
2463
2556
  try {
2464
- const mtime = statSync5(target).mtimeMs;
2557
+ const mtime = statSync4(target).mtimeMs;
2465
2558
  return Math.max(0, now - mtime);
2466
2559
  } catch {
2467
2560
  return 0;
2468
2561
  }
2469
2562
  }
2470
2563
  function loadRunStatus(harnessRoot, runId) {
2471
- const runPath = path12.join(harnessRoot, "runs", runId, "run.json");
2564
+ const runPath = path13.join(harnessRoot, "runs", runId, "run.json");
2472
2565
  if (!existsSync11(runPath)) return null;
2473
2566
  return readJson(runPath, null);
2474
2567
  }
2475
2568
  function runDirectoryIsEmpty(runPath) {
2476
2569
  try {
2477
- const entries = readdirSync6(runPath);
2570
+ const entries = readdirSync5(runPath);
2478
2571
  return entries.length === 0;
2479
2572
  } catch {
2480
2573
  return false;
@@ -2498,7 +2591,7 @@ function scanStaleRunDirectoryCandidates(opts) {
2498
2591
  const candidates = [];
2499
2592
  let entries;
2500
2593
  try {
2501
- entries = readdirSync6(opts.worktreesDir, { withFileTypes: true });
2594
+ entries = readdirSync5(opts.worktreesDir, { withFileTypes: true });
2502
2595
  } catch {
2503
2596
  return [];
2504
2597
  }
@@ -2506,7 +2599,7 @@ function scanStaleRunDirectoryCandidates(opts) {
2506
2599
  if (!runEntry.isDirectory()) continue;
2507
2600
  const runId = runEntry.name;
2508
2601
  if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
2509
- const runPath = path12.join(opts.worktreesDir, runId);
2602
+ const runPath = path13.join(opts.worktreesDir, runId);
2510
2603
  if (!runDirectoryIsEmpty(runPath)) continue;
2511
2604
  candidates.push({
2512
2605
  kind: "remove_run_directory",
@@ -2526,8 +2619,8 @@ import { existsSync as existsSync14, rmSync as rmSync2 } from "node:fs";
2526
2619
 
2527
2620
  // src/cleanup-dir-size.ts
2528
2621
  import { execFileSync } from "node:child_process";
2529
- import { existsSync as existsSync12, readdirSync as readdirSync7, statSync as statSync6 } from "node:fs";
2530
- import path13 from "node:path";
2622
+ import { existsSync as existsSync12, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
2623
+ import path14 from "node:path";
2531
2624
  var DEFAULT_DU_TIMEOUT_MS = 2500;
2532
2625
  function directorySizeBytesDu(root, timeoutMs = DEFAULT_DU_TIMEOUT_MS) {
2533
2626
  if (!existsSync12(root)) return 0;
@@ -2555,16 +2648,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
2555
2648
  const current = stack.pop();
2556
2649
  let entries;
2557
2650
  try {
2558
- entries = readdirSync7(current);
2651
+ entries = readdirSync6(current);
2559
2652
  } catch {
2560
2653
  continue;
2561
2654
  }
2562
2655
  for (const name of entries) {
2563
2656
  if (seen++ > maxEntries) return null;
2564
- const full = path13.join(current, name);
2657
+ const full = path14.join(current, name);
2565
2658
  let st;
2566
2659
  try {
2567
- st = statSync6(full);
2660
+ st = statSync5(full);
2568
2661
  } catch {
2569
2662
  continue;
2570
2663
  }
@@ -2580,7 +2673,7 @@ import { existsSync as existsSync13, rmSync } from "node:fs";
2580
2673
  init_paths();
2581
2674
 
2582
2675
  // src/cleanup-path-ownership.ts
2583
- import { lstatSync, readdirSync as readdirSync8 } from "node:fs";
2676
+ import { lstatSync, readdirSync as readdirSync7 } from "node:fs";
2584
2677
  function readPathOwnership(targetPath) {
2585
2678
  try {
2586
2679
  const st = lstatSync(targetPath);
@@ -2597,7 +2690,7 @@ function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
2597
2690
  if (!root) return false;
2598
2691
  if (root.foreign) return true;
2599
2692
  try {
2600
- const names = readdirSync8(targetPath);
2693
+ const names = readdirSync7(targetPath);
2601
2694
  let checked = 0;
2602
2695
  for (const name of names) {
2603
2696
  if (checked >= maxEntries) break;
@@ -2613,20 +2706,20 @@ function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
2613
2706
 
2614
2707
  // src/cleanup-privileged-remove.ts
2615
2708
  import { spawnSync as spawnSync2 } from "node:child_process";
2616
- import path15 from "node:path";
2709
+ import path16 from "node:path";
2617
2710
 
2618
2711
  // src/cleanup-harness-path-validate.ts
2619
- import path14 from "node:path";
2712
+ import path15 from "node:path";
2620
2713
  function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
2621
- const resolved = path14.resolve(targetPath);
2622
- const suffix = `${path14.sep}${cacheDirName}`;
2714
+ const resolved = path15.resolve(targetPath);
2715
+ const suffix = `${path15.sep}${cacheDirName}`;
2623
2716
  const cachePath = resolved.endsWith(suffix) ? resolved : null;
2624
2717
  if (!cachePath) return "path_outside_harness";
2625
- const rel = path14.relative(worktreesDir, cachePath);
2626
- if (rel.startsWith("..") || path14.isAbsolute(rel)) return "path_outside_harness";
2627
- const parts = rel.split(path14.sep);
2718
+ const rel = path15.relative(worktreesDir, cachePath);
2719
+ if (rel.startsWith("..") || path15.isAbsolute(rel)) return "path_outside_harness";
2720
+ const parts = rel.split(path15.sep);
2628
2721
  if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
2629
- if (!resolved.startsWith(path14.resolve(harnessRoot))) return "path_outside_harness";
2722
+ if (!resolved.startsWith(path15.resolve(harnessRoot))) return "path_outside_harness";
2630
2723
  return null;
2631
2724
  }
2632
2725
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
@@ -2636,16 +2729,16 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
2636
2729
  return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
2637
2730
  }
2638
2731
  function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
2639
- const resolved = path14.resolve(targetPath);
2640
- const relToWt = path14.relative(worktreesDir, resolved);
2641
- if (relToWt.startsWith("..") || path14.isAbsolute(relToWt)) return "path_outside_harness";
2642
- const parts = relToWt.split(path14.sep);
2732
+ const resolved = path15.resolve(targetPath);
2733
+ const relToWt = path15.relative(worktreesDir, resolved);
2734
+ if (relToWt.startsWith("..") || path15.isAbsolute(relToWt)) return "path_outside_harness";
2735
+ const parts = relToWt.split(path15.sep);
2643
2736
  if (parts.length < 3) return "path_outside_harness";
2644
- if (!resolved.startsWith(path14.resolve(harnessRoot))) return "path_outside_harness";
2737
+ if (!resolved.startsWith(path15.resolve(harnessRoot))) return "path_outside_harness";
2645
2738
  return null;
2646
2739
  }
2647
2740
  function isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir) {
2648
- const resolved = path14.resolve(targetPath);
2741
+ const resolved = path15.resolve(targetPath);
2649
2742
  return isHarnessNodeModulesPath(resolved, harnessRoot, worktreesDir) === null || isHarnessNextCachePath(resolved, harnessRoot, worktreesDir) === null || isHarnessBuildCachePath(resolved, harnessRoot, worktreesDir) === null;
2650
2743
  }
2651
2744
 
@@ -2679,12 +2772,12 @@ function tryPrivilegedReclaimHarnessCache(targetPath, harnessRoot, worktreesDir)
2679
2772
  "chown",
2680
2773
  "-R",
2681
2774
  `${effectiveUid}:${effectiveGid}`,
2682
- path15.resolve(targetPath)
2775
+ path16.resolve(targetPath)
2683
2776
  ]);
2684
2777
  if (chown.ok) {
2685
2778
  return { ok: true, method: "chown_then_rm" };
2686
2779
  }
2687
- const rm = runSudoNonInteractive(["rm", "-rf", path15.resolve(targetPath)]);
2780
+ const rm = runSudoNonInteractive(["rm", "-rf", path16.resolve(targetPath)]);
2688
2781
  if (rm.ok) {
2689
2782
  return { ok: true, method: "sudo_rm" };
2690
2783
  }
@@ -2885,27 +2978,27 @@ function removeWorktree(candidate, execute) {
2885
2978
  }
2886
2979
 
2887
2980
  // src/cleanup-scan.ts
2888
- import { existsSync as existsSync15, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
2889
- import path16 from "node:path";
2981
+ import { existsSync as existsSync15, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
2982
+ import path17 from "node:path";
2890
2983
  function pathAgeMs2(target, now) {
2891
2984
  try {
2892
- const mtime = statSync7(target).mtimeMs;
2985
+ const mtime = statSync6(target).mtimeMs;
2893
2986
  return Math.max(0, now - mtime);
2894
2987
  } catch {
2895
2988
  return 0;
2896
2989
  }
2897
2990
  }
2898
2991
  function isPathInside(child, parent) {
2899
- const rel = path16.relative(parent, child);
2900
- return rel === "" || !rel.startsWith("..") && !path16.isAbsolute(rel);
2992
+ const rel = path17.relative(parent, child);
2993
+ return rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel);
2901
2994
  }
2902
2995
  function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
2903
2996
  const out = [];
2904
2997
  for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
2905
2998
  if (rel === ".next") continue;
2906
- const target = path16.join(worktreePath, rel);
2999
+ const target = path17.join(worktreePath, rel);
2907
3000
  if (!existsSync15(target)) continue;
2908
- const resolved = path16.resolve(target);
3001
+ const resolved = path17.resolve(target);
2909
3002
  if (seen.has(resolved)) continue;
2910
3003
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
2911
3004
  seen.add(resolved);
@@ -2935,12 +3028,12 @@ function scanBuildCacheCandidates(opts) {
2935
3028
  );
2936
3029
  }
2937
3030
  if (!opts.includeOrphans || !existsSync15(opts.worktreesDir)) return candidates;
2938
- for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
3031
+ for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
2939
3032
  if (!runEntry.isDirectory()) continue;
2940
- const runPath = path16.join(opts.worktreesDir, runEntry.name);
2941
- for (const workerEntry of readdirSync9(runPath, { withFileTypes: true })) {
3033
+ const runPath = path17.join(opts.worktreesDir, runEntry.name);
3034
+ for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
2942
3035
  if (!workerEntry.isDirectory()) continue;
2943
- const worktreePath = path16.join(runPath, workerEntry.name);
3036
+ const worktreePath = path17.join(runPath, workerEntry.name);
2944
3037
  candidates.push(
2945
3038
  ...collectBuildCacheForWorktree(worktreePath, opts, seen, {
2946
3039
  runId: runEntry.name,
@@ -2978,21 +3071,21 @@ function scanWorktreeCandidates(opts) {
2978
3071
  if (!orphanEnabled || !existsSync15(opts.worktreesDir)) return candidates;
2979
3072
  const indexedPaths = /* @__PURE__ */ new Set();
2980
3073
  for (const entry of opts.index.values()) {
2981
- indexedPaths.add(path16.resolve(entry.worktreePath));
3074
+ indexedPaths.add(path17.resolve(entry.worktreePath));
2982
3075
  }
2983
- for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
3076
+ for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
2984
3077
  if (!runEntry.isDirectory()) continue;
2985
3078
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
2986
- const runPath = path16.join(opts.worktreesDir, runEntry.name);
3079
+ const runPath = path17.join(opts.worktreesDir, runEntry.name);
2987
3080
  let workerEntries;
2988
3081
  try {
2989
- workerEntries = readdirSync9(runPath, { withFileTypes: true });
3082
+ workerEntries = readdirSync8(runPath, { withFileTypes: true });
2990
3083
  } catch {
2991
3084
  continue;
2992
3085
  }
2993
3086
  for (const workerEntry of workerEntries) {
2994
3087
  if (!workerEntry.isDirectory()) continue;
2995
- const worktreePath = path16.resolve(path16.join(runPath, workerEntry.name));
3088
+ const worktreePath = path17.resolve(path17.join(runPath, workerEntry.name));
2996
3089
  if (seen.has(worktreePath)) continue;
2997
3090
  if (indexedPaths.has(worktreePath)) continue;
2998
3091
  if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
@@ -3011,27 +3104,27 @@ function scanWorktreeCandidates(opts) {
3011
3104
  }
3012
3105
 
3013
3106
  // src/cleanup-dependency-scan.ts
3014
- import { existsSync as existsSync16, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
3015
- import path17 from "node:path";
3107
+ import { existsSync as existsSync16, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
3108
+ import path18 from "node:path";
3016
3109
  var DEPENDENCY_CACHE_DIRS = [
3017
3110
  { dirName: "node_modules", kind: "remove_node_modules" },
3018
3111
  { dirName: ".next", kind: "remove_next_cache" }
3019
3112
  ];
3020
3113
  function pathAgeMs3(target, now) {
3021
3114
  try {
3022
- const mtime = statSync8(target).mtimeMs;
3115
+ const mtime = statSync7(target).mtimeMs;
3023
3116
  return Math.max(0, now - mtime);
3024
3117
  } catch {
3025
3118
  return 0;
3026
3119
  }
3027
3120
  }
3028
3121
  function isPathInside2(child, parent) {
3029
- const rel = path17.relative(parent, child);
3030
- return rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel);
3122
+ const rel = path18.relative(parent, child);
3123
+ return rel === "" || !rel.startsWith("..") && !path18.isAbsolute(rel);
3031
3124
  }
3032
3125
  function pushCandidate(candidates, seen, opts, targetPath, kind, meta) {
3033
3126
  if (!existsSync16(targetPath)) return;
3034
- const resolved = path17.resolve(targetPath);
3127
+ const resolved = path18.resolve(targetPath);
3035
3128
  if (seen.has(resolved)) return;
3036
3129
  if (!isPathInside2(resolved, opts.harnessRoot)) return;
3037
3130
  seen.add(resolved);
@@ -3048,7 +3141,7 @@ function pushCandidate(candidates, seen, opts, targetPath, kind, meta) {
3048
3141
  }
3049
3142
  function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
3050
3143
  for (const entry of DEPENDENCY_CACHE_DIRS) {
3051
- pushCandidate(candidates, seen, opts, path17.join(worktreePath, entry.dirName), entry.kind, meta);
3144
+ pushCandidate(candidates, seen, opts, path18.join(worktreePath, entry.dirName), entry.kind, meta);
3052
3145
  }
3053
3146
  }
3054
3147
  function scanDependencyCacheCandidates(opts) {
@@ -3063,19 +3156,19 @@ function scanDependencyCacheCandidates(opts) {
3063
3156
  });
3064
3157
  }
3065
3158
  if (!opts.includeOrphans || !existsSync16(opts.worktreesDir)) return candidates;
3066
- for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
3159
+ for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
3067
3160
  if (!runEntry.isDirectory()) continue;
3068
3161
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
3069
- const runPath = path17.join(opts.worktreesDir, runEntry.name);
3162
+ const runPath = path18.join(opts.worktreesDir, runEntry.name);
3070
3163
  let workerEntries;
3071
3164
  try {
3072
- workerEntries = readdirSync10(runPath, { withFileTypes: true });
3165
+ workerEntries = readdirSync9(runPath, { withFileTypes: true });
3073
3166
  } catch {
3074
3167
  continue;
3075
3168
  }
3076
3169
  for (const workerEntry of workerEntries) {
3077
3170
  if (!workerEntry.isDirectory()) continue;
3078
- const worktreePath = path17.join(runPath, workerEntry.name);
3171
+ const worktreePath = path18.join(runPath, workerEntry.name);
3079
3172
  scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
3080
3173
  runId: runEntry.name,
3081
3174
  worker: workerEntry.name
@@ -3087,11 +3180,11 @@ function scanDependencyCacheCandidates(opts) {
3087
3180
 
3088
3181
  // src/cleanup-duplicate-worktrees.ts
3089
3182
  init_git();
3090
- import { existsSync as existsSync17, statSync as statSync9 } from "node:fs";
3091
- import path18 from "node:path";
3183
+ import { existsSync as existsSync17, statSync as statSync8 } from "node:fs";
3184
+ import path19 from "node:path";
3092
3185
  function pathAgeMs4(target, now) {
3093
3186
  try {
3094
- const mtime = statSync9(target).mtimeMs;
3187
+ const mtime = statSync8(target).mtimeMs;
3095
3188
  return Math.max(0, now - mtime);
3096
3189
  } catch {
3097
3190
  return 0;
@@ -3118,32 +3211,24 @@ function parseWorktreePorcelain(output) {
3118
3211
  return records;
3119
3212
  }
3120
3213
  function isUnderWorktreesDir(worktreePath, worktreesDir) {
3121
- const rel = path18.relative(path18.resolve(worktreesDir), path18.resolve(worktreePath));
3122
- return rel !== "" && !rel.startsWith("..") && !path18.isAbsolute(rel);
3123
- }
3124
- function isCleanWorktree(worktreePath, repoRoot) {
3125
- try {
3126
- const porcelain = git(repoRoot, ["-C", worktreePath, "status", "--porcelain"], {
3127
- allowFailure: true
3128
- });
3129
- return !String(porcelain || "").trim();
3130
- } catch {
3131
- return false;
3132
- }
3214
+ const rel = path19.relative(path19.resolve(worktreesDir), path19.resolve(worktreePath));
3215
+ return rel !== "" && !rel.startsWith("..") && !path19.isAbsolute(rel);
3133
3216
  }
3217
+ var MAX_DUPLICATE_WORKTREE_CANDIDATES_PER_REPO = 200;
3134
3218
  function scanDuplicateWorktreeCandidates(opts) {
3135
3219
  if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return [];
3136
3220
  const repos = /* @__PURE__ */ new Set();
3137
3221
  for (const entry of opts.index.values()) {
3138
- if (entry.run.repo) repos.add(path18.resolve(entry.run.repo));
3222
+ if (entry.run.repo) repos.add(path19.resolve(entry.run.repo));
3139
3223
  }
3140
3224
  const indexedPaths = /* @__PURE__ */ new Set();
3141
3225
  for (const entry of opts.index.values()) {
3142
- indexedPaths.add(path18.resolve(entry.worktreePath));
3226
+ indexedPaths.add(path19.resolve(entry.worktreePath));
3143
3227
  }
3144
3228
  const candidates = [];
3145
3229
  const seen = /* @__PURE__ */ new Set();
3146
3230
  for (const repoRoot of repos) {
3231
+ let repoCandidateCount = 0;
3147
3232
  let porcelain;
3148
3233
  try {
3149
3234
  porcelain = git(repoRoot, ["worktree", "list", "--porcelain"], { allowFailure: true });
@@ -3152,18 +3237,19 @@ function scanDuplicateWorktreeCandidates(opts) {
3152
3237
  }
3153
3238
  const worktrees = parseWorktreePorcelain(porcelain);
3154
3239
  for (const wt of worktrees) {
3155
- const resolved = path18.resolve(wt.path);
3156
- if (resolved === path18.resolve(repoRoot)) continue;
3240
+ if (repoCandidateCount >= MAX_DUPLICATE_WORKTREE_CANDIDATES_PER_REPO) break;
3241
+ const resolved = path19.resolve(wt.path);
3242
+ if (resolved === path19.resolve(repoRoot)) continue;
3157
3243
  if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
3158
3244
  if (indexedPaths.has(resolved)) continue;
3159
3245
  if (seen.has(resolved)) continue;
3160
3246
  if (!existsSync17(resolved)) continue;
3161
- if (!isCleanWorktree(resolved, repoRoot)) continue;
3162
- const rel = path18.relative(opts.worktreesDir, resolved);
3163
- const parts = rel.split(path18.sep);
3247
+ const rel = path19.relative(opts.worktreesDir, resolved);
3248
+ const parts = rel.split(path19.sep);
3164
3249
  const runId = parts[0];
3165
3250
  const worker = parts[1] ?? "unknown";
3166
3251
  seen.add(resolved);
3252
+ repoCandidateCount += 1;
3167
3253
  candidates.push({
3168
3254
  kind: "remove_worktree",
3169
3255
  path: resolved,
@@ -3181,12 +3267,22 @@ function scanDuplicateWorktreeCandidates(opts) {
3181
3267
  // src/cleanup-worktree-index.ts
3182
3268
  init_run_store();
3183
3269
  init_util();
3184
- import path19 from "node:path";
3270
+ import path20 from "node:path";
3271
+ function filterWorktreeIndexForRoot(index, harnessRoot) {
3272
+ const resolvedRoot = path20.resolve(harnessRoot);
3273
+ const scoped = /* @__PURE__ */ new Map();
3274
+ for (const [key, entry] of index) {
3275
+ const entryRoot = entry.harnessRoot ? path20.resolve(entry.harnessRoot) : null;
3276
+ if (entryRoot && entryRoot !== resolvedRoot) continue;
3277
+ scoped.set(key, entry);
3278
+ }
3279
+ return scoped;
3280
+ }
3185
3281
  function buildWorktreeIndexAt(harnessRoot) {
3186
3282
  const index = /* @__PURE__ */ new Map();
3187
3283
  for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
3188
3284
  for (const name of Object.keys(run.workers || {})) {
3189
- const workerPath = path19.join(
3285
+ const workerPath = path20.join(
3190
3286
  runDirectoryAt(harnessRoot, run.id),
3191
3287
  "workers",
3192
3288
  safeSlug(name),
@@ -3194,9 +3290,9 @@ function buildWorktreeIndexAt(harnessRoot) {
3194
3290
  );
3195
3291
  const worker = readJson(workerPath, void 0);
3196
3292
  if (!worker?.worktreePath) continue;
3197
- index.set(path19.resolve(worker.worktreePath), {
3293
+ index.set(path20.resolve(worker.worktreePath), {
3198
3294
  harnessRoot,
3199
- worktreePath: path19.resolve(worker.worktreePath),
3295
+ worktreePath: path20.resolve(worker.worktreePath),
3200
3296
  runId: run.id,
3201
3297
  workerName: name,
3202
3298
  run,
@@ -3222,6 +3318,7 @@ function resolveHarnessRetention(options = {}) {
3222
3318
  const execute = options.execute === true || options.execute !== false && envFlag("KYNVER_CLEANUP_EXECUTE");
3223
3319
  const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
3224
3320
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
3321
+ const scanDependencyCaches = options.scanDependencyCaches ?? (options.nodeModulesAgeMs !== void 0 || process.env.KYNVER_CLEANUP_NODE_MODULES_AGE_MS != null);
3225
3322
  const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
3226
3323
  const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
3227
3324
  const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
@@ -3237,6 +3334,7 @@ function resolveHarnessRetention(options = {}) {
3237
3334
  return {
3238
3335
  execute,
3239
3336
  finalizeStaleRuns: finalizeStaleRuns2,
3337
+ scanDependencyCaches,
3240
3338
  nodeModulesAgeMs,
3241
3339
  worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
3242
3340
  terminalWorktreesAgeMs: terminalWorktreesAgeMs >= 0 ? terminalWorktreesAgeMs : 0,
@@ -3252,29 +3350,31 @@ function resolveHarnessRetention(options = {}) {
3252
3350
 
3253
3351
  // src/cleanup-orphan-safety.ts
3254
3352
  init_git();
3255
- import { existsSync as existsSync18, statSync as statSync10 } from "node:fs";
3256
- import path20 from "node:path";
3353
+ import { existsSync as existsSync18 } from "node:fs";
3354
+ import path21 from "node:path";
3355
+ init_util();
3257
3356
  var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
3258
3357
  function assessOrphanWorktreeSafety(input) {
3259
3358
  const now = input.now ?? Date.now();
3260
3359
  const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
3261
3360
  if (!existsSync18(input.worktreePath)) return null;
3262
3361
  if (input.runId && input.workerName) {
3263
- const heartbeatPath = path20.join(
3362
+ const workerDir = path21.join(
3264
3363
  input.harnessRoot,
3265
3364
  "runs",
3266
3365
  input.runId,
3267
3366
  "workers",
3268
- input.workerName,
3269
- "heartbeat.jsonl"
3367
+ input.workerName
3270
3368
  );
3271
- try {
3272
- const mtime = statSync10(heartbeatPath).mtimeMs;
3273
- if (now - mtime < heartbeatFreshMs) return "active_worker";
3274
- } catch {
3369
+ const worker = readJson(
3370
+ path21.join(workerDir, "worker.json"),
3371
+ void 0
3372
+ );
3373
+ if (worker && heartbeatContentIsFresh(worker, now, heartbeatFreshMs)) {
3374
+ return "active_worker";
3275
3375
  }
3276
3376
  }
3277
- const gitDir = path20.join(input.worktreePath, ".git");
3377
+ const gitDir = path21.join(input.worktreePath, ".git");
3278
3378
  if (!existsSync18(gitDir)) return null;
3279
3379
  const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
3280
3380
  if (porcelain.status !== 0) return "pr_or_unmerged_commits";
@@ -3305,8 +3405,8 @@ function assessOrphanWorktreeSafety(input) {
3305
3405
 
3306
3406
  // src/harness-storage-snapshot.ts
3307
3407
  init_paths();
3308
- import { existsSync as existsSync19, readdirSync as readdirSync11, statSync as statSync11 } from "node:fs";
3309
- import path21 from "node:path";
3408
+ import { existsSync as existsSync19, readdirSync as readdirSync10, statSync as statSync9 } from "node:fs";
3409
+ import path22 from "node:path";
3310
3410
  function harnessStorageSnapshot(opts = {}) {
3311
3411
  const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
3312
3412
  const worktreesDir = harnessWorktreesDir(harnessRoot);
@@ -3329,7 +3429,7 @@ function harnessStorageSnapshot(opts = {}) {
3329
3429
  let oldestMs = null;
3330
3430
  let entries;
3331
3431
  try {
3332
- entries = readdirSync11(worktreesDir, { withFileTypes: true });
3432
+ entries = readdirSync10(worktreesDir, { withFileTypes: true });
3333
3433
  } catch {
3334
3434
  return {
3335
3435
  harnessRoot,
@@ -3344,14 +3444,14 @@ function harnessStorageSnapshot(opts = {}) {
3344
3444
  for (const runEntry of entries) {
3345
3445
  if (!runEntry.isDirectory()) continue;
3346
3446
  runCount += 1;
3347
- const runPath = path21.join(worktreesDir, runEntry.name);
3447
+ const runPath = path22.join(worktreesDir, runEntry.name);
3348
3448
  try {
3349
- const st = statSync11(runPath);
3449
+ const st = statSync9(runPath);
3350
3450
  oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
3351
3451
  } catch {
3352
3452
  }
3353
3453
  try {
3354
- for (const workerEntry of readdirSync11(runPath, { withFileTypes: true })) {
3454
+ for (const workerEntry of readdirSync10(runPath, { withFileTypes: true })) {
3355
3455
  if (workerEntry.isDirectory()) workerCount += 1;
3356
3456
  }
3357
3457
  } catch {
@@ -3382,10 +3482,10 @@ function harnessStorageSnapshot(opts = {}) {
3382
3482
  init_paths();
3383
3483
  import { existsSync as existsSync20 } from "node:fs";
3384
3484
  import { homedir as homedir4 } from "node:os";
3385
- import path22 from "node:path";
3485
+ import path23 from "node:path";
3386
3486
  var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
3387
3487
  "/var/tmp/kynver-harness",
3388
- path22.join(homedir4(), ".openclaw", "harness")
3488
+ path23.join(homedir4(), ".openclaw", "harness")
3389
3489
  ];
3390
3490
  function addRoot(seen, roots, candidate) {
3391
3491
  if (!candidate?.trim()) return;
@@ -3407,7 +3507,7 @@ function resolveHarnessScanRoots(options = {}) {
3407
3507
  for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
3408
3508
  if (shouldScanWellKnownRoots(options)) {
3409
3509
  for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
3410
- const resolved = path22.resolve(candidate);
3510
+ const resolved = path23.resolve(candidate);
3411
3511
  if (!seen.has(resolved) && existsSync20(resolved)) addRoot(seen, roots, resolved);
3412
3512
  }
3413
3513
  }
@@ -3486,15 +3586,52 @@ var CleanupGitRevCache = class {
3486
3586
  }
3487
3587
  };
3488
3588
 
3589
+ // src/cleanup-git-probe.ts
3590
+ import { spawnSync as spawnSync3 } from "node:child_process";
3591
+ import { existsSync as existsSync21 } from "node:fs";
3592
+ import path24 from "node:path";
3593
+ var CLEANUP_GIT_PROBE_TIMEOUT_MS = 5e3;
3594
+ function cleanupGitCapture(cwd, args) {
3595
+ if (!existsSync21(path24.join(cwd, ".git"))) {
3596
+ return { status: null, stdout: "", stderr: "", error: "not_a_git_repo" };
3597
+ }
3598
+ try {
3599
+ const res = spawnSync3("git", args, {
3600
+ cwd,
3601
+ encoding: "utf8",
3602
+ timeout: CLEANUP_GIT_PROBE_TIMEOUT_MS
3603
+ });
3604
+ const timedOut = res.error?.message?.includes("ETIMEDOUT") === true;
3605
+ return {
3606
+ status: timedOut ? null : res.status,
3607
+ stdout: res.stdout || "",
3608
+ stderr: res.stderr || "",
3609
+ error: timedOut ? "git_timeout" : res.error ? res.error.message : null
3610
+ };
3611
+ } catch (error) {
3612
+ return {
3613
+ status: null,
3614
+ stdout: "",
3615
+ stderr: "",
3616
+ error: error.message
3617
+ };
3618
+ }
3619
+ }
3620
+ function gitStatusShortForCleanup(worktreePath) {
3621
+ const captured = cleanupGitCapture(worktreePath, ["status", "--short"]);
3622
+ if (captured.error === "not_a_git_repo") return [];
3623
+ if (captured.error === "git_timeout" || captured.status !== 0) return null;
3624
+ return captured.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
3625
+ }
3626
+
3489
3627
  // src/cleanup-git-status-cache.ts
3490
- init_git();
3491
3628
  var CleanupGitStatusCache = class {
3492
3629
  cache = /* @__PURE__ */ new Map();
3493
3630
  porcelain(worktreePath) {
3494
3631
  const resolved = worktreePath;
3495
3632
  const cached = this.cache.get(resolved);
3496
3633
  if (cached !== void 0) return cached;
3497
- const lines = gitStatusShort(resolved);
3634
+ const lines = gitStatusShortForCleanup(resolved) ?? [];
3498
3635
  this.cache.set(resolved, lines);
3499
3636
  return lines;
3500
3637
  }
@@ -3599,9 +3736,9 @@ function mergeWorktreeIndexes(scanRoots) {
3599
3736
  }
3600
3737
  function worktreePathForCandidate(candidate, worktreesDir) {
3601
3738
  if (candidate.runId && candidate.worker) {
3602
- return path23.join(worktreesDir, candidate.runId, candidate.worker);
3739
+ return path25.join(worktreesDir, candidate.runId, candidate.worker);
3603
3740
  }
3604
- return path23.resolve(candidate.path, "..");
3741
+ return path25.resolve(candidate.path, "..");
3605
3742
  }
3606
3743
  function runHarnessCleanup(options = {}) {
3607
3744
  let retention = resolveHarnessRetention(options);
@@ -3630,7 +3767,8 @@ function runHarnessCleanup(options = {}) {
3630
3767
  for (const harnessRoot of paths.scanRoots) {
3631
3768
  if (atSweepCap()) break;
3632
3769
  emitCleanupProgress("root", harnessRoot);
3633
- const worktreesDir = path23.join(harnessRoot, "worktrees");
3770
+ const worktreesDir = path25.join(harnessRoot, "worktrees");
3771
+ const rootIndex = filterWorktreeIndexForRoot(index, harnessRoot);
3634
3772
  const scanOpts = {
3635
3773
  harnessRoot,
3636
3774
  worktreesDir,
@@ -3638,11 +3776,14 @@ function runHarnessCleanup(options = {}) {
3638
3776
  worktreesAgeMs: retention.worktreesAgeMs,
3639
3777
  includeOrphans: retention.includeOrphans,
3640
3778
  runIdFilter: retention.runIdFilter,
3641
- index,
3779
+ index: rootIndex,
3642
3780
  now: paths.now
3643
3781
  };
3644
- const dependencyCandidates = scanDependencyCacheCandidates(scanOpts);
3645
- emitCleanupProgress("dependency", `${dependencyCandidates.length} cache candidate(s) at ${harnessRoot}`);
3782
+ const dependencyCandidates = retention.scanDependencyCaches ? scanDependencyCacheCandidates(scanOpts) : [];
3783
+ emitCleanupProgress(
3784
+ "dependency",
3785
+ retention.scanDependencyCaches ? `${dependencyCandidates.length} cache candidate(s) at ${harnessRoot}` : "skipped (worktree-only sweep)"
3786
+ );
3646
3787
  let dependencyProcessed = 0;
3647
3788
  for (const raw of dependencyCandidates) {
3648
3789
  if (atSweepCap()) break;
@@ -3650,7 +3791,7 @@ function runHarnessCleanup(options = {}) {
3650
3791
  if (dependencyProcessed % 50 === 0) {
3651
3792
  emitCleanupProgress("dependency", `${dependencyProcessed}/${dependencyCandidates.length} evaluated`);
3652
3793
  }
3653
- const resolved = path23.resolve(raw.path);
3794
+ const resolved = path25.resolve(raw.path);
3654
3795
  if (processedPaths.has(resolved)) continue;
3655
3796
  processedPaths.add(resolved);
3656
3797
  const candidate = { ...raw, path: resolved };
@@ -3661,7 +3802,7 @@ function runHarnessCleanup(options = {}) {
3661
3802
  continue;
3662
3803
  }
3663
3804
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
3664
- const indexed = index.get(path23.resolve(worktreePath)) ?? null;
3805
+ const indexed = rootIndex.get(path25.resolve(worktreePath)) ?? null;
3665
3806
  const guardReason = skipDependencyCacheRemoval({
3666
3807
  indexed,
3667
3808
  includeOrphans: true,
@@ -3684,9 +3825,9 @@ function runHarnessCleanup(options = {}) {
3684
3825
  )
3685
3826
  );
3686
3827
  }
3687
- for (const raw of scanBuildCacheCandidates(scanOpts)) {
3828
+ if (retention.scanDependencyCaches) for (const raw of scanBuildCacheCandidates(scanOpts)) {
3688
3829
  if (atSweepCap()) break;
3689
- const resolved = path23.resolve(raw.path);
3830
+ const resolved = path25.resolve(raw.path);
3690
3831
  if (processedPaths.has(resolved)) continue;
3691
3832
  processedPaths.add(resolved);
3692
3833
  const candidate = { ...raw, path: resolved };
@@ -3697,7 +3838,7 @@ function runHarnessCleanup(options = {}) {
3697
3838
  continue;
3698
3839
  }
3699
3840
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
3700
- const indexed = index.get(path23.resolve(worktreePath)) ?? null;
3841
+ const indexed = rootIndex.get(path25.resolve(worktreePath)) ?? null;
3701
3842
  const guardReason = skipBuildCacheRemoval({
3702
3843
  indexed,
3703
3844
  includeOrphans: true,
@@ -3733,11 +3874,11 @@ function runHarnessCleanup(options = {}) {
3733
3874
  if (worktreeProcessed % 50 === 0) {
3734
3875
  emitCleanupProgress("worktrees", `${worktreeProcessed}/${worktreeCandidates.length} evaluated`);
3735
3876
  }
3736
- const resolved = path23.resolve(raw.path);
3877
+ const resolved = path25.resolve(raw.path);
3737
3878
  if (worktreeSeen.has(resolved)) continue;
3738
3879
  worktreeSeen.add(resolved);
3739
3880
  const candidate = { ...raw, path: resolved };
3740
- const indexed = index.get(path23.resolve(candidate.path)) ?? null;
3881
+ const indexed = rootIndex.get(path25.resolve(candidate.path)) ?? null;
3741
3882
  const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
3742
3883
  worktreePath: candidate.path,
3743
3884
  harnessRoot,
@@ -3747,14 +3888,17 @@ function runHarnessCleanup(options = {}) {
3747
3888
  });
3748
3889
  const guardSkip = skipWorktreeRemoval({
3749
3890
  indexed,
3750
- worktreePath: path23.resolve(candidate.path),
3891
+ worktreePath: path25.resolve(candidate.path),
3751
3892
  includeOrphans: retention.includeOrphans,
3752
3893
  worktreesAgeMs: retention.worktreesAgeMs,
3753
3894
  terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
3754
3895
  ageMs: candidate.ageMs,
3755
3896
  orphanSafety,
3756
3897
  worktreeRemovalGuard: options.worktreeRemovalGuard,
3757
- liveness
3898
+ liveness,
3899
+ now: paths.now,
3900
+ harnessRoot,
3901
+ writeSalvageEvidence: retention.execute
3758
3902
  });
3759
3903
  if (guardSkip) {
3760
3904
  const { reason: guardReason, detail: guardDetail } = normalizeGuardSkip(guardSkip);
@@ -3779,11 +3923,11 @@ function runHarnessCleanup(options = {}) {
3779
3923
  now: paths.now
3780
3924
  })) {
3781
3925
  if (atSweepCap()) break;
3782
- const resolved = path23.resolve(raw.path);
3926
+ const resolved = path25.resolve(raw.path);
3783
3927
  if (processedPaths.has(resolved)) continue;
3784
3928
  processedPaths.add(resolved);
3785
3929
  const candidate = { ...raw, path: resolved };
3786
- const runId = candidate.runId ?? path23.basename(resolved);
3930
+ const runId = candidate.runId ?? path25.basename(resolved);
3787
3931
  const dirSkip = skipRunDirectoryRemoval({
3788
3932
  harnessRoot,
3789
3933
  runId,