@kynver-app/runtime 0.1.103 → 0.1.106

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.
Files changed (47) hide show
  1. package/dist/cleanup-completion-blocker.d.ts +10 -0
  2. package/dist/cleanup-guards.d.ts +1 -6
  3. package/dist/cleanup-worktree-salvage.d.ts +7 -0
  4. package/dist/cli.js +171 -62
  5. package/dist/cli.js.map +4 -4
  6. package/dist/index.js +172 -63
  7. package/dist/index.js.map +4 -4
  8. package/dist/server/cleanup.d.ts +3 -0
  9. package/dist/server/cleanup.js +3511 -0
  10. package/dist/server/cleanup.js.map +7 -0
  11. package/dist/server/default-repo.d.ts +1 -0
  12. package/dist/server/default-repo.js +228 -0
  13. package/dist/server/default-repo.js.map +7 -0
  14. package/dist/server/harness-notice.d.ts +2 -0
  15. package/dist/server/harness-notice.js +287 -0
  16. package/dist/server/harness-notice.js.map +7 -0
  17. package/dist/server/heavy-verification.d.ts +2 -0
  18. package/dist/server/heavy-verification.js +223 -0
  19. package/dist/server/heavy-verification.js.map +7 -0
  20. package/dist/server/landing.d.ts +1 -0
  21. package/dist/server/landing.js +44 -0
  22. package/dist/server/landing.js.map +7 -0
  23. package/dist/server/memory-cost-enforce.d.ts +1 -0
  24. package/dist/server/memory-cost-enforce.js +470 -0
  25. package/dist/server/memory-cost-enforce.js.map +7 -0
  26. package/dist/server/memory-cost.d.ts +1 -0
  27. package/dist/server/memory-cost.js +184 -0
  28. package/dist/server/memory-cost.js.map +7 -0
  29. package/dist/server/monitor.d.ts +3 -0
  30. package/dist/server/monitor.js +1577 -0
  31. package/dist/server/monitor.js.map +7 -0
  32. package/dist/server/orchestration.d.ts +10 -0
  33. package/dist/server/orchestration.js +444 -0
  34. package/dist/server/orchestration.js.map +7 -0
  35. package/dist/server/pr-evidence.d.ts +2 -0
  36. package/dist/server/pr-evidence.js +163 -0
  37. package/dist/server/pr-evidence.js.map +7 -0
  38. package/dist/server/repo-search.d.ts +1 -0
  39. package/dist/server/repo-search.js +224 -0
  40. package/dist/server/repo-search.js.map +7 -0
  41. package/dist/server/worker-policy.d.ts +2 -0
  42. package/dist/server/worker-policy.js +177 -0
  43. package/dist/server/worker-policy.js.map +7 -0
  44. package/dist/status.d.ts +4 -0
  45. package/dist/worker-persona-catalog.js +2 -2
  46. package/dist/worker-persona-catalog.js.map +2 -2
  47. package/package.json +63 -3
package/dist/index.js CHANGED
@@ -350,7 +350,9 @@ function redactHomePath(value) {
350
350
  if (resolved.startsWith(`${home}${path2.sep}`)) {
351
351
  return `~/${path2.relative(home, resolved).split(path2.sep).join("/")}`;
352
352
  }
353
- return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
353
+ const posix = resolved.replace(/\\/g, "/");
354
+ const redacted = posix.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^[A-Za-z]:\/home\/[^/]+(?=\/|$)/i, "~").replace(/^[A-Za-z]:\/Users\/[^/]+(?=\/|$)/i, "~");
355
+ return redacted;
354
356
  }
355
357
  function displayUserPath(value) {
356
358
  return redactHomePath(value);
@@ -2128,7 +2130,7 @@ var NO_START_MS = 18e4;
2128
2130
  var STALE_MS = 6e5;
2129
2131
  function computeAttention(input) {
2130
2132
  const now = Date.now();
2131
- if (input.completionBlocker) {
2133
+ if (input.completionBlocker && !isSkippedTerminalCompletionBlocker(input.completionBlocker)) {
2132
2134
  return { state: "blocked", reason: input.completionBlocker };
2133
2135
  }
2134
2136
  if (input.finalResult) {
@@ -2166,6 +2168,9 @@ function computeAttention(input) {
2166
2168
  return { state: "done", reason: "final result recorded" };
2167
2169
  }
2168
2170
  if (!input.alive) {
2171
+ if (isAbandonedEmptyWorker(input)) {
2172
+ return { state: "done", reason: "empty abandoned worker record" };
2173
+ }
2169
2174
  const classified = classifyExitFailure(input.error);
2170
2175
  if (classified) return { state: "blocked", reason: classified.reason };
2171
2176
  const salvage = assessExitedWorkerSalvage({
@@ -2200,6 +2205,19 @@ function computeAttention(input) {
2200
2205
  }
2201
2206
  return { state: "ok", reason: "recent activity" };
2202
2207
  }
2208
+ function isSkippedTerminalCompletionBlocker(reason) {
2209
+ const text = reason?.trim();
2210
+ if (!text) return false;
2211
+ return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
2212
+ }
2213
+ function isAbandonedEmptyWorker(input) {
2214
+ if (input.finalResult) return false;
2215
+ if (input.taskId || input.agentOsId) return false;
2216
+ if (input.stdoutBytes > 0 || (input.stderrBytes ?? 0) > 0 || input.heartbeatBytes > 0) return false;
2217
+ if (input.error?.trim()) return false;
2218
+ if ((input.changedFiles ?? []).some((line) => line.trim())) return false;
2219
+ return /empty worker dir|marked abandoned/i.test(input.reconcileReason ?? "");
2220
+ }
2203
2221
  function hasMergedTargetPrReconciliation(value) {
2204
2222
  let record3 = null;
2205
2223
  if (typeof value === "string") record3 = extractEmbeddedWorkerFinalResultRecord(value);
@@ -2244,6 +2262,7 @@ function computeWorkerStatus(worker, options = {}) {
2244
2262
  ]);
2245
2263
  const error = parsed.error || (!alive && !finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
2246
2264
  const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim() ? worker.completionBlocker.trim() : null;
2265
+ const effectiveCompletionBlocker = isSkippedTerminalCompletionBlocker(completionBlocker) ? null : completionBlocker;
2247
2266
  const landingContract = worker.repairTargetPrUrl ? {
2248
2267
  landingOnly: false,
2249
2268
  targetPrUrls: [worker.repairTargetPrUrl],
@@ -2255,6 +2274,7 @@ function computeWorkerStatus(worker, options = {}) {
2255
2274
  finalResult,
2256
2275
  firstEventAt: parsed.firstEventAt,
2257
2276
  stdoutBytes,
2277
+ stderrBytes,
2258
2278
  heartbeatBytes,
2259
2279
  lastActivityAt,
2260
2280
  heartbeatBlocker: heartbeat.heartbeatBlocker,
@@ -2262,12 +2282,15 @@ function computeWorkerStatus(worker, options = {}) {
2262
2282
  error,
2263
2283
  changedFiles,
2264
2284
  gitAncestry,
2265
- completionBlocker,
2285
+ completionBlocker: effectiveCompletionBlocker,
2266
2286
  landingContract,
2267
2287
  prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null,
2268
- localOnly: worker.localOnly === true
2288
+ localOnly: worker.localOnly === true,
2289
+ taskId: worker.taskId ?? null,
2290
+ agentOsId: worker.agentOsId ?? null,
2291
+ reconcileReason: worker.reconcileReason ?? null
2269
2292
  });
2270
- const workerStatusLabel = completionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
2293
+ const workerStatusLabel = effectiveCompletionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
2271
2294
  return {
2272
2295
  runId: worker.runId,
2273
2296
  worker: worker.name,
@@ -3027,14 +3050,14 @@ var WORKER_PERSONA_CATALOG = [
3027
3050
  {
3028
3051
  slug: "lorentz",
3029
3052
  displayName: "Lorentz",
3030
- description: "Review / testing \u2014 pre-landing and post-landing verification, report + deep review.",
3053
+ description: "Deep/adversarial review lane expert for risk, correctness, and safety gates. Run adversarial review and validation gating.",
3031
3054
  dispatchLane: "review",
3032
3055
  defaultRoleLane: "report_reviewer"
3033
3056
  },
3034
3057
  {
3035
3058
  slug: "dalton",
3036
3059
  displayName: "Dalton",
3037
- description: "Landing / merge execution \u2014 merge-ready PR landing and merge evidence only (no implementation).",
3060
+ description: "Landing-only \u2014 merge-ready handoff and final verification evidence; no implementation ownership.",
3038
3061
  dispatchLane: "landing",
3039
3062
  defaultRoleLane: "implementer"
3040
3063
  }
@@ -3203,6 +3226,9 @@ function enforceCursorWorkerProvider(input) {
3203
3226
  if (taskAllowsClaudeWorker(task)) {
3204
3227
  return routing;
3205
3228
  }
3229
+ if (routing.rule === "explicit:cli" && isClaudeFamilyProvider(routing.provider)) {
3230
+ return routing;
3231
+ }
3206
3232
  if (!isClaudeFamilyProvider(routing.provider)) {
3207
3233
  return routing;
3208
3234
  }
@@ -4256,7 +4282,7 @@ function resolveWorkerLaunch(input) {
4256
4282
  if (!input.task || Object.keys(input.task).length === 0) {
4257
4283
  return afterCursorPolicy;
4258
4284
  }
4259
- if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider") {
4285
+ if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider" || afterCursorPolicy.rule === "explicit:cli") {
4260
4286
  return afterCursorPolicy;
4261
4287
  }
4262
4288
  if (isClaudeFamilyProvider(afterCursorPolicy.provider) && (input.explicitProviderIsOperatorOverride || taskAllowsClaudeWorker(input.task))) {
@@ -4873,6 +4899,7 @@ function asString(value) {
4873
4899
  var ADVANCED_OUTCOMES = /* @__PURE__ */ new Set([
4874
4900
  "review_scheduled",
4875
4901
  "review_already_scheduled",
4902
+ "skipped_terminal_task",
4876
4903
  "closed",
4877
4904
  "dispatched",
4878
4905
  "dispatch_already_done"
@@ -5220,6 +5247,36 @@ function defaultPrBody(taskId, workerName, runId) {
5220
5247
  "Opened by orchestrator completion enforcement so production review receives a reviewable artifact."
5221
5248
  ].filter(Boolean).join("\n");
5222
5249
  }
5250
+ function commitDirtyToExistingPr(input) {
5251
+ if (input.snapshot.changedFiles.length === 0) {
5252
+ return {
5253
+ ok: true,
5254
+ prUrl: input.prUrl,
5255
+ headCommit: input.snapshot.headCommit ?? resolveHeadCommit(input.snapshot.worktreePath, input.exec) ?? void 0
5256
+ };
5257
+ }
5258
+ const pushResult = commitAndPushBranch({
5259
+ worktreePath: input.snapshot.worktreePath,
5260
+ branch: input.snapshot.branch,
5261
+ commitMessage: input.commitMessage,
5262
+ hasDirtyFiles: true,
5263
+ exec: input.exec
5264
+ });
5265
+ if (!pushResult.ok) {
5266
+ return {
5267
+ ok: false,
5268
+ reason: `PR-ready handoff blocked: ${pushResult.detail ?? "git commit/push failed"}`,
5269
+ nextAction: "Commit and push the dirty worker changes to the existing PR branch, then rerun `kynver worker complete`."
5270
+ };
5271
+ }
5272
+ return {
5273
+ ok: true,
5274
+ prUrl: input.prUrl,
5275
+ headCommit: pushResult.headCommit ?? input.snapshot.headCommit ?? void 0,
5276
+ committed: pushResult.committed,
5277
+ pushed: pushResult.pushed
5278
+ };
5279
+ }
5223
5280
  function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
5224
5281
  const prUrlHint = input.prUrlHint ?? extractPrUrlFromText(input.status.finalResult) ?? null;
5225
5282
  const snapshot = buildPrHandoffSnapshotFromStatus(input.status, {
@@ -5243,10 +5300,23 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
5243
5300
  snapshot
5244
5301
  });
5245
5302
  if (!requirement.required) {
5246
- return { ok: true, prUrl: prUrlHint ?? void 0 };
5303
+ if (prUrlHint) {
5304
+ return commitDirtyToExistingPr({
5305
+ snapshot,
5306
+ prUrl: prUrlHint,
5307
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
5308
+ exec
5309
+ });
5310
+ }
5311
+ return { ok: true };
5247
5312
  }
5248
5313
  if (prUrlHint) {
5249
- return { ok: true, prUrl: prUrlHint };
5314
+ return commitDirtyToExistingPr({
5315
+ snapshot,
5316
+ prUrl: prUrlHint,
5317
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
5318
+ exec
5319
+ });
5250
5320
  }
5251
5321
  if (!ghAvailable(exec)) {
5252
5322
  const dirty = snapshot.changedFiles.length;
@@ -5309,11 +5379,12 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
5309
5379
  }
5310
5380
  const existing = findOpenPrUrl(snapshot.worktreePath, repo, snapshot.branch, exec);
5311
5381
  if (existing) {
5312
- return {
5313
- ok: true,
5382
+ return commitDirtyToExistingPr({
5383
+ snapshot,
5314
5384
  prUrl: existing,
5315
- headCommit: snapshot.headCommit ?? resolveHeadCommit(snapshot.worktreePath, exec) ?? void 0
5316
- };
5385
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
5386
+ exec
5387
+ });
5317
5388
  }
5318
5389
  const hasDirty = snapshot.changedFiles.length > 0;
5319
5390
  let committed = false;
@@ -5723,6 +5794,11 @@ function deriveNextAction(input) {
5723
5794
  }
5724
5795
  return null;
5725
5796
  }
5797
+ function isSkippedTerminalCompletionBlocker2(reason) {
5798
+ const text = reason?.trim();
5799
+ if (!text) return false;
5800
+ return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
5801
+ }
5726
5802
  function deriveHandoffState(input) {
5727
5803
  if (input.prUrl) return "pr_handoff";
5728
5804
  if (input.headCommit) return "commit_handoff";
@@ -5779,6 +5855,23 @@ async function tryCompleteWorker(args) {
5779
5855
  if (!forceReplay && shouldReplayHarnessCompletion(worker)) {
5780
5856
  clearCompletionBlockerForReplay(worker);
5781
5857
  }
5858
+ const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
5859
+ if (!skipPrHandoff && worker.dispatched && taskId) {
5860
+ const handoff = ensurePrReadyHandoff({ worker, run, status });
5861
+ if (!handoff.ok) {
5862
+ persistCompletionBlocker(worker, handoff.reason);
5863
+ return {
5864
+ ok: false,
5865
+ reason: handoff.reason,
5866
+ nextAction: handoff.nextAction,
5867
+ completionBlocked: true
5868
+ };
5869
+ }
5870
+ if (handoff.prUrl || handoff.headCommit) {
5871
+ status = computeWorkerStatus(worker, workerStatusOptions(run));
5872
+ status = applyPrHandoffToStatus(status, handoff);
5873
+ }
5874
+ }
5782
5875
  const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : status.headCommit;
5783
5876
  if (worker.dispatched) {
5784
5877
  const handoff = assessWorktreeCompletionHandoff({
@@ -5799,22 +5892,6 @@ async function tryCompleteWorker(args) {
5799
5892
  };
5800
5893
  }
5801
5894
  }
5802
- const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
5803
- if (!skipPrHandoff && worker.dispatched && taskId) {
5804
- const handoff = ensurePrReadyHandoff({ worker, run, status });
5805
- if (!handoff.ok) {
5806
- persistCompletionBlocker(worker, handoff.reason);
5807
- return {
5808
- ok: false,
5809
- reason: handoff.reason,
5810
- nextAction: handoff.nextAction,
5811
- completionBlocked: true
5812
- };
5813
- }
5814
- if (handoff.prUrl || handoff.headCommit) {
5815
- status = applyPrHandoffToStatus(status, handoff);
5816
- }
5817
- }
5818
5895
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
5819
5896
  const explicitSecret = args.secret ? String(args.secret) : void 0;
5820
5897
  let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
@@ -6020,7 +6097,8 @@ function buildWorkerBoardEntry(input) {
6020
6097
  headCommit
6021
6098
  });
6022
6099
  const rawBlocker = worker.completionBlocker;
6023
- const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
6100
+ const rawCompletionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
6101
+ const completionBlocker = isSkippedTerminalCompletionBlocker2(rawCompletionBlocker) ? void 0 : rawCompletionBlocker;
6024
6102
  const boardStatus = completionBlocker ? "blocked" : status.status;
6025
6103
  const boardAttention = completionBlocker ? "blocked" : status.attention.state;
6026
6104
  const completionResponse = asRecord3(worker.completionResponse);
@@ -6105,7 +6183,8 @@ function buildWorkerBoardEntry(input) {
6105
6183
  }
6106
6184
  function isMetadataTerminalDone(worker) {
6107
6185
  const status = typeof worker.status === "string" ? worker.status : "";
6108
- const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim().length > 0;
6186
+ const rawCompletionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim().length > 0 ? worker.completionBlocker : void 0;
6187
+ const completionBlocker = rawCompletionBlocker && !isSkippedTerminalCompletionBlocker2(rawCompletionBlocker);
6109
6188
  if (completionBlocker) return false;
6110
6189
  if (typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim()) return true;
6111
6190
  if (worker.completionOutcome === "acknowledged") return true;
@@ -8135,7 +8214,7 @@ function expandHomePath2(filePath) {
8135
8214
  function discoverProductionDbEnvFilePaths(options = {}) {
8136
8215
  const env = options.env ?? process.env;
8137
8216
  const cwd = options.cwd ?? process.cwd();
8138
- const home = options.homeDir ?? homedir12();
8217
+ const home = options.homeDir ?? (env.HOME?.trim() || env.USERPROFILE?.trim() || homedir12());
8139
8218
  const seen = /* @__PURE__ */ new Set();
8140
8219
  const out = [];
8141
8220
  const push = (candidate) => {
@@ -9724,30 +9803,7 @@ function indexedWorktreeHasMaterialChanges(entry) {
9724
9803
  return materialWorktreeChanges2(gitStatusShort(entry.worktreePath)).length > 0;
9725
9804
  }
9726
9805
 
9727
- // src/cleanup-run-liveness.ts
9728
- function deriveRunTerminal(indexed, ctx) {
9729
- if (ctx) return ctx.runTerminalCache.derive(indexed.run);
9730
- return deriveTerminalRunStatus(indexed.run);
9731
- }
9732
- function isWorkerProcessLive(indexed) {
9733
- if (isPidAlive(indexed.worker.pid)) return true;
9734
- if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
9735
- return false;
9736
- }
9737
- function isRunStaleActive(indexed, ctx) {
9738
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9739
- return deriveRunTerminal(indexed, ctx) !== null;
9740
- }
9741
- function runBlocksWorktreeRemoval(indexed, ctx) {
9742
- if (isWorkerProcessLive(indexed)) return true;
9743
- if (indexed.worker.completionBlocker) return true;
9744
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9745
- if (isRunStaleActive(indexed, ctx)) return false;
9746
- if (!isFinishedWorkerStatus(indexedWorktreeStatus(indexed))) return true;
9747
- return deriveRunTerminal(indexed, ctx) === null;
9748
- }
9749
-
9750
- // src/cleanup-guards.ts
9806
+ // src/cleanup-worktree-salvage.ts
9751
9807
  function prUrlFromFinalResult(finalResult) {
9752
9808
  if (typeof finalResult === "string") {
9753
9809
  const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
@@ -9772,12 +9828,63 @@ function isPrOrUnmergedWork(status) {
9772
9828
  if (status.changedFiles.length > 0 && status.finalResult) return true;
9773
9829
  return false;
9774
9830
  }
9831
+
9832
+ // src/cleanup-completion-blocker.ts
9833
+ function completionBlockerBlocksWorktreeRemoval(indexed, status) {
9834
+ const blocker = typeof indexed.worker.completionBlocker === "string" ? indexed.worker.completionBlocker.trim() : "";
9835
+ if (!blocker) return false;
9836
+ if (isWorkerProcessLive(indexed)) return true;
9837
+ const resolved = status ?? indexedWorktreeStatus(indexed);
9838
+ if (!isFinishedWorkerStatus(resolved)) return true;
9839
+ if (isPrOrUnmergedWork(resolved)) return true;
9840
+ if (materialWorktreeChanges2(resolved.changedFiles).length > 0) return true;
9841
+ const landing = assessWorkerLanding({
9842
+ finalResult: resolved.finalResult,
9843
+ changedFiles: resolved.changedFiles,
9844
+ gitAncestry: resolved.gitAncestry,
9845
+ prUrl: prUrlFromFinalResult(resolved.finalResult)
9846
+ });
9847
+ if (landing.blocked) return true;
9848
+ return false;
9849
+ }
9850
+
9851
+ // src/cleanup-run-liveness.ts
9852
+ function deriveRunTerminal(indexed, ctx) {
9853
+ if (ctx) return ctx.runTerminalCache.derive(indexed.run);
9854
+ return deriveTerminalRunStatus(indexed.run);
9855
+ }
9856
+ function isWorkerProcessLive(indexed) {
9857
+ if (isPidAlive(indexed.worker.pid)) return true;
9858
+ if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
9859
+ return false;
9860
+ }
9861
+ function isRunStaleActive(indexed, ctx) {
9862
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9863
+ return deriveRunTerminal(indexed, ctx) !== null;
9864
+ }
9865
+ function runBlocksWorktreeRemoval(indexed, ctx) {
9866
+ if (isWorkerProcessLive(indexed)) return true;
9867
+ const status = indexedWorktreeStatus(indexed);
9868
+ if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
9869
+ if (isFinishedWorkerStatus(status)) return false;
9870
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9871
+ if (isRunStaleActive(indexed, ctx)) return false;
9872
+ return deriveRunTerminal(indexed, ctx) === null;
9873
+ }
9874
+
9875
+ // src/cleanup-guards.ts
9775
9876
  function effectiveWorktreeAgeMs(input) {
9776
9877
  const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
9777
9878
  if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
9778
9879
  if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) {
9779
9880
  return terminalWorktreesAgeMs;
9780
9881
  }
9882
+ if (input.liveness && isRunStaleActive(indexed, input.liveness)) {
9883
+ return terminalWorktreesAgeMs;
9884
+ }
9885
+ if (input.liveness && isFinishedWorkerStatus(indexedWorktreeStatus(indexed)) && !isWorkerProcessLive(indexed)) {
9886
+ return terminalWorktreesAgeMs;
9887
+ }
9781
9888
  return worktreesAgeMs;
9782
9889
  }
9783
9890
  function skipWorktreeRemoval(input) {
@@ -9791,7 +9898,7 @@ function skipWorktreeRemoval(input) {
9791
9898
  if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
9792
9899
  const status = indexedWorktreeStatus(indexed);
9793
9900
  if (isWorkerProcessLive(indexed)) return "active_worker";
9794
- if (indexed.worker.completionBlocker) return "completion_blocked";
9901
+ if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
9795
9902
  if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
9796
9903
  if (!isFinishedWorkerStatus(status)) return "run_still_active";
9797
9904
  if (isPrOrUnmergedWork(status)) return "pr_or_unmerged_commits";
@@ -9947,7 +10054,9 @@ function skipRunDirectoryRemoval(input) {
9947
10054
  }
9948
10055
  if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
9949
10056
  const run = loadRunStatus(harnessRoot, runId);
9950
- if (run && !TERMINAL_RUN_STATUSES.has(run.status)) return "run_still_active";
10057
+ if (run && !TERMINAL_RUN_STATUSES.has(run.status)) {
10058
+ if (!deriveTerminalRunStatus(run)) return "run_still_active";
10059
+ }
9951
10060
  if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
9952
10061
  return null;
9953
10062
  }
@@ -10912,14 +11021,14 @@ function runHarnessCleanup(options = {}) {
10912
11021
  const paths = resolvePaths(options);
10913
11022
  emitCleanupProgress("scan", `${paths.scanRoots.length} harness root(s)`);
10914
11023
  const activeGuards = collectActiveWorktreeGuards(paths.scanRoots, paths.now);
10915
- emitCleanupProgress("index", "building worktree index");
10916
- const index = mergeWorktreeIndexes(paths.scanRoots);
10917
- emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
10918
- const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
10919
11024
  const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
10920
11025
  if (finalizedRuns.length > 0) {
10921
11026
  emitCleanupProgress("finalize", `${finalizedRuns.length} stale run(s) marked terminal`);
10922
11027
  }
11028
+ emitCleanupProgress("index", "building worktree index");
11029
+ const index = mergeWorktreeIndexes(paths.scanRoots);
11030
+ emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
11031
+ const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
10923
11032
  const skips = [];
10924
11033
  const actions = [];
10925
11034
  const processedPaths = /* @__PURE__ */ new Set();