@ordewell/core 0.12.0 → 0.12.1

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 (48) hide show
  1. package/dist/{ModeResolver-CHsaoV7M.d.ts → ModeResolver-B64aUZXJ.d.ts} +1 -1
  2. package/dist/{ModeResolver-b_7bPlBS.d.mts → ModeResolver-CA2fUU9h.d.mts} +1 -1
  3. package/dist/{Task-BTEH-ztX.d.mts → Task-CxGgN6I0.d.mts} +503 -16
  4. package/dist/{Task-BTEH-ztX.d.ts → Task-CxGgN6I0.d.ts} +503 -16
  5. package/dist/{chunk-BKTDYGIP.mjs → chunk-2IFKPYPO.mjs} +441 -17
  6. package/dist/chunk-2IFKPYPO.mjs.map +1 -0
  7. package/dist/{chunk-BFLVOYNR.mjs → chunk-FX33UKAD.mjs} +8 -6
  8. package/dist/chunk-FX33UKAD.mjs.map +1 -0
  9. package/dist/{chunk-F6WS3JLQ.mjs → chunk-PKJTN2PD.mjs} +20 -11
  10. package/dist/chunk-PKJTN2PD.mjs.map +1 -0
  11. package/dist/{chunk-HP2LNKST.mjs → chunk-RZ23BMVR.mjs} +345 -134
  12. package/dist/chunk-RZ23BMVR.mjs.map +1 -0
  13. package/dist/chunk-UDUATSCJ.mjs +48 -0
  14. package/dist/chunk-UDUATSCJ.mjs.map +1 -0
  15. package/dist/{IRunner-BwzyUVIL.d.mts → eventStore-CTCSKS9E.d.ts} +916 -129
  16. package/dist/{IRunner-DjJkYwko.d.ts → eventStore-CcB8yELZ.d.mts} +916 -129
  17. package/dist/index.d.mts +677 -97
  18. package/dist/index.d.ts +677 -97
  19. package/dist/index.js +8233 -2450
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +6951 -1772
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/parsing.d.mts +2 -2
  24. package/dist/parsing.d.ts +2 -2
  25. package/dist/parsing.js +277 -21
  26. package/dist/parsing.js.map +1 -1
  27. package/dist/parsing.mjs +3 -3
  28. package/dist/plan-utils.d.mts +8 -5
  29. package/dist/plan-utils.d.ts +8 -5
  30. package/dist/plan-utils.js +129 -16
  31. package/dist/plan-utils.js.map +1 -1
  32. package/dist/plan-utils.mjs +17 -3
  33. package/dist/{runSettingsText-x-YML2ay.d.mts → runSettingsText-DFVpe6aU.d.mts} +1142 -934
  34. package/dist/{runSettingsText-CvZ40IPv.d.ts → runSettingsText-lKLDsyli.d.ts} +1142 -934
  35. package/dist/testing.d.mts +83 -4
  36. package/dist/testing.d.ts +83 -4
  37. package/dist/testing.js +142 -1
  38. package/dist/testing.js.map +1 -1
  39. package/dist/testing.mjs +126 -2
  40. package/dist/testing.mjs.map +1 -1
  41. package/docker/task.Dockerfile +22 -0
  42. package/package.json +2 -1
  43. package/skills/diagnose/SKILL.md +2 -2
  44. package/skills/explore/SKILL.md +1 -1
  45. package/dist/chunk-BFLVOYNR.mjs.map +0 -1
  46. package/dist/chunk-BKTDYGIP.mjs.map +0 -1
  47. package/dist/chunk-F6WS3JLQ.mjs.map +0 -1
  48. package/dist/chunk-HP2LNKST.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { R as IRunnerSession, ar as QueuedTaskMessage, b6 as StructuredTurnEnd, b5 as StructuredEvent, bc as TaskCompleteArgs, t as CheckpointAnswer, aX as ScriptExit, be as TaskMessageFrom, aE as RunnerCommand, S as InvokedCommand, k as ApprovalDecision, O as IConfig, P as IFileSystem } from './IRunner-BwzyUVIL.mjs';
2
- import { J as IntegrationDisposal, aL as RunnerSessionCost, z as IWorktreeIsolation, L as IsolationAvailability, T as IsolationMergeResult, bf as TreeSnapshot, aQ as SetupFailure, W as IsolationPruneResult, X as IsolationRemoval, U as IsolationOutcome, _ as IsolationRevertResult, aH as RevertCommand, aw as RepairEvidence, a0 as IsolationRun, b1 as Task, ao as PrepareIntent, ap as PreparedTask, q as HandoffCommit, a7 as LandedWork, aI as RevertWatch, M as IsolationHandoff } from './Task-BTEH-ztX.mjs';
1
+ import { J as EventStore, bP as TreeEvent, bQ as TreeFact, aj as NodePath, K as ExecutionBackend, a4 as LeaseRequest, a5 as LeaseSpec, S as Guarantee, a3 as LeaseRepo, b5 as RunnerPlacement, a2 as LeaseRecord, T as GuaranteeReport, aR as ReservedLease, _ as IRunnerSession, aK as QueuedTaskMessage, bs as StructuredTurnEnd, br as StructuredEvent, by as TaskCompleteArgs, x as CheckpointAnswer, be as ScriptExit, bA as TaskMessageFrom, aX as RunnerCommand, $ as InvokedCommand, k as ApprovalDecision, V as IConfig, W as IFileSystem } from './eventStore-CcB8yELZ.mjs';
2
+ import { U as IntegrationDisposal, b4 as RunnerSessionCost, T as IWorktreeIsolation, W as IsolationAvailability, a2 as IsolationMergeResult, bE as TreeSnapshot, bb as SetupFailure, a5 as IsolationPruneResult, a6 as IsolationRemoval, a3 as IsolationOutcome, a9 as IsolationRevertResult, a$ as RevertCommand, aP as RepairEvidence, ab as IsolationRun, bp as Task, aH as PrepareIntent, aI as PreparedTask, J as HandoffCommit, ai as LandedWork, b0 as RevertWatch, X as IsolationHandoff } from './Task-CxGgN6I0.mjs';
3
3
  import 'child_process';
4
4
  import 'zod/v4';
5
5
 
@@ -107,6 +107,82 @@ declare class FakeRunnerSession implements IRunnerSession {
107
107
  emitTurnEnd(reason: StructuredTurnEnd): void;
108
108
  private deliver;
109
109
  }
110
+ /** An in-memory {@link EventStore}. `failing` makes the next appends reject, as a full disk would. */
111
+ declare class FakeEventStore implements EventStore {
112
+ readonly events: TreeEvent[];
113
+ failing: Error | null;
114
+ private closed;
115
+ append(events: readonly TreeFact[]): Promise<void>;
116
+ read(after: number, path?: NodePath): AsyncIterable<TreeEvent>;
117
+ close(): Promise<void>;
118
+ }
119
+ /**
120
+ * An in-memory {@link ExecutionBackend}, as {@link FakeWorktreeIsolation} fakes
121
+ * git: a count of slots and nothing else. `requests`, `opened` and `released`
122
+ * log what the seam was asked; `holdOpen` keeps an open waiting, `openError`
123
+ * fails one, `refuse` turns a role down, `provenGuarantees` is what its probe
124
+ * proves, and `freed` is what a release does, a pool's tick.
125
+ */
126
+ declare class FakeExecutionBackend implements ExecutionBackend {
127
+ /** Slots; a planner, a merge and a delegated task take none, as a flat run's do not. */
128
+ limit: number;
129
+ held: number;
130
+ requests: LeaseRequest[];
131
+ opened: {
132
+ request: LeaseRequest;
133
+ spec: LeaseSpec;
134
+ }[];
135
+ released: {
136
+ id: string;
137
+ reason: string;
138
+ }[];
139
+ letGoCalls: {
140
+ taskId: string;
141
+ keep: boolean;
142
+ reason: string;
143
+ }[];
144
+ openError: Error | null;
145
+ refuse: {
146
+ role: LeaseRequest['role'];
147
+ reason: string;
148
+ } | null;
149
+ provenGuarantees: readonly Guarantee[];
150
+ /** What exportResult reports of a session, in place of what it is told. */
151
+ sessionState: {
152
+ nativeSessionId: string;
153
+ } | null;
154
+ /** The repositories an open says its worktree holds. */
155
+ repos: readonly LeaseRepo[];
156
+ /** Where an open puts its runner, for a lease that does not run it on this host. */
157
+ placement: RunnerPlacement | null;
158
+ /** Brings a task's work back; absent, it is where the attempt ran. */
159
+ collect?: (taskId: string) => Promise<void>;
160
+ /** Waits on what it has running, as a closing Session does; absent, nothing outlives a runner. */
161
+ settle?: () => Promise<void>;
162
+ /** The workspace root's repositories; absent, none is known. */
163
+ checkoutRepos?: () => readonly LeaseRepo[];
164
+ records: LeaseRecord[];
165
+ freed: () => void;
166
+ private openHold;
167
+ private nextId;
168
+ /** Make the next open wait until the returned function is called. */
169
+ holdOpen(): () => void;
170
+ probe(): Promise<GuaranteeReport>;
171
+ private takesSlot;
172
+ refusal(request: LeaseRequest): string | null;
173
+ reserve(request: LeaseRequest): {
174
+ lease: ReservedLease;
175
+ } | {
176
+ refused: string;
177
+ };
178
+ private open;
179
+ letGo(taskId: string, { keep, landing, reason }: {
180
+ keep: boolean;
181
+ landing?: Promise<unknown> | null;
182
+ reason: string;
183
+ }): Promise<void>;
184
+ list(): Promise<readonly LeaseRecord[]>;
185
+ }
110
186
  type FakeIsolationCall = {
111
187
  op: 'isActive';
112
188
  workspaceRoot: string;
@@ -147,6 +223,8 @@ type FakeIsolationCall = {
147
223
  */
148
224
  declare class FakeWorktreeIsolation implements IWorktreeIsolation {
149
225
  availability: IsolationAvailability;
226
+ /** What `gitShortfall` answers. */
227
+ gitShortfallText: string | null;
150
228
  /** The repo paths `startRun` groups: a group of one at `.` unless set. */
151
229
  repos: string[];
152
230
  /** Per task id, the repos it changes; every repo of the group when not listed. */
@@ -204,10 +282,11 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
204
282
  holdRevert(): () => void;
205
283
  /** Make `verifyRepair` for this task wait until the returned function is called. */
206
284
  holdVerification(taskId: string): () => void;
285
+ gitShortfall(): Promise<string | null>;
207
286
  isActive(workspaceRoot: string): Promise<IsolationAvailability>;
208
287
  /** Like git: once the tracked changes are stashed, the tree is no longer dirty. */
209
288
  stash(workspaceRoot: string): Promise<void>;
210
- startRun(workspaceRoot: string): Promise<IsolationRun>;
289
+ startRun(workspaceRoot: string, stateRoot?: string): Promise<IsolationRun>;
211
290
  prepare(task: Task, run: IsolationRun, _intent?: PrepareIntent, opts?: {
212
291
  readOnly?: boolean;
213
292
  }): Promise<PreparedTask>;
@@ -237,4 +316,4 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
237
316
  sweep(): Promise<void>;
238
317
  }
239
318
 
240
- export { type FakeIsolationCall, FakeRunnerSession, FakeWorktreeIsolation, fakeConfig, fakeFileSystem, flushMicrotasks };
319
+ export { FakeEventStore, FakeExecutionBackend, type FakeIsolationCall, FakeRunnerSession, FakeWorktreeIsolation, fakeConfig, fakeFileSystem, flushMicrotasks };
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as IRunnerSession, ar as QueuedTaskMessage, b6 as StructuredTurnEnd, b5 as StructuredEvent, bc as TaskCompleteArgs, t as CheckpointAnswer, aX as ScriptExit, be as TaskMessageFrom, aE as RunnerCommand, S as InvokedCommand, k as ApprovalDecision, O as IConfig, P as IFileSystem } from './IRunner-DjJkYwko.js';
2
- import { J as IntegrationDisposal, aL as RunnerSessionCost, z as IWorktreeIsolation, L as IsolationAvailability, T as IsolationMergeResult, bf as TreeSnapshot, aQ as SetupFailure, W as IsolationPruneResult, X as IsolationRemoval, U as IsolationOutcome, _ as IsolationRevertResult, aH as RevertCommand, aw as RepairEvidence, a0 as IsolationRun, b1 as Task, ao as PrepareIntent, ap as PreparedTask, q as HandoffCommit, a7 as LandedWork, aI as RevertWatch, M as IsolationHandoff } from './Task-BTEH-ztX.js';
1
+ import { J as EventStore, bP as TreeEvent, bQ as TreeFact, aj as NodePath, K as ExecutionBackend, a4 as LeaseRequest, a5 as LeaseSpec, S as Guarantee, a3 as LeaseRepo, b5 as RunnerPlacement, a2 as LeaseRecord, T as GuaranteeReport, aR as ReservedLease, _ as IRunnerSession, aK as QueuedTaskMessage, bs as StructuredTurnEnd, br as StructuredEvent, by as TaskCompleteArgs, x as CheckpointAnswer, be as ScriptExit, bA as TaskMessageFrom, aX as RunnerCommand, $ as InvokedCommand, k as ApprovalDecision, V as IConfig, W as IFileSystem } from './eventStore-CTCSKS9E.js';
2
+ import { U as IntegrationDisposal, b4 as RunnerSessionCost, T as IWorktreeIsolation, W as IsolationAvailability, a2 as IsolationMergeResult, bE as TreeSnapshot, bb as SetupFailure, a5 as IsolationPruneResult, a6 as IsolationRemoval, a3 as IsolationOutcome, a9 as IsolationRevertResult, a$ as RevertCommand, aP as RepairEvidence, ab as IsolationRun, bp as Task, aH as PrepareIntent, aI as PreparedTask, J as HandoffCommit, ai as LandedWork, b0 as RevertWatch, X as IsolationHandoff } from './Task-CxGgN6I0.js';
3
3
  import 'child_process';
4
4
  import 'zod/v4';
5
5
 
@@ -107,6 +107,82 @@ declare class FakeRunnerSession implements IRunnerSession {
107
107
  emitTurnEnd(reason: StructuredTurnEnd): void;
108
108
  private deliver;
109
109
  }
110
+ /** An in-memory {@link EventStore}. `failing` makes the next appends reject, as a full disk would. */
111
+ declare class FakeEventStore implements EventStore {
112
+ readonly events: TreeEvent[];
113
+ failing: Error | null;
114
+ private closed;
115
+ append(events: readonly TreeFact[]): Promise<void>;
116
+ read(after: number, path?: NodePath): AsyncIterable<TreeEvent>;
117
+ close(): Promise<void>;
118
+ }
119
+ /**
120
+ * An in-memory {@link ExecutionBackend}, as {@link FakeWorktreeIsolation} fakes
121
+ * git: a count of slots and nothing else. `requests`, `opened` and `released`
122
+ * log what the seam was asked; `holdOpen` keeps an open waiting, `openError`
123
+ * fails one, `refuse` turns a role down, `provenGuarantees` is what its probe
124
+ * proves, and `freed` is what a release does, a pool's tick.
125
+ */
126
+ declare class FakeExecutionBackend implements ExecutionBackend {
127
+ /** Slots; a planner, a merge and a delegated task take none, as a flat run's do not. */
128
+ limit: number;
129
+ held: number;
130
+ requests: LeaseRequest[];
131
+ opened: {
132
+ request: LeaseRequest;
133
+ spec: LeaseSpec;
134
+ }[];
135
+ released: {
136
+ id: string;
137
+ reason: string;
138
+ }[];
139
+ letGoCalls: {
140
+ taskId: string;
141
+ keep: boolean;
142
+ reason: string;
143
+ }[];
144
+ openError: Error | null;
145
+ refuse: {
146
+ role: LeaseRequest['role'];
147
+ reason: string;
148
+ } | null;
149
+ provenGuarantees: readonly Guarantee[];
150
+ /** What exportResult reports of a session, in place of what it is told. */
151
+ sessionState: {
152
+ nativeSessionId: string;
153
+ } | null;
154
+ /** The repositories an open says its worktree holds. */
155
+ repos: readonly LeaseRepo[];
156
+ /** Where an open puts its runner, for a lease that does not run it on this host. */
157
+ placement: RunnerPlacement | null;
158
+ /** Brings a task's work back; absent, it is where the attempt ran. */
159
+ collect?: (taskId: string) => Promise<void>;
160
+ /** Waits on what it has running, as a closing Session does; absent, nothing outlives a runner. */
161
+ settle?: () => Promise<void>;
162
+ /** The workspace root's repositories; absent, none is known. */
163
+ checkoutRepos?: () => readonly LeaseRepo[];
164
+ records: LeaseRecord[];
165
+ freed: () => void;
166
+ private openHold;
167
+ private nextId;
168
+ /** Make the next open wait until the returned function is called. */
169
+ holdOpen(): () => void;
170
+ probe(): Promise<GuaranteeReport>;
171
+ private takesSlot;
172
+ refusal(request: LeaseRequest): string | null;
173
+ reserve(request: LeaseRequest): {
174
+ lease: ReservedLease;
175
+ } | {
176
+ refused: string;
177
+ };
178
+ private open;
179
+ letGo(taskId: string, { keep, landing, reason }: {
180
+ keep: boolean;
181
+ landing?: Promise<unknown> | null;
182
+ reason: string;
183
+ }): Promise<void>;
184
+ list(): Promise<readonly LeaseRecord[]>;
185
+ }
110
186
  type FakeIsolationCall = {
111
187
  op: 'isActive';
112
188
  workspaceRoot: string;
@@ -147,6 +223,8 @@ type FakeIsolationCall = {
147
223
  */
148
224
  declare class FakeWorktreeIsolation implements IWorktreeIsolation {
149
225
  availability: IsolationAvailability;
226
+ /** What `gitShortfall` answers. */
227
+ gitShortfallText: string | null;
150
228
  /** The repo paths `startRun` groups: a group of one at `.` unless set. */
151
229
  repos: string[];
152
230
  /** Per task id, the repos it changes; every repo of the group when not listed. */
@@ -204,10 +282,11 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
204
282
  holdRevert(): () => void;
205
283
  /** Make `verifyRepair` for this task wait until the returned function is called. */
206
284
  holdVerification(taskId: string): () => void;
285
+ gitShortfall(): Promise<string | null>;
207
286
  isActive(workspaceRoot: string): Promise<IsolationAvailability>;
208
287
  /** Like git: once the tracked changes are stashed, the tree is no longer dirty. */
209
288
  stash(workspaceRoot: string): Promise<void>;
210
- startRun(workspaceRoot: string): Promise<IsolationRun>;
289
+ startRun(workspaceRoot: string, stateRoot?: string): Promise<IsolationRun>;
211
290
  prepare(task: Task, run: IsolationRun, _intent?: PrepareIntent, opts?: {
212
291
  readOnly?: boolean;
213
292
  }): Promise<PreparedTask>;
@@ -237,4 +316,4 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
237
316
  sweep(): Promise<void>;
238
317
  }
239
318
 
240
- export { type FakeIsolationCall, FakeRunnerSession, FakeWorktreeIsolation, fakeConfig, fakeFileSystem, flushMicrotasks };
319
+ export { FakeEventStore, FakeExecutionBackend, type FakeIsolationCall, FakeRunnerSession, FakeWorktreeIsolation, fakeConfig, fakeFileSystem, flushMicrotasks };
package/dist/testing.js CHANGED
@@ -20,6 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/testing.ts
21
21
  var testing_exports = {};
22
22
  __export(testing_exports, {
23
+ FakeEventStore: () => FakeEventStore,
24
+ FakeExecutionBackend: () => FakeExecutionBackend,
23
25
  FakeRunnerSession: () => FakeRunnerSession,
24
26
  FakeWorktreeIsolation: () => FakeWorktreeIsolation,
25
27
  fakeConfig: () => fakeConfig,
@@ -73,6 +75,26 @@ function handoffOf(run) {
73
75
  var SKILL_NAME = new RegExp(`^${SKILL_NAME_PATTERN}$`);
74
76
  var DEFAULT_RUNNERS = ["claude-code"];
75
77
 
78
+ // src/services/executionLease.ts
79
+ var GUARANTEES = ["fs.hostHidden", "fs.writeScope", "net.allowlist", "net.noMetadata", "cred.unreadable", "kernel.separate", "limits.memory"];
80
+ var LeaseGuaranteeError = class extends Error {
81
+ constructor(unproven) {
82
+ super(`This session requires ${unproven.join(", ")}, which its execution backend could not prove.`);
83
+ this.unproven = unproven;
84
+ this.name = "LeaseGuaranteeError";
85
+ }
86
+ unproven;
87
+ };
88
+ function refuseUnproven(required, report) {
89
+ const unproven = required.filter((guarantee) => !report[guarantee].proven);
90
+ if (unproven.length > 0) throw new LeaseGuaranteeError(unproven);
91
+ }
92
+
93
+ // src/models/TreeEvent.ts
94
+ function isWithin(node, ancestor) {
95
+ return node === ancestor || node.startsWith(`${ancestor}/`);
96
+ }
97
+
76
98
  // src/testing.ts
77
99
  async function flushMicrotasks(rounds = 20) {
78
100
  for (let i = 0; i < rounds; i += 1) await Promise.resolve();
@@ -344,8 +366,121 @@ var FakeRunnerSession = class {
344
366
  this.emitEvent({ type: "turn_start", text, ...from ? { from } : {} });
345
367
  }
346
368
  };
369
+ var FakeEventStore = class {
370
+ events = [];
371
+ failing = null;
372
+ closed = false;
373
+ async append(events) {
374
+ if (this.closed) throw new Error("This event log is closed.");
375
+ if (this.failing) throw this.failing;
376
+ for (const event of events) this.events.push({ ...event, seq: this.events.length + 1 });
377
+ }
378
+ async *read(after, path) {
379
+ for (const event of [...this.events]) {
380
+ if (event.seq > after && (path === void 0 || isWithin(event.node, path))) yield event;
381
+ }
382
+ }
383
+ async close() {
384
+ this.closed = true;
385
+ }
386
+ };
387
+ var FakeExecutionBackend = class {
388
+ /** Slots; a planner, a merge and a delegated task take none, as a flat run's do not. */
389
+ limit = Infinity;
390
+ held = 0;
391
+ requests = [];
392
+ opened = [];
393
+ released = [];
394
+ letGoCalls = [];
395
+ openError = null;
396
+ refuse = null;
397
+ provenGuarantees = [];
398
+ /** What exportResult reports of a session, in place of what it is told. */
399
+ sessionState = null;
400
+ /** The repositories an open says its worktree holds. */
401
+ repos = [];
402
+ /** Where an open puts its runner, for a lease that does not run it on this host. */
403
+ placement = null;
404
+ /** Brings a task's work back; absent, it is where the attempt ran. */
405
+ collect;
406
+ /** Waits on what it has running, as a closing Session does; absent, nothing outlives a runner. */
407
+ settle;
408
+ /** The workspace root's repositories; absent, none is known. */
409
+ checkoutRepos;
410
+ records = [];
411
+ freed = () => void 0;
412
+ openHold = null;
413
+ nextId = 0;
414
+ /** Make the next open wait until the returned function is called. */
415
+ holdOpen() {
416
+ let open;
417
+ this.openHold = new Promise((resolve) => {
418
+ open = resolve;
419
+ });
420
+ return open;
421
+ }
422
+ async probe() {
423
+ const at = (/* @__PURE__ */ new Date(0)).toISOString();
424
+ return Object.fromEntries(GUARANTEES.map((g) => [g, { proven: this.provenGuarantees.includes(g), by: "fake probe", at }]));
425
+ }
426
+ takesSlot(request) {
427
+ return request.role === "task" || request.role === "script";
428
+ }
429
+ refusal(request) {
430
+ if (this.refuse?.role === request.role) return this.refuse.reason;
431
+ return this.takesSlot(request) && this.held >= this.limit ? `no slot for ${request.node.taskId ?? request.role}` : null;
432
+ }
433
+ reserve(request) {
434
+ this.requests.push(request);
435
+ const refused = this.refusal(request);
436
+ if (refused) return { refused };
437
+ const holdsSlot = this.takesSlot(request);
438
+ if (holdsSlot) this.held += 1;
439
+ const id = `lease-${++this.nextId}`;
440
+ let live = true;
441
+ return {
442
+ lease: {
443
+ id,
444
+ holdsSlot,
445
+ open: (spec) => this.open(request, spec),
446
+ release: (reason) => {
447
+ if (!live) return;
448
+ live = false;
449
+ this.released.push({ id, reason });
450
+ if (holdsSlot) this.held -= 1;
451
+ this.freed();
452
+ }
453
+ }
454
+ };
455
+ }
456
+ async open(request, spec) {
457
+ this.opened.push({ request, spec });
458
+ const guarantees = await this.probe();
459
+ refuseUnproven(spec.required, guarantees);
460
+ const hold = this.openHold;
461
+ this.openHold = null;
462
+ await hold;
463
+ if (this.openError) throw this.openError;
464
+ const worktree = spec.workspace.kind === "worktree";
465
+ return {
466
+ workspace: { cwd: worktree ? `/fake-lease/${spec.task?.id}` : "/repo", worktree, ...this.repos.length ? { repos: this.repos } : {} },
467
+ guarantees,
468
+ exportResult: (session) => ({ refs: {}, sessionState: { ...session, ...this.sessionState } }),
469
+ ...this.placement ? { placement: this.placement } : {}
470
+ };
471
+ }
472
+ async letGo(taskId, { keep, landing, reason }) {
473
+ await landing;
474
+ this.letGoCalls.push({ taskId, keep, reason });
475
+ }
476
+ async list() {
477
+ return this.records;
478
+ }
479
+ };
347
480
  var FakeWorktreeIsolation = class {
348
481
  availability = { active: true };
482
+ /** What `gitShortfall` answers. */
483
+ gitShortfallText = null;
349
484
  /** The repo paths `startRun` groups: a group of one at `.` unless set. */
350
485
  repos = [SELF_REPO];
351
486
  /** Per task id, the repos it changes; every repo of the group when not listed. */
@@ -423,6 +558,9 @@ var FakeWorktreeIsolation = class {
423
558
  }));
424
559
  return open;
425
560
  }
561
+ async gitShortfall() {
562
+ return this.gitShortfallText;
563
+ }
426
564
  async isActive(workspaceRoot) {
427
565
  this.log({ op: "isActive", workspaceRoot });
428
566
  return this.availability;
@@ -432,13 +570,14 @@ var FakeWorktreeIsolation = class {
432
570
  this.log({ op: "stash", workspaceRoot });
433
571
  if (!this.availability.active && this.availability.reason === "dirty") this.availability = { active: true };
434
572
  }
435
- async startRun(workspaceRoot) {
573
+ async startRun(workspaceRoot, stateRoot) {
436
574
  this.log({ op: "startRun", workspaceRoot });
437
575
  if (this.startRunError) throw this.startRunError;
438
576
  const id = `run${++this.runCount}`;
439
577
  return {
440
578
  id,
441
579
  workspaceRoot,
580
+ ...stateRoot !== void 0 ? { stateRoot } : {},
442
581
  repos: this.repos.map((repo) => ({
443
582
  path: repo,
444
583
  root: `${workspaceRoot}/${repo}`.replace(/\/\.$/, ""),
@@ -635,6 +774,8 @@ var FakeWorktreeIsolation = class {
635
774
  };
636
775
  // Annotate the CommonJS export names for ESM import in node:
637
776
  0 && (module.exports = {
777
+ FakeEventStore,
778
+ FakeExecutionBackend,
638
779
  FakeRunnerSession,
639
780
  FakeWorktreeIsolation,
640
781
  fakeConfig,