weir-harness 1.2.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/cordis.patch.yml +7 -18
  2. package/lib/client.file-lease-badge.js +307 -0
  3. package/lib/client.js +20 -30
  4. package/lib/client.settings-page.js +10 -34
  5. package/package.json +2 -2
  6. package/src/capabilities/store/checked-contract.d.ts +2 -2
  7. package/src/capabilities/store/fs-adapter.js +2 -3
  8. package/src/context-guard/index.js +2 -2
  9. package/src/delegate/supervision-mount.js +5 -5
  10. package/src/delegate/tool.js +2 -10
  11. package/src/file-lease/client.js +1066 -0
  12. package/src/file-lease/index.js +30 -0
  13. package/src/file-lease/lineage.js +207 -0
  14. package/src/file-lease/node-contracts.d.ts +42 -0
  15. package/src/file-lease/plugin.js +789 -0
  16. package/src/file-lease/singleton.js +155 -0
  17. package/src/file-lease/spawn.js +62 -0
  18. package/src/file-lease/tools.js +459 -0
  19. package/src/file-lease/view.js +144 -0
  20. package/src/hashline-edit/index.js +76 -42
  21. package/src/hashline-edit/write-tool.js +90 -0
  22. package/src/intent-gate/matcher.js +27 -0
  23. package/src/lease-broker/entry.js +98 -0
  24. package/src/lease-broker/node-contracts.d.ts +63 -0
  25. package/src/lease-broker/protocol.js +236 -0
  26. package/src/lease-broker/server.js +399 -0
  27. package/src/lease-broker/state.js +425 -0
  28. package/src/lsp/admin.js +24 -13
  29. package/src/lsp/index.js +5 -2
  30. package/src/lsp/registry.js +83 -17
  31. package/src/lsp/tools.js +137 -27
  32. package/src/notify/commands.js +15 -2
  33. package/src/notify/index.js +48 -10
  34. package/src/notify/web-channel.js +26 -3
  35. package/src/process/index.js +11 -0
  36. package/src/settings/index.js +2 -44
  37. package/src/settings/sections.js +29 -28
  38. package/src/shared/audit.js +6 -1
  39. package/src/todo-driver/index.js +1 -6
  40. package/src/worktree/cards.js +6 -21
  41. package/src/worktree/lane-core.js +1 -19
  42. package/src/worktree/lane-workflows.js +11 -32
  43. package/lib/client.edit-lock-maintenance.js +0 -592
  44. package/lib/client.edit-lock-panel.js +0 -405
  45. package/src/edit-lock/adapter.js +0 -57
  46. package/src/edit-lock/admin-ledger.js +0 -149
  47. package/src/edit-lock/admin-recovery.js +0 -151
  48. package/src/edit-lock/admission.js +0 -82
  49. package/src/edit-lock/binding-attestation.js +0 -29
  50. package/src/edit-lock/binding-guidance.js +0 -72
  51. package/src/edit-lock/binding-machine.js +0 -312
  52. package/src/edit-lock/call-context.js +0 -65
  53. package/src/edit-lock/derived-facts.js +0 -67
  54. package/src/edit-lock/domain.js +0 -165
  55. package/src/edit-lock/domains.js +0 -170
  56. package/src/edit-lock/host.js +0 -149
  57. package/src/edit-lock/incarnations.js +0 -150
  58. package/src/edit-lock/index.js +0 -1303
  59. package/src/edit-lock/inspect.js +0 -219
  60. package/src/edit-lock/lifecycle.js +0 -579
  61. package/src/edit-lock/limits.js +0 -61
  62. package/src/edit-lock/maintenance.js +0 -310
  63. package/src/edit-lock/manager.js +0 -875
  64. package/src/edit-lock/negotiation.js +0 -132
  65. package/src/edit-lock/operation-history.js +0 -214
  66. package/src/edit-lock/peer-client.js +0 -86
  67. package/src/edit-lock/peer-transport.js +0 -76
  68. package/src/edit-lock/peer.js +0 -87
  69. package/src/edit-lock/publisher.js +0 -191
  70. package/src/edit-lock/read-authority.js +0 -101
  71. package/src/edit-lock/recovery.js +0 -139
  72. package/src/edit-lock/remote.js +0 -362
  73. package/src/edit-lock/request-binding.js +0 -42
  74. package/src/edit-lock/request-data.js +0 -40
  75. package/src/edit-lock/reservation.js +0 -301
  76. package/src/edit-lock/reserved-runtime.js +0 -45
  77. package/src/edit-lock/resource-identity.js +0 -130
  78. package/src/edit-lock/runtime.js +0 -145
  79. package/src/edit-lock/settle.js +0 -144
  80. package/src/edit-lock/snapshot.js +0 -175
  81. package/src/edit-lock/stale-sweep.js +0 -120
  82. package/src/edit-lock/state.js +0 -462
  83. package/src/edit-lock/store.js +0 -327
  84. package/src/edit-lock/tool-scope.js +0 -29
  85. package/src/edit-lock/view.js +0 -170
  86. package/src/edit-lock/write-scopes.js +0 -48
  87. package/src/edit-lock/write-tool.js +0 -45
  88. package/src/shared/delegate-edit-lifecycle.js +0 -36
  89. package/src/worktree/authority.js +0 -56
@@ -68,9 +68,6 @@ function apply(ctx, config = {}) {
68
68
  }
69
69
  }
70
70
 
71
- // Edit Lock (when enabled) owns session interruption: only its trusted
72
- // resume re-arms editing work, never an ordinary user message or retry.
73
- const editLockBlocks = (agent) => ctx.get?.('weirEditLock')?.blocksContinuation?.(agent) === true
74
71
  const compactionBlocks = (agent) => agent && ctx.get?.('weirCompactionCoordinator')?.blocksContinuation(agent) === true
75
72
  /** Delayed followup continuation for the provider-error path. */
76
73
  function scheduleRetry(session, delayMs) {
@@ -82,7 +79,6 @@ function apply(ctx, config = {}) {
82
79
  if (remaining.length === 0) return
83
80
  if (!stateOf(session.id).armed) return
84
81
  if (jobsRunningOf(agent.session)) return
85
- if (editLockBlocks(agent)) return
86
82
  if (compactionBlocks(agent)) return
87
83
  injectOrWarn(ctx, `todo-driver: could not queue continuation for "${session.id}"`, () => {
88
84
  agent.followup(userTextMessage(renderContinuation(remaining), 'weir-todo-driver'))
@@ -95,7 +91,7 @@ function apply(ctx, config = {}) {
95
91
 
96
92
  // The sanctioned continuation boundary: steer and the turn runs on.
97
93
  function continueAtBoundary({ agent, signal }) {
98
- if (compactionBlocks(agent) || editLockBlocks(agent)) return
94
+ if (compactionBlocks(agent)) return
99
95
  if (!opts.enabled) return
100
96
  const state = stateOf(agent.id)
101
97
  const remaining = remainingTodos(agent.session)
@@ -106,7 +102,6 @@ function apply(ctx, config = {}) {
106
102
  error: providerErrorPending.get(agent.id),
107
103
  })
108
104
  if (decision.kind !== 'continue') return
109
- if (editLockBlocks(agent)) return
110
105
  injectOrWarn(ctx, `todo-driver: could not steer continuation for "${agent.id}"`, () => {
111
106
  agent.steer(userTextMessage(renderContinuation(remaining), 'weir-todo-driver'))
112
107
  })
@@ -72,12 +72,6 @@ const COPY = {
72
72
  abandonLine: (/** @type {string} */ id, /** @type {string} */ title, /** @type {string} */ state) => `Abandon lane \`${id}\` — ${title} (state ${state}).`,
73
73
  unmergedLost: (/** @type {number} */ count, /** @type {string} */ branch) => `**${count} unmerged commit(s)** on \`${branch}\` will be lost if the branch is removed.`,
74
74
  noUnmerged: (/** @type {string} */ branch) => `\`${branch}\` has no unmerged commits.`,
75
- residueWarning: (/** @type {{ operations: number, locks: number }} */ residue) => {
76
- const parts = []
77
- if (residue.operations > 0) parts.push(`${residue.operations} unresolved Edit Lock operation(s)`)
78
- if (residue.locks > 0) parts.push(`${residue.locks} Edit Lock lock(s)`)
79
- return `⚠️ **Edit Lock residue**: this lane's session left ${parts.join(' and ')} in the Edit Lock authority. Locks are reclaimed automatically by the stale-lock sweep; unresolved publications settle via crash self-heal (dead process) or one-click recovery in the Edit Lock maintenance panel (Settings → Editing). This is a warning only — cleanup still proceeds.`
80
- },
81
75
  dirtyWarning: (/** @type {{ modified: number, untracked: number }} */ dirty) => {
82
76
  const parts = []
83
77
  if (dirty.modified > 0) parts.push(`${dirty.modified} modified`)
@@ -137,12 +131,6 @@ const COPY = {
137
131
  abandonLine: (/** @type {string} */ id, /** @type {string} */ title, /** @type {string} */ state) => `放弃车道 \`${id}\` —— ${title}(状态 ${state})。`,
138
132
  unmergedLost: (/** @type {number} */ count, /** @type {string} */ branch) => `删除分支将**永久丢失** \`${branch}\` 上的 **${count} 个未合并提交**。`,
139
133
  noUnmerged: (/** @type {string} */ branch) => `\`${branch}\` 没有未合并的提交。`,
140
- residueWarning: (/** @type {{ operations: number, locks: number }} */ residue) => {
141
- const parts = []
142
- if (residue.operations > 0) parts.push(`${residue.operations} 个未决 Edit Lock 操作`)
143
- if (residue.locks > 0) parts.push(`${residue.locks} 把 Edit Lock 锁`)
144
- return `⚠️ **Edit Lock 残留**:该车道的会话在 Edit Lock 权威中留有 ${parts.join(' 和 ')}。锁会由 stale-lock 自动清扫回收;未决发布会由崩溃自愈(进程死亡时)或 Edit Lock 维护面板(设置 → 编辑)的一键恢复结清。此仅为警示——清理仍会进行。`
145
- },
146
134
  dirtyWarning: (/** @type {{ modified: number, untracked: number }} */ dirty) => {
147
135
  const parts = []
148
136
  if (dirty.modified > 0) parts.push(`${dirty.modified} 个已修改`)
@@ -193,15 +181,14 @@ export function cardCopy(locale) {
193
181
  ].join('\n')
194
182
  },
195
183
  /**
196
- * @param {any} lane @param {{ files: number, added: number, removed: number }} stat @param {string} [root] @param {{ operations: number, locks: number } | null} [residue]
184
+ * @param {any} lane @param {{ files: number, added: number, removed: number }} stat @param {string} [root]
197
185
  * @param {{ modified: number, untracked: number } | null} [dirty] - dirty-content probe; disclosed as its own warning paragraph when non-zero
198
186
  */
199
- cleanupDetail(lane, stat, root, residue = null, dirty = null) {
187
+ cleanupDetail(lane, stat, root, dirty = null) {
200
188
  return [
201
189
  `- ${copy.merged(lane.branch, lane.base.branch)} (${stat.files} ${copy.files}, +${stat.added} −${stat.removed}).`,
202
190
  `- ${copy.worktree}: \`${displayPath(lane.path, root)}\``,
203
191
  `- ${copy.scratch}`,
204
- ...(residue ? ['', copy.residueWarning(residue)] : []),
205
192
  ...(hasDirtyContent(dirty) ? ['', copy.dirtyWarning(/** @type {any} */ (dirty))] : []),
206
193
  ].join('\n')
207
194
  },
@@ -216,11 +203,11 @@ export function cardCopy(locale) {
216
203
  return copy.disputedReasons.noEvidence
217
204
  },
218
205
  /**
219
- * @param {any} lane @param {number} unmerged @param {string} [root] @param {{ operations: number, locks: number } | null} [residue]
206
+ * @param {any} lane @param {number} unmerged @param {string} [root]
220
207
  * @param {{ child: string, forced: boolean } | null} [reclaimed] - a binding this abandon already settled (design D1/D4), disclosed on the card
221
208
  * @param {{ modified: number, untracked: number } | null} [dirty] - dirty-content probe; disclosed as its own warning paragraph when non-zero
222
209
  */
223
- abandonDetail(lane, unmerged, root, residue = null, reclaimed = null, dirty = null) {
210
+ abandonDetail(lane, unmerged, root, reclaimed = null, dirty = null) {
224
211
  return [
225
212
  `- ${copy.abandonLine(lane.id, lane.title, lane.state)}`,
226
213
  `- ${copy.worktree}: \`${displayPath(lane.path, root)}\``,
@@ -228,22 +215,20 @@ export function cardCopy(locale) {
228
215
  // The unmerged-commit warning stays its own paragraph for emphasis.
229
216
  unmerged > 0 ? copy.unmergedLost(unmerged, lane.branch) : copy.noUnmerged(lane.branch),
230
217
  ...(reclaimed ? ['', copy.reconciledNote(reclaimed.child, reclaimed.forced)] : []),
231
- ...(residue ? ['', copy.residueWarning(residue)] : []),
232
218
  ...(hasDirtyContent(dirty) ? ['', copy.dirtyWarning(/** @type {any} */ (dirty))] : []),
233
219
  ].join('\n')
234
220
  },
235
221
  /**
236
222
  * The disputed-binding card body (design D4): lane facts, then the
237
223
  * reconciliation outcome as the warning paragraph.
238
- * @param {any} lane @param {string} child @param {{ childAlive?: boolean, ownerAlive?: boolean, terminalEvidence?: any } | null} verdict @param {string} [root] @param {{ operations: number, locks: number } | null} [residue]
224
+ * @param {any} lane @param {string} child @param {{ childAlive?: boolean, ownerAlive?: boolean, terminalEvidence?: any } | null} verdict @param {string} [root]
239
225
  */
240
- abandonDisputedDetail(lane, child, verdict, root, residue = null) {
226
+ abandonDisputedDetail(lane, child, verdict, root) {
241
227
  return [
242
228
  `- ${copy.abandonLine(lane.id, lane.title, lane.state)}`,
243
229
  `- ${copy.worktree}: \`${displayPath(lane.path, root)}\``,
244
230
  '',
245
231
  copy.disputedWarning(child, this.disputedReason(verdict)),
246
- ...(residue ? ['', copy.residueWarning(residue)] : []),
247
232
  ].join('\n')
248
233
  },
249
234
  }
@@ -16,7 +16,6 @@ import { normalizeRoot, parseRepoConfig } from './rules.js'
16
16
  import { reconcile } from './reconcile.js'
17
17
  import { createLedger } from './ledger.js'
18
18
  import { renderNotice, renderWatchExpired, renderWatchHit } from './prompts.js'
19
- import { authorityResidue } from './authority.js'
20
19
 
21
20
  export const CONFIG_FILE = '.config.json'
22
21
  const LOG_TAIL_LINES = 40
@@ -74,7 +73,6 @@ export function pathKey(path, platform = process.platform) {
74
73
  * @property {(handle: any) => void} [clearTimer]
75
74
  * @property {number} [pid] - this process id (tests)
76
75
  * @property {{ warn?: (message: string) => void }} [logger]
77
- * @property {(root: string, sessionId: string | null | undefined) => ({ operations: number, locks: number } | null)} [authorityResidue] - read-only Edit Lock residue probe feeding the cleanup/abandon warning (tests); defaults to the bounded snapshot read in ./authority.js
78
76
  * @property {(boundChild: string, ownerSession: string | null, context: { root: string }) => Promise<{ childAlive: boolean, ownerAlive: boolean, terminalEvidence: { source: string, detail: string } | null }>} [bindingLiveness] - read-only zombie-binding probe (design D2); absent = LANE_BUSY refusals stand unreconciled
79
77
  */
80
78
 
@@ -347,22 +345,6 @@ export function createLaneCore(deps) {
347
345
  }
348
346
  }
349
347
 
350
- /**
351
- * Best-effort Edit Lock authority-residue probe (design D5): does the
352
- * lane's owner session still own unresolved operations or locks in the
353
- * management domain? Warning input only — read-only, and a failed check
354
- * is silent (never blocks the cleanup/abandon transition).
355
- * @param {any} repo @param {any} lane @returns {{ operations: number, locks: number } | null}
356
- */
357
- function residueOf(repo, lane) {
358
- try {
359
- return (deps.authorityResidue ?? authorityResidue)(repo.mainRoot, lane?.ownerSession) ?? null
360
- } catch (error) {
361
- deps.logger?.warn?.(`worktree: authority-residue check failed: ${/** @type {any} */ (error)?.message ?? error}`)
362
- return null
363
- }
364
- }
365
-
366
348
  /** @param {any} lane */
367
349
  function result(lane, summary, extra = {}) {
368
350
  return {
@@ -635,7 +617,7 @@ export function createLaneCore(deps) {
635
617
  approveModeSafe, approveModeSourceSafe,
636
618
  cwdOf, config, repoFor, repoCached,
637
619
  armWatch, disarmWatch,
638
- laneOf, apply, patchLane, commitBinding, notifyOwner, residueOf, result, readConfig, logFile,
620
+ laneOf, apply, patchLane, commitBinding, notifyOwner, result, readConfig, logFile,
639
621
  refresh, reconcileNote, reconcileBinding, childSettled, check,
640
622
  }
641
623
  }
@@ -21,21 +21,9 @@ import { CONFIG_FILE, pathKey } from './lane-core.js'
21
21
 
22
22
  const SETUP_TIMEOUT_MS = 600_000
23
23
 
24
- /**
25
- * Model-facing English residue warning appended to cleanup/abandon tool
26
- * results (the human-facing card copy localizes its own in cards.js).
27
- * @param {{ operations: number, locks: number }} residue
28
- */
29
- function residueNote(residue) {
30
- const parts = []
31
- if (residue.operations > 0) parts.push(`${residue.operations} unresolved Edit Lock operation(s)`)
32
- if (residue.locks > 0) parts.push(`${residue.locks} Edit Lock lock(s)`)
33
- return `warning: the lane's session left ${parts.join(' and ')} in the management authority; locks are reclaimed by the stale-lock sweep, unresolved publications settle via crash self-heal (dead process) or the Edit Lock maintenance panel's one-click recovery`
34
- }
35
-
36
24
  /** @param {ReturnType<typeof import('./lane-core.js').createLaneCore>} core */
37
25
  export function createLaneWorkflows(core) {
38
- const { deps, git, now, pid, asking, approveModeSafe, cwdOf, config, repoFor, laneOf, apply, patchLane, commitBinding, notifyOwner, residueOf, result, readConfig, logFile, refresh, reconcileNote, reconcileBinding, armWatch, disarmWatch } = core
26
+ const { deps, git, now, pid, asking, approveModeSafe, cwdOf, config, repoFor, laneOf, apply, patchLane, commitBinding, notifyOwner, result, readConfig, logFile, refresh, reconcileNote, reconcileBinding, armWatch, disarmWatch } = core
39
27
 
40
28
  // ─── open / setup ─────────────────────────────────────────────────────
41
29
 
@@ -263,7 +251,7 @@ export function createLaneWorkflows(core) {
263
251
  id: 'abandon',
264
252
  header: copy.abandonHeader,
265
253
  question: copy.abandonQuestion(lane.title),
266
- detail: copy.abandonDisputedDetail(lane, lane.boundChild, reconciliation?.verdict ?? null, repo.mainRoot, residueOf(repo, lane)),
254
+ detail: copy.abandonDisputedDetail(lane, lane.boundChild, reconciliation?.verdict ?? null, repo.mainRoot),
267
255
  options: [
268
256
  { label: copy.forceReclaim, description: copy.forceReclaimDescription },
269
257
  { label: copy.cancel, description: copy.abandonDescriptions.cancel },
@@ -452,10 +440,9 @@ export function createLaneWorkflows(core) {
452
440
  }
453
441
  // Auto cleanup after the merge (design D3): auto-keep → mode worktree,
454
442
  // auto-clean → mode all. The existing cleanup() body runs unchanged —
455
- // scratch sync first, never --force on an auto path, the Edit Lock residue
456
- // probe feeding the summary. A blocked removal stays `landed` and is
457
- // reported concisely with the typed --force retry instead of a false
458
- // success (the user can clean up later).
443
+ // scratch sync first, never --force on an auto path. A blocked removal
444
+ // stays `landed` and is reported concisely with the typed --force retry
445
+ // instead of a false success (the user can clean up later).
459
446
  const cleanupMode = approveMode === 'auto-keep' ? 'worktree' : 'all'
460
447
  try {
461
448
  const cleaned = await cleanup(session, laneId, /** @type {'worktree' | 'all'} */ (cleanupMode), { by: 'host', auto: true, approveMode })
@@ -488,7 +475,7 @@ export function createLaneWorkflows(core) {
488
475
  id: 'cleanup',
489
476
  header: copy.cleanupHeader,
490
477
  question: copy.cleanupQuestion(lane.title),
491
- detail: copy.cleanupDetail(lane, lane.land?.stat ?? { files: 0, added: 0, removed: 0 }, repo.mainRoot, residueOf(repo, lane), dirtyCard ? dirty : null),
478
+ detail: copy.cleanupDetail(lane, lane.land?.stat ?? { files: 0, added: 0, removed: 0 }, repo.mainRoot, dirtyCard ? dirty : null),
492
479
  options: [
493
480
  { label: copy.choices.keep, description: copy.cleanupDescriptions.keep },
494
481
  { label: copy.choices.worktree, description: dirtyCard ? copy.cleanupDescriptionsDirty.worktree : copy.cleanupDescriptions.worktree },
@@ -539,9 +526,8 @@ export function createLaneWorkflows(core) {
539
526
  * Auto paths (design D3) reuse this exact body with `{ by: 'host', auto: true,
540
527
  * approveMode }`: the scratch sync, the removal (forced ONLY behind the
541
528
  * user's informed explicit choice — a disclosed card choice or a typed
542
- * `--force`, never an auto/model path), the residue warning and the
543
- * blocked-removal semantics are identical; only the audit payload gains the
544
- * auto marker.
529
+ * `--force`, never an auto/model path) and the blocked-removal semantics are
530
+ * identical; only the audit payload gains the auto marker.
545
531
  * @param {any} session @param {string} laneId @param {'keep' | 'worktree' | 'all'} mode @param {{ by?: string, auto?: boolean, approveMode?: string, force?: boolean }} [options]
546
532
  */
547
533
  async function cleanup(session, laneId, mode, options = {}) {
@@ -557,9 +543,6 @@ export function createLaneWorkflows(core) {
557
543
  const kept = await apply(repo, laneId, { type: 'keep', by, patch: { cleanup: { mode: 'keep', at: now(), by } } })
558
544
  return result(kept, `kept ${kept.path}`)
559
545
  }
560
- // Read-only residue probe BEFORE anything is removed (design D5): a
561
- // warning only, never a gate, and it never mutates the authority.
562
- const residue = residueOf(repo, lane)
563
546
  const scratch = existsSync(lane.path) ? syncScratch(repo, lane) : null
564
547
  // `force` reaches git only from an informed explicit user choice (a
565
548
  // disclosed card choice via askCleanup, or the typed `--force`); auto and
@@ -591,7 +574,7 @@ export function createLaneWorkflows(core) {
591
574
  ? await patchLane(repo, laneId, () => ({ cleanup: record }))
592
575
  : await apply(repo, laneId, { type: 'clean', by, patch: { cleanup: record } })
593
576
  deps.audit('cleanup', { lane: laneId, mode, by, scratch, ...(force ? { forced: true } : {}), ...(options.auto === true ? { auto: true, approveMode: options.approveMode ?? null } : {}) }, repo.mainRoot, lane.ownerSession)
594
- return result(next, `removed ${lane.path}${branchNote}${scratch ? `; scratch copied to ${scratch}` : ''}${residue ? `; ${residueNote(residue)}` : ''}`)
577
+ return result(next, `removed ${lane.path}${branchNote}${scratch ? `; scratch copied to ${scratch}` : ''}`)
595
578
  }
596
579
 
597
580
  /**
@@ -651,7 +634,7 @@ export function createLaneWorkflows(core) {
651
634
  id: 'abandon',
652
635
  header: copy.abandonHeader,
653
636
  question: copy.abandonQuestion(lane.title),
654
- detail: copy.abandonDetail(lane, unmerged, repo.mainRoot, residueOf(repo, lane), reconciliation?.settled ? { child: reconciliation.childId, forced: reconciliation.forced } : null, dirtyCard ? dirty : null),
637
+ detail: copy.abandonDetail(lane, unmerged, repo.mainRoot, reconciliation?.settled ? { child: reconciliation.childId, forced: reconciliation.forced } : null, dirtyCard ? dirty : null),
655
638
  options: [
656
639
  { label: copy.choices.keep, description: copy.abandonDescriptions.keep },
657
640
  { label: copy.choices.worktree, description: dirtyCard ? copy.abandonDescriptionsDirty.worktree : copy.abandonDescriptions.worktree },
@@ -678,10 +661,6 @@ export function createLaneWorkflows(core) {
678
661
  const auditExtra = { ...(auto ? { auto: true, approveMode } : {}), ...(force ? { forced: true } : {}) }
679
662
  const abandoned = await apply(repo, laneId, { type: 'abandon', by: auto ? 'host' : 'user', reason: auto ? `abandoned automatically (${approveMode} mode)` : 'abandoned by the user', audit: Object.keys(auditExtra).length > 0 ? auditExtra : undefined, patch: { cleanup: { mode, at: now(), by: auto ? 'host' : 'user' } } })
680
663
  if (mode === 'keep') return result(abandoned, `abandoned; ${abandoned.path} and ${abandoned.branch} are kept`)
681
- // Read-only residue probe BEFORE anything is removed (design D5): a
682
- // warning only, never a gate, and it never mutates the authority. The
683
- // confirmation card already carried the warning when there was one.
684
- const residue = residueOf(repo, lane)
685
664
  const scratch = existsSync(lane.path) ? syncScratch(repo, lane) : null
686
665
  if (existsSync(lane.path)) {
687
666
  try {
@@ -701,7 +680,7 @@ export function createLaneWorkflows(core) {
701
680
  branchNote = `; deleted ${lane.branch}${unmerged > 0 ? ` (${unmerged} unmerged commit(s) discarded)` : ''}`
702
681
  }
703
682
  const final = await patchLane(repo, laneId, (current) => ({ cleanup: { ...current.cleanup, scratch } }))
704
- return result(final, `abandoned; removed ${lane.path}${branchNote}${residue ? `; ${residueNote(residue)}` : ''}`)
683
+ return result(final, `abandoned; removed ${lane.path}${branchNote}`)
705
684
  }
706
685
 
707
686
  // ─── watches (lane state subscriptions) ────────────────────────────────