dorfl 0.13.2 → 0.13.4

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 (56) hide show
  1. package/dist/claim-cas.d.ts.map +1 -1
  2. package/dist/claim-cas.js +39 -0
  3. package/dist/claim-cas.js.map +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +10 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/complete.d.ts.map +1 -1
  8. package/dist/complete.js +9 -2
  9. package/dist/complete.js.map +1 -1
  10. package/dist/cwd-section.d.ts +40 -0
  11. package/dist/cwd-section.d.ts.map +1 -1
  12. package/dist/cwd-section.js +111 -5
  13. package/dist/cwd-section.js.map +1 -1
  14. package/dist/format.d.ts.map +1 -1
  15. package/dist/format.js +56 -0
  16. package/dist/format.js.map +1 -1
  17. package/dist/frontmatter.d.ts.map +1 -1
  18. package/dist/frontmatter.js +16 -4
  19. package/dist/frontmatter.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/item-lock.d.ts +167 -0
  25. package/dist/item-lock.d.ts.map +1 -1
  26. package/dist/item-lock.js +255 -1
  27. package/dist/item-lock.js.map +1 -1
  28. package/dist/needs-attention.d.ts +216 -1
  29. package/dist/needs-attention.d.ts.map +1 -1
  30. package/dist/needs-attention.js +595 -2
  31. package/dist/needs-attention.js.map +1 -1
  32. package/dist/reconcile-terminal.d.ts +97 -0
  33. package/dist/reconcile-terminal.d.ts.map +1 -0
  34. package/dist/reconcile-terminal.js +88 -0
  35. package/dist/reconcile-terminal.js.map +1 -0
  36. package/dist/scan.d.ts +17 -0
  37. package/dist/scan.d.ts.map +1 -1
  38. package/dist/scan.js +51 -2
  39. package/dist/scan.js.map +1 -1
  40. package/dist/status.d.ts +28 -0
  41. package/dist/status.d.ts.map +1 -1
  42. package/dist/status.js +79 -2
  43. package/dist/status.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/claim-cas.ts +40 -0
  46. package/src/cli.ts +18 -1
  47. package/src/complete.ts +9 -2
  48. package/src/cwd-section.ts +157 -4
  49. package/src/format.ts +72 -0
  50. package/src/frontmatter.ts +16 -4
  51. package/src/index.ts +2 -0
  52. package/src/item-lock.ts +369 -1
  53. package/src/needs-attention.ts +783 -0
  54. package/src/reconcile-terminal.ts +180 -0
  55. package/src/scan.ts +82 -5
  56. package/src/status.ts +131 -6
package/dist/status.d.ts CHANGED
@@ -83,6 +83,15 @@ export interface RepoLockEntries {
83
83
  /** The held lock entries (action × state + reason), sorted by entry. */
84
84
  entries: LockEntry[];
85
85
  }
86
+ /** One repo's STALE per-item locks: entries whose item has already come to rest
87
+ * in a terminal folder on that repo's `main`, so the hold is finished work
88
+ * awaiting release rather than an in-flight claim. */
89
+ export interface RepoStaleLocks {
90
+ /** The repo path whose lock refs were classified (a hub-mirror path). */
91
+ repoPath: string;
92
+ /** The stale lock `<entry>` names, sorted. */
93
+ entries: string[];
94
+ }
86
95
  /** One repo's one-slug-one-folder LINT result, surfaced for the dashboard. */
87
96
  export interface RepoLedgerDuplicates {
88
97
  /** The repo path whose `work/` ledger was linted (a hub-mirror path). */
@@ -109,6 +118,16 @@ export interface StatusReport {
109
118
  * populates it (possibly empty).
110
119
  */
111
120
  lockHeld?: RepoLockEntries[];
121
+ /**
122
+ * Per registered hub mirror, the lock `<entry>` names whose item is already at
123
+ * REST in a terminal folder on that mirror's `main`, finished work (typically
124
+ * a merged propose PR) whose lock has not been released yet. Reported
125
+ * SEPARATELY from {@link StatusReport.lockHeld}, and deliberately excluded from
126
+ * it, so completed work never reads as in-progress (the symptom of the
127
+ * propose-path lock leak). `status` RELEASES nothing by default, it is
128
+ * read-only; these drain on the next claim, or under `--reconcile-locks`.
129
+ */
130
+ staleLocks?: RepoStaleLocks[];
112
131
  /**
113
132
  * The one-slug-one-folder LINT (spec `ledger-integrity` story 3): per registered
114
133
  * hub mirror, any slug present in MORE THAN ONE `work/` status folder (a corrupt
@@ -155,6 +174,15 @@ export interface StatusOptions {
155
174
  * Omitted ⇒ only the job worktrees are reported.
156
175
  */
157
176
  mirrorPaths?: string[];
177
+ /**
178
+ * OPT-IN WRITE (`status --reconcile-locks`). `false`/omitted (the DEFAULT)
179
+ * keeps `status` strictly READ-ONLY: stale locks are classified and REPORTED
180
+ * via {@link StatusReport.staleLocks} and nothing on any arbiter is touched.
181
+ * `true` additionally RELEASES them. Not needed for routine convergence, the
182
+ * claim path already sweeps on every unit of work; this is the manual
183
+ * "drain them now" lever.
184
+ */
185
+ reconcileLocks?: boolean;
158
186
  /**
159
187
  * Sink for the fetch-first fall-back warning (ADR §5/§6): when a mirror's `main`
160
188
  * cannot be fetched, `status` warns through this and reads that mirror's
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,OAAO,EAAC,MAAM,cAAc,CAAC;AAG1D,OAAO,iBAAiB,CAAC;AACzB,OAAO,EAAC,KAAK,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAsB,KAAK,mBAAmB,EAAC,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAsB,KAAK,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAEnE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACzB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,KAAK,EAAE,QAAQ,CAAC;IAChB,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC/B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACpC,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,UAAU,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC5B,oDAAoD;IACpD,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,qFAAqF;IACrF,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC1C;;;;;OAKG;IACH,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,UAAU,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC7B,sEAAsE;IACtE,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;;;OAIG;IACH,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAkE1E;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAgGzD"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
package/dist/status.js CHANGED
@@ -5,7 +5,7 @@ import { resolveHarness } from './harness.js';
5
5
  import './pi-harness.js';
6
6
  import { fetchMirrorMainOrWarn } from './repo-mirror.js';
7
7
  import { formatArbiterStatus } from './arbiter.js';
8
- import { listItemLockEntries } from './item-lock.js';
8
+ import { classifyTerminalItemLocks, reconcileTerminalItemLocks, } from './item-lock.js';
9
9
  import { formatCwdSection, formatLockEntryLines } from './format.js';
10
10
  import { lintRefLedger, formatDuplicateWarnings, } from './ledger-lint.js';
11
11
  /**
@@ -42,22 +42,73 @@ export async function status(options) {
42
42
  // held (`active` = in-progress) AND stuck (`needs-attention`) entries + their
43
43
  // reasons/questions.
44
44
  const lockHeld = [];
45
+ const staleLocks = [];
45
46
  const ledgerDuplicates = [];
46
47
  for (const mirrorPath of options.mirrorPaths ?? []) {
47
48
  // Fetch-first (ADR §5/§6): refresh this mirror's `main` so the duplicate lint
48
49
  // reflects the remote truth. Never fatal — a failed fetch WARNS and falls back
49
50
  // to the mirror's last-known `main`.
50
51
  fetchMirrorMainOrWarn({ mirrorPath, warn: options.warn, env: options.env });
52
+ // CLASSIFY the mirror's held locks against its `main` (fix for the propose-path
53
+ // lock leak; observation
54
+ // `every-completed-task-leaves-its-lock-ref-reporting-in-progress`). A
55
+ // `complete --propose` KEEPS its lock held across the open PR and defers the
56
+ // release to the merge, an event no dorfl process is present for (no hook, no
57
+ // daemon), so the release never fired and every completed item reported
58
+ // in-progress for ever.
59
+ //
60
+ // `status` is READ-ONLY and stays that way: by DEFAULT we only classify, so a
61
+ // lock whose item has come to REST in a terminal folder on the mirror's `main`
62
+ // is reported as STALE rather than listed in-flight. The refs themselves are
63
+ // drained by the paths that already write on every unit of work (the claim
64
+ // path), or here under the explicit `--reconcile-locks` opt-in. An item on an
65
+ // OPEN PR (still in the pool on `main`) stays in-flight either way.
66
+ // Best-effort and never throws, any fault treats the lock as HELD.
67
+ //
68
+ // A hub mirror is a BARE clone with no `refs/remotes/*` namespace, so its copy
69
+ // of the arbiter's main is the plain `main` ref (the SAME ref
70
+ // `fetchMirrorMainOrWarn` above and `lintRefLedger` below read). Passing the
71
+ // default `origin/main` here would fail EVERY probe with `invalid object name`
72
+ // and silently classify every lock as in-flight.
73
+ const MIRROR_MAIN = { mainRef: 'main' };
74
+ let staleEntries = [];
75
+ let entries;
76
+ if (options.reconcileLocks === true) {
77
+ const reconciled = await reconcileTerminalItemLocks(mirrorPath, 'origin', options.env, MIRROR_MAIN);
78
+ if (reconciled.released.length > 0) {
79
+ options.warn?.(`${mirrorPath}: released ${reconciled.released.length} stale per-item ` +
80
+ 'lock(s) whose item is terminal on main (work landed; the ' +
81
+ `propose PR merged out-of-band): ${reconciled.released.join(', ')}`);
82
+ }
83
+ // Under an EXPLICIT drain request a refusal must be reported: the operator
84
+ // asked for these to go away, so silence would be a lie.
85
+ for (const e of reconciled.errors) {
86
+ options.warn?.(`${mirrorPath}: could not release '${e.entry}': ${e.message}`);
87
+ }
88
+ entries = reconciled.stillHeld;
89
+ }
90
+ else {
91
+ const classified = await classifyTerminalItemLocks(mirrorPath, 'origin', options.env, MIRROR_MAIN);
92
+ staleEntries = classified.terminal.map((l) => l.entry);
93
+ entries = classified.inFlight;
94
+ }
51
95
  // The PER-ITEM LOCK in-flight view (spec US #8): read the mirror's lock refs to
52
96
  // surface held (`active` = in-progress) and stuck (`needs-attention`) entries +
53
97
  // reasons/questions. A bare hub mirror's arbiter is its `origin` (the SAME
54
98
  // handle the `scan` held-slug subtraction reads). Best-effort: a fetch/read
55
99
  // fault yields an EMPTY list (see {@link listItemLockEntries}), so this
56
100
  // read-only view degrades to "no in-flight locks" rather than erroring.
57
- const entries = await listItemLockEntries(mirrorPath, 'origin', options.env);
101
+ // `entries` comes STRAIGHT from the classification above (its `inFlight`
102
+ // partition, or `stillHeld` after a sweep), so the stale ones are already
103
+ // excluded: finished work is not an in-flight hold, and listing it as one is
104
+ // the exact symptom being fixed. Reusing that result also avoids a SECOND
105
+ // `ls-remote` + fetch per mirror, and the TOCTOU window between two reads.
58
106
  if (entries.length > 0) {
59
107
  lockHeld.push({ repoPath: mirrorPath, entries });
60
108
  }
109
+ if (staleEntries.length > 0) {
110
+ staleLocks.push({ repoPath: mirrorPath, entries: staleEntries });
111
+ }
61
112
  // The one-slug-one-folder LINT (spec story 3): derive any slug residing in >1
62
113
  // status folder from the SAME freshly-fetched `main` ref, surfaced LOUDLY.
63
114
  const dups = lintRefLedger('main', mirrorPath, options.env);
@@ -66,11 +117,13 @@ export async function status(options) {
66
117
  }
67
118
  }
68
119
  lockHeld.sort((a, b) => a.repoPath.localeCompare(b.repoPath));
120
+ staleLocks.sort((a, b) => a.repoPath.localeCompare(b.repoPath));
69
121
  ledgerDuplicates.sort((a, b) => a.repoPath.localeCompare(b.repoPath));
70
122
  return {
71
123
  active,
72
124
  attention,
73
125
  lockHeld,
126
+ staleLocks,
74
127
  ledgerDuplicates,
75
128
  ...(options.arbiter ? { arbiter: options.arbiter } : {}),
76
129
  ...(options.cwd ? { cwd: options.cwd } : {}),
@@ -120,11 +173,17 @@ export function formatStatus(report) {
120
173
  const cwdLines = report.cwd !== undefined ? formatCwdSection(report.cwd) : [];
121
174
  const lockHeld = report.lockHeld ?? [];
122
175
  const lockCount = lockHeld.reduce((sum, r) => sum + r.entries.length, 0);
176
+ const staleLocks = report.staleLocks ?? [];
177
+ const staleCount = staleLocks.reduce((sum, r) => sum + r.entries.length, 0);
123
178
  const ledgerDuplicates = report.ledgerDuplicates ?? [];
124
179
  const dupCount = ledgerDuplicates.reduce((sum, r) => sum + r.duplicates.length, 0);
125
180
  if (report.active.length === 0 &&
126
181
  report.attention.length === 0 &&
127
182
  lockCount === 0 &&
183
+ // A repo whose ONLY locks are stale must not print "the work area is empty":
184
+ // that would turn the old "wrongly shown as in-progress" bug into a worse
185
+ // "not shown at all" one.
186
+ staleCount === 0 &&
128
187
  dupCount === 0 &&
129
188
  report.arbiter === undefined &&
130
189
  cwdLines.length === 0) {
@@ -170,6 +229,24 @@ export function formatStatus(report) {
170
229
  }
171
230
  }
172
231
  }
232
+ // STALE locks: finished work whose lock has not been released yet (the
233
+ // propose-path lock leak). Rendered as its OWN block and deliberately NOT under
234
+ // "In-flight locks" above, because reporting completed work as in-progress for
235
+ // ever was the symptom. `status` is read-only, so it names the state and how it
236
+ // clears rather than clearing it.
237
+ if (staleCount > 0) {
238
+ lines.push('');
239
+ lines.push(`Completed, lock not yet released (${staleCount}; the item is at rest on ` +
240
+ 'main, so this is NOT in flight):');
241
+ for (const repo of staleLocks) {
242
+ lines.push(` ${repo.repoPath}`);
243
+ for (const entry of repo.entries) {
244
+ lines.push(` ${entry}`);
245
+ }
246
+ }
247
+ lines.push(' These clear automatically on the next claim. To drain them now: ' +
248
+ '`dorfl status --reconcile-locks`.');
249
+ }
173
250
  // The one-slug-one-folder LINT (spec `ledger-integrity` story 3): WARN LOUDLY
174
251
  // about every slug residing in >1 status folder of a registered mirror's ledger
175
252
  // (a corrupt ledger — never a silent pass). A human must resolve each.
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dorfl",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/wighawag/dorfl.git",
package/src/claim-cas.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {runAsync, type RunResult} from './git.js';
2
2
  import {acquireItemLock, releaseItemLock, heldTaskSlugs} from './item-lock.js';
3
+ import {reconcileTerminalState} from './reconcile-terminal.js';
3
4
  import {extractPromptSection} from './prompt.js';
4
5
  import {resolveReadiness} from './readiness.js';
5
6
  import {workBranchRef} from './slug-namespace.js';
@@ -386,6 +387,45 @@ async function runClaim(
386
387
  }
387
388
  }
388
389
 
390
+ // LAZY RECONCILIATION of TERMINAL-STATE RESIDUE: the stale per-item lock a
391
+ // propose PR leaves behind (observation
392
+ // `every-completed-task-leaves-its-lock-ref-reporting-in-progress`) AND the
393
+ // stranded sidecar + `needsAnswers` flag a bounced-then-rebuilt item leaves
394
+ // behind (observation
395
+ // `a-rebuilt-task-leaves-its-bounce-question-asking-to-cancel-a-merged-task`).
396
+ // ONE pass, because they are the SAME defect wearing two hats: both are cleared
397
+ // by a step that only runs on a path the item did not take, both become moot at
398
+ // exactly the same moment (the done-move landing on `main`), and both are
399
+ // otherwise detectable only from a loop the manual path never enters.
400
+ //
401
+ // `complete --propose` deliberately KEEPS its per-item lock held across the open
402
+ // PR and promises "It is released when the PR merges (reconciled against main)".
403
+ // That release could never fire: no dorfl process runs when a human clicks merge
404
+ // on GitHub, there is no merge hook, and there is no daemon, so every completed
405
+ // propose item leaked its lock ref permanently.
406
+ //
407
+ // THIS is where the release belongs, and why it is here rather than on `status`:
408
+ // the merge EVENT is unobservable, but its CONSEQUENCE on `main` is durable, so
409
+ // any later sweep converges just as well as a timely one. `claim` is the natural
410
+ // host, it ALREADY writes to the arbiter (it is about to acquire a lock), it
411
+ // ALREADY fetched `main` just above, and it runs on EVERY unit of work, so the
412
+ // leaked set drains continuously without a human ever being asked to run a
413
+ // clean-up verb. Keeping the release on a write path is what makes the fix real:
414
+ // `gc --ledger` had been reporting these very locks and printing the
415
+ // `release-lock` command all along, and that offer is precisely what nobody was
416
+ // routed to. The read-only surfaces (`status`, `scan`) therefore only CLASSIFY.
417
+ //
418
+ // It acts EXACTLY on items that have come to REST in a terminal folder on
419
+ // `<arbiter>/main`; an item on an OPEN PR still shows its body in the pool and
420
+ // keeps both its lock and its question state, as does a genuinely stuck one.
421
+ // Clearing a live `needsAnswers` would be as bad as releasing a live lock: it
422
+ // would disarm a gate and hand gated work to agents. Best-effort and never throws:
423
+ // any fault leaves every lock HELD (the safe direction) and never fails the
424
+ // claim, which is unrelated work, this sweep is opportunistic hygiene, not a
425
+ // precondition. It also cannot affect THIS claim's outcome: an item terminal on
426
+ // `main` is not in the claimable pool anyway.
427
+ await reconcileTerminalState({cwd, arbiter, env});
428
+
389
429
  // Acquire the lock FIRST. A lock `lost` (someone already holds this SAME item's
390
430
  // lock) makes claim lose DEFINITIVELY — exit 2, NO retry budget (a per-item ref
391
431
  // never falsely contends, so a rejection is a GENUINE same-item conflict the
package/src/cli.ts CHANGED
@@ -167,6 +167,7 @@ interface ScanFlags {
167
167
  autoBuild?: boolean;
168
168
  json?: boolean;
169
169
  here?: boolean;
170
+ reconcileLocks?: boolean;
170
171
  arbiterRemote?: string;
171
172
  arbiter?: string;
172
173
  }
@@ -1029,6 +1030,7 @@ interface StatusFlags {
1029
1030
  arbiter?: string;
1030
1031
  noArbiter?: boolean;
1031
1032
  here?: boolean;
1033
+ reconcileLocks?: boolean;
1032
1034
  json?: boolean;
1033
1035
  }
1034
1036
 
@@ -1385,6 +1387,10 @@ export function buildProgram(): Command {
1385
1387
  '--here',
1386
1388
  'report ONLY the current repo (the cwd working tree, fetch-first): skip the cross-repo registry loop entirely. The fast, focused path — no N-mirror fetches.',
1387
1389
  )
1390
+ .option(
1391
+ '--reconcile-locks',
1392
+ 'WRITE: release the per-item locks whose item is already at rest in a terminal folder on the arbiter’s main (e.g. a merged propose PR left its lock behind). Without this flag `scan` is strictly read-only. Not normally needed, the claim path releases them automatically on every unit of work. An item that is NOT terminal on main (a live build, an open PR, a stuck item) is NEVER released, with or without the flag.',
1393
+ )
1388
1394
  .option('--json', 'output the raw report as JSON')
1389
1395
  .action(async (flags: ScanFlags, command: Commander) => {
1390
1396
  const fileConfig = loadConfig(flags.config);
@@ -1403,6 +1409,7 @@ export function buildProgram(): Command {
1403
1409
  override,
1404
1410
  arbiterRemote: flags.arbiterRemote,
1405
1411
  lockArbiterRemote: flags.arbiter ?? 'origin',
1412
+ reconcileLocks: flags.reconcileLocks === true,
1406
1413
  warn,
1407
1414
  });
1408
1415
  // `--here`: report ONLY the cwd — skip the registry loop ENTIRELY (the fast,
@@ -1425,7 +1432,11 @@ export function buildProgram(): Command {
1425
1432
  }
1426
1433
  return;
1427
1434
  }
1428
- const report = await scan(config, {warn, override});
1435
+ const report = await scan(config, {
1436
+ warn,
1437
+ override,
1438
+ reconcileLocks: flags.reconcileLocks === true,
1439
+ });
1429
1440
  // The cwd-local section: resolve it ONLY when a participating cwd is NOT
1430
1441
  // already covered by a registered mirror. A FETCH-FREE pre-check
1431
1442
  // (`cwdSectionDisposition`) decides this with zero network I/O; an
@@ -3908,6 +3919,10 @@ export function buildProgram(): Command {
3908
3919
  '--here',
3909
3920
  'report ONLY the current repo (the cwd working tree, fetch-first): skip the jobs, registry-mirror, and arbiter sections entirely. "This repo, nothing else" — the fast, focused path.',
3910
3921
  )
3922
+ .option(
3923
+ '--reconcile-locks',
3924
+ 'WRITE: release the per-item locks reported as "Completed, lock not yet released" (item already at rest in a terminal folder on the arbiter’s main, e.g. a merged propose PR). Without this flag `status` is strictly read-only and only REPORTS them. Not normally needed, the claim path releases them automatically on every unit of work; this is the manual “drain them now” lever. An item that is NOT terminal on main (a live build, an open PR, a stuck item) is NEVER released, with or without the flag.',
3925
+ )
3911
3926
  .option('--json', 'output the raw report as JSON')
3912
3927
  .action(async (flags: StatusFlags) => {
3913
3928
  const config = resolveGlobalConfig(loadConfig(flags.config), {});
@@ -3923,6 +3938,7 @@ export function buildProgram(): Command {
3923
3938
  override,
3924
3939
  arbiterRemote: flags.arbiterRemote ?? DEFAULT_ARBITER_REMOTE,
3925
3940
  lockArbiterRemote: flags.arbiter ?? 'origin',
3941
+ reconcileLocks: flags.reconcileLocks === true,
3926
3942
  warn,
3927
3943
  });
3928
3944
  // `--here`: report ONLY the cwd — skip the jobs, registry-mirror, and arbiter
@@ -3976,6 +3992,7 @@ export function buildProgram(): Command {
3976
3992
  mirrorPaths,
3977
3993
  arbiter,
3978
3994
  cwd: cwdSection,
3995
+ reconcileLocks: flags.reconcileLocks === true,
3979
3996
  warn,
3980
3997
  });
3981
3998
  if (flags.json) {
package/src/complete.ts CHANGED
@@ -1260,10 +1260,17 @@ async function releaseClaimLockAfterDurableMove(
1260
1260
  if (!durablyOnMain) {
1261
1261
  // PROPOSE, PR open: the done-move is on the PR branch, not on `main`. KEEP the
1262
1262
  // lock held so the in-flight task is not re-claimed during the review window.
1263
+ // The wording used to promise "It is released when the PR merges (reconciled
1264
+ // against main)", which was FALSE: no dorfl process is present at merge time
1265
+ // (no hook, no daemon), so nothing could act on that event and every propose
1266
+ // build leaked its lock ref for ever. The release actually happens on the next
1267
+ // command that writes to the arbiter (the claim path sweeps stale terminal
1268
+ // locks), so the message now names a trigger that really fires.
1263
1269
  note(
1264
1270
  `'${slug}': keeping the per-item lock HELD (propose PR open; the work is ` +
1265
- 'not yet on main). It is released when the PR merges (reconciled against ' +
1266
- 'main).',
1271
+ 'not yet on main). Once the PR merges, the item comes to rest on main and ' +
1272
+ 'the lock is released by the next claim (or now, with `dorfl status ' +
1273
+ '--reconcile-locks`).',
1267
1274
  );
1268
1275
  return;
1269
1276
  }
@@ -8,7 +8,14 @@ import {
8
8
  heldTaskSlugsStrict,
9
9
  heldSpecSlugsStrict,
10
10
  listItemLockEntries,
11
+ classifyTerminalItemLocks,
12
+ reconcileTerminalItemLocks,
13
+ type LockEntry,
11
14
  } from './item-lock.js';
15
+ import {
16
+ classifyTerminalQuestionResidue,
17
+ reconcileTerminalQuestionResidue,
18
+ } from './needs-attention.js';
12
19
  import type {Config} from './config.js';
13
20
  import type {ConfigOverrideMap} from './config-override.js';
14
21
 
@@ -105,6 +112,36 @@ export interface CwdSection {
105
112
  registeredMirrorPath?: string;
106
113
  /** The cwd repo's arbiter + divergence (fetch-first). Absent when no arbiter. */
107
114
  arbiter?: CwdArbiter;
115
+ /**
116
+ * Lock `<entry>` names whose item is already at REST in a terminal folder on
117
+ * `<arbiter>/main`, finished work (typically a merged propose PR) whose lock
118
+ * has not been released yet. These are STALE, not in flight, and are reported
119
+ * separately from {@link RepoReport.lockHeld} so completed work never reads as
120
+ * in-progress. Purely a CLASSIFICATION: `status` releases nothing (it is
121
+ * read-only). They are released automatically by the next claim, or on demand
122
+ * via `--reconcile-locks` / `dorfl gc --ledger --reap-stale-locks`.
123
+ */
124
+ staleLocks?: string[];
125
+ /**
126
+ * Lock `<entry>` names this call actually RELEASED. Populated ONLY under the
127
+ * explicit `reconcileLocks` opt-in (`status --reconcile-locks`), which is the
128
+ * one way this otherwise read-only resolver writes to the arbiter. Empty/absent
129
+ * ⇒ nothing was written.
130
+ */
131
+ reconciledLocks?: string[];
132
+ /**
133
+ * Items resting in a TERMINAL folder on `main` that still carry question state
134
+ * (a stale bounce sidecar, and often a stranded `needsAnswers:true` gate over
135
+ * shipped work). Reported so the residue is visible; `status` clears nothing.
136
+ * These drain on the next claim.
137
+ */
138
+ staleQuestions?: string[];
139
+ /**
140
+ * Terminal items whose sidecar carries a human's ANSWER that was never applied.
141
+ * Deliberately NEVER auto-drained (the answer is prose the tool did not
142
+ * author), so it is surfaced for a human instead.
143
+ */
144
+ unappliedAnswers?: string[];
108
145
  }
109
146
 
110
147
  /** True iff the working repo has a git remote with name `remote`. The held-lock
@@ -204,6 +241,16 @@ export interface ResolveCwdSectionOptions {
204
241
  * arbiter even when the divergence remote is absent. Default `origin`.
205
242
  */
206
243
  lockArbiterRemote?: string;
244
+ /**
245
+ * OPT-IN WRITE (`status --reconcile-locks`). `false`/omitted (the DEFAULT)
246
+ * keeps this resolver strictly READ-ONLY: stale locks are classified and
247
+ * REPORTED via {@link CwdSection.staleLocks}, and nothing on the arbiter is
248
+ * touched. `true` additionally RELEASES the locks whose item is terminal on
249
+ * `<arbiter>/main`, reporting them via {@link CwdSection.reconciledLocks}.
250
+ * Not needed for routine convergence, the claim path already sweeps on every
251
+ * unit of work; this is the manual "drain them now" lever.
252
+ */
253
+ reconcileLocks?: boolean;
207
254
  /** Sink for the fetch-first fall-back warning (warn + last-known, never error). */
208
255
  warn?: (message: string) => void;
209
256
  env?: NodeJS.ProcessEnv;
@@ -261,6 +308,102 @@ export async function resolveCwdSection(
261
308
  // nothing to fail against — it keeps the empty set.
262
309
  const lockRemote = options.lockArbiterRemote ?? 'origin';
263
310
  const hasLockRemote = remoteExists(cwd, lockRemote, env);
311
+
312
+ // 2a. CLASSIFY the held locks against `main` (fix for the propose-path lock
313
+ // leak; observation
314
+ // `every-completed-task-leaves-its-lock-ref-reporting-in-progress`).
315
+ // `complete --propose` deliberately keeps the lock HELD across the open PR
316
+ // and promises it is "released when the PR merges (reconciled against
317
+ // main)", but NO dorfl process runs when a human clicks merge on GitHub,
318
+ // and there is no daemon, so that release could never fire. Every completed
319
+ // propose item therefore kept its lock ref and was reported in-progress for
320
+ // ever.
321
+ //
322
+ // `status` is READ-ONLY and stays that way: by DEFAULT this only
323
+ // CLASSIFIES, splitting the held locks into genuinely in-flight ones and
324
+ // ones whose item has come to REST in a terminal folder on
325
+ // `<arbiter>/main`. That alone fixes the MIS-REPORTING half of the bug
326
+ // finished work stops reading as in-progress, without this command
327
+ // mutating the arbiter. The REF is drained by the paths that already write
328
+ // on every unit of work (the claim path), or on demand here via the
329
+ // explicit `reconcileLocks` opt-in below.
330
+ //
331
+ // The terminal test is the item's POSITION ON MAIN, never a branch or a
332
+ // PR, so an item on an OPEN PR (body still in the pool on `main`) stays
333
+ // in-flight, as does a genuinely stuck one. Ordered BEFORE the held-slug
334
+ // reads below so, under the opt-in, the subtraction and the in-flight
335
+ // surface both see the reconciled truth in the SAME run.
336
+ //
337
+ // Best-effort by construction: neither call throws, and every uncertainty
338
+ // resolves to KEEP, so an offline or push-denied checkout degrades to the
339
+ // previous behaviour.
340
+ //
341
+ // The cwd is a WORKING CLONE, so the arbiter's main is the remote-tracking
342
+ // ref `<lockRemote>/main`, which is the default. (A bare hub mirror would
343
+ // need `mainRef: 'main'`; see the mirror callers in `status`/`scan`.)
344
+ let staleLocks: string[] = [];
345
+ let reconciledLocks: string[] = [];
346
+ // The in-flight entries, taken straight from the classification so the surface
347
+ // below needs no second `ls-remote` + fetch. `undefined` = not read at all.
348
+ let inFlightLocks: LockEntry[] | undefined;
349
+ if (hasLockRemote) {
350
+ if (options.reconcileLocks === true) {
351
+ // The ONLY write this resolver performs, and only when explicitly asked.
352
+ const swept = await reconcileTerminalItemLocks(cwd, lockRemote, env);
353
+ reconciledLocks = swept.released;
354
+ inFlightLocks = swept.stillHeld;
355
+ // Under an EXPLICIT drain request a refusal must be reported: the operator
356
+ // asked for these to go away, so silence would be a lie. (On the claim
357
+ // path the same errors are ignored on purpose: there the sweep is
358
+ // opportunistic hygiene, not something the operator asked for.)
359
+ for (const e of swept.errors) {
360
+ options.warn?.(`could not release lock '${e.entry}': ${e.message}`);
361
+ }
362
+ } else {
363
+ const classified = await classifyTerminalItemLocks(cwd, lockRemote, env);
364
+ staleLocks = classified.terminal.map((l) => l.entry);
365
+ inFlightLocks = classified.inFlight;
366
+ }
367
+ }
368
+
369
+ // 2b. The OTHER half of the same terminal residue: a bounced-then-rebuilt item
370
+ // whose sidecar + `needsAnswers` flag were never cleared (observation
371
+ // `a-rebuilt-task-leaves-its-bounce-question-asking-to-cancel-a-merged-task`).
372
+ // READ-ONLY here, exactly like the lock half: `status` reports it and the
373
+ // claim path drains it. Best-effort; a fault yields an empty report.
374
+ let staleQuestions: string[] = [];
375
+ let unappliedAnswers: string[] = [];
376
+ if (hasLockRemote && options.reconcileLocks !== true) {
377
+ const residue = await classifyTerminalQuestionResidue({
378
+ cwd,
379
+ arbiter: lockRemote,
380
+ mainRef: `${lockRemote}/main`,
381
+ env,
382
+ });
383
+ // BOTH halves of the residue are reported under one heading: a stale sidecar
384
+ // (with or without its flag) and an armed gate with no sidecar left are the
385
+ // same defect seen from either side, and both drain on the same pass.
386
+ staleQuestions = [
387
+ ...residue.drainable.map((r) => r.item),
388
+ ...residue.staleFlags.map((r) => r.item),
389
+ ];
390
+ unappliedAnswers = residue.answeredHeld.map((r) => r.item);
391
+ } else if (hasLockRemote) {
392
+ // Under the explicit opt-in the question residue is DRAINED alongside the
393
+ // locks, through the one shared pass.
394
+ const drained = await reconcileTerminalQuestionResidue({
395
+ cwd,
396
+ arbiter: lockRemote,
397
+ mainRef: `${lockRemote}/main`,
398
+ env,
399
+ note: options.warn,
400
+ });
401
+ unappliedAnswers = drained.answeredHeld;
402
+ for (const e of drained.errors) {
403
+ options.warn?.(`could not drain questions for '${e.item}': ${e.message}`);
404
+ }
405
+ }
406
+
264
407
  const heldSlugs = hasLockRemote
265
408
  ? await heldTaskSlugsStrict(cwd, lockRemote, env)
266
409
  : new Set<string>();
@@ -307,10 +450,16 @@ export async function resolveCwdSection(
307
450
  // different fault models (selection fails closed; the surface degrades).
308
451
  // Read from the SAME coordination remote the subtraction used. A repo with no
309
452
  // coordination remote has no lock refs to read — empty surface.
310
- if (repo !== undefined && hasLockRemote) {
311
- const lockHeld = await listItemLockEntries(cwd, lockRemote, env);
312
- if (lockHeld.length > 0) {
313
- repo.lockHeld = lockHeld;
453
+ if (repo !== undefined && inFlightLocks !== undefined) {
454
+ // Taken STRAIGHT from the classification in 2a, so the stale ones are already
455
+ // excluded. This is the reporting half of the propose-lock-leak fix: a lock
456
+ // whose item is already at REST on `main` is finished work, NOT an in-flight
457
+ // hold, so it must not appear under "In progress" (the symptom being fixed:
458
+ // completed tasks reported in-progress for ever). It is surfaced under
459
+ // `staleLocks` instead. Reusing that result also avoids a second `ls-remote` +
460
+ // fetch and the TOCTOU window between two separate reads.
461
+ if (inFlightLocks.length > 0) {
462
+ repo.lockHeld = inFlightLocks;
314
463
  }
315
464
  }
316
465
 
@@ -326,6 +475,10 @@ export async function resolveCwdSection(
326
475
  path: cwd,
327
476
  participating: true,
328
477
  repo,
478
+ staleLocks,
479
+ reconciledLocks,
480
+ staleQuestions,
481
+ unappliedAnswers,
329
482
  totalItems: localReport.totalItems,
330
483
  totalEligible: localReport.totalEligible,
331
484
  alsoRegistered,