@ordewell/core 0.11.3 → 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.
- package/AUTHORS.md +3 -1
- package/dist/{ModeResolver-DyXeZ9ib.d.ts → ModeResolver-B64aUZXJ.d.ts} +1 -1
- package/dist/{ModeResolver-BDAgouuE.d.mts → ModeResolver-CA2fUU9h.d.mts} +1 -1
- package/dist/{Task-BL4Idm9r.d.mts → Task-CxGgN6I0.d.mts} +801 -39
- package/dist/{Task-BL4Idm9r.d.ts → Task-CxGgN6I0.d.ts} +801 -39
- package/dist/{chunk-YAFNYNFX.mjs → chunk-2IFKPYPO.mjs} +453 -17
- package/dist/chunk-2IFKPYPO.mjs.map +1 -0
- package/dist/{chunk-MYXMIVWA.mjs → chunk-FX33UKAD.mjs} +8 -6
- package/dist/chunk-FX33UKAD.mjs.map +1 -0
- package/dist/{chunk-QTZ5XTBY.mjs → chunk-PKJTN2PD.mjs} +458 -13
- package/dist/chunk-PKJTN2PD.mjs.map +1 -0
- package/dist/{chunk-7WNLN63J.mjs → chunk-RZ23BMVR.mjs} +599 -124
- package/dist/chunk-RZ23BMVR.mjs.map +1 -0
- package/dist/chunk-UDUATSCJ.mjs +48 -0
- package/dist/chunk-UDUATSCJ.mjs.map +1 -0
- package/dist/{IRunner-DOMtDTVt.d.ts → eventStore-CTCSKS9E.d.ts} +976 -145
- package/dist/{IRunner-ihalYfTo.d.mts → eventStore-CcB8yELZ.d.mts} +976 -145
- package/dist/index.d.mts +905 -164
- package/dist/index.d.ts +905 -164
- package/dist/index.js +12496 -3308
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10830 -2919
- package/dist/index.mjs.map +1 -1
- package/dist/parsing.d.mts +2 -2
- package/dist/parsing.d.ts +2 -2
- package/dist/parsing.js +277 -21
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +8 -5
- package/dist/plan-utils.d.ts +8 -5
- package/dist/plan-utils.js +262 -33
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +39 -3
- package/dist/{runSettingsText-DVOd7vKC.d.ts → runSettingsText-DFVpe6aU.d.mts} +1996 -895
- package/dist/{runSettingsText-BuLVBlXS.d.mts → runSettingsText-lKLDsyli.d.ts} +1996 -895
- package/dist/testing.d.mts +99 -4
- package/dist/testing.d.ts +99 -4
- package/dist/testing.js +213 -8
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +193 -8
- package/dist/testing.mjs.map +1 -1
- package/docker/task.Dockerfile +22 -0
- package/package.json +3 -1
- package/skills/diagnose/SKILL.md +2 -2
- package/skills/explore/SKILL.md +1 -1
- package/dist/chunk-7WNLN63J.mjs.map +0 -1
- package/dist/chunk-MYXMIVWA.mjs.map +0 -1
- package/dist/chunk-QTZ5XTBY.mjs.map +0 -1
- package/dist/chunk-YAFNYNFX.mjs.map +0 -1
package/dist/testing.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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;
|
|
@@ -131,6 +207,9 @@ type FakeIsolationCall = {
|
|
|
131
207
|
} | {
|
|
132
208
|
op: 'discard';
|
|
133
209
|
integration: IntegrationDisposal;
|
|
210
|
+
} | {
|
|
211
|
+
op: 'readLanded' | 'revert';
|
|
212
|
+
taskIds: readonly string[];
|
|
134
213
|
};
|
|
135
214
|
/**
|
|
136
215
|
* An in-memory {@link IWorktreeIsolation} for scheduling tests: no git, no
|
|
@@ -144,6 +223,8 @@ type FakeIsolationCall = {
|
|
|
144
223
|
*/
|
|
145
224
|
declare class FakeWorktreeIsolation implements IWorktreeIsolation {
|
|
146
225
|
availability: IsolationAvailability;
|
|
226
|
+
/** What `gitShortfall` answers. */
|
|
227
|
+
gitShortfallText: string | null;
|
|
147
228
|
/** The repo paths `startRun` groups: a group of one at `.` unless set. */
|
|
148
229
|
repos: string[];
|
|
149
230
|
/** Per task id, the repos it changes; every repo of the group when not listed. */
|
|
@@ -179,23 +260,33 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
|
|
|
179
260
|
removal: IsolationRemoval;
|
|
180
261
|
/** Per task id; a task not listed integrates as `merged`. */
|
|
181
262
|
outcomes: Map<string, IsolationOutcome>;
|
|
263
|
+
/** What `revert` stops with; a revert that goes through when null. */
|
|
264
|
+
revertStop: Exclude<IsolationRevertResult, {
|
|
265
|
+
outcome: 'reverted';
|
|
266
|
+
}> | null;
|
|
267
|
+
/** What `readLanded` finds of a task's work in the user's branch, by task id. */
|
|
268
|
+
inBranch: Map<string, RevertCommand[]>;
|
|
182
269
|
/** Per task id, what `verifyRepair` finds; a task not listed passes. */
|
|
183
270
|
repairEvidence: Map<string, RepairEvidence>;
|
|
184
271
|
calls: FakeIsolationCall[];
|
|
185
272
|
private holds;
|
|
186
273
|
private verifyHolds;
|
|
274
|
+
private revertHold;
|
|
187
275
|
private runCount;
|
|
188
276
|
private log;
|
|
189
277
|
/** Task ids in the order `op` was called for them. */
|
|
190
278
|
taskIdsFor(op: 'prepare' | 'reopen' | 'verifyRepair' | 'integrate' | 'release'): string[];
|
|
191
279
|
/** Make `integrate` for this task wait until the returned function is called. */
|
|
192
280
|
holdIntegration(taskId: string): () => void;
|
|
281
|
+
/** Make the next `revert` wait, past its `refusal`, until the returned function is called. */
|
|
282
|
+
holdRevert(): () => void;
|
|
193
283
|
/** Make `verifyRepair` for this task wait until the returned function is called. */
|
|
194
284
|
holdVerification(taskId: string): () => void;
|
|
285
|
+
gitShortfall(): Promise<string | null>;
|
|
195
286
|
isActive(workspaceRoot: string): Promise<IsolationAvailability>;
|
|
196
287
|
/** Like git: once the tracked changes are stashed, the tree is no longer dirty. */
|
|
197
288
|
stash(workspaceRoot: string): Promise<void>;
|
|
198
|
-
startRun(workspaceRoot: string): Promise<IsolationRun>;
|
|
289
|
+
startRun(workspaceRoot: string, stateRoot?: string): Promise<IsolationRun>;
|
|
199
290
|
prepare(task: Task, run: IsolationRun, _intent?: PrepareIntent, opts?: {
|
|
200
291
|
readOnly?: boolean;
|
|
201
292
|
}): Promise<PreparedTask>;
|
|
@@ -204,6 +295,10 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
|
|
|
204
295
|
verifyRepair(task: Task, _run: IsolationRun): Promise<RepairEvidence>;
|
|
205
296
|
/** Like git: the landing is recorded and persisted before the (held) merge, and cleared once it settles. */
|
|
206
297
|
integrate(task: Task, run: IsolationRun, persist?: () => void, commit?: HandoffCommit): Promise<IsolationOutcome>;
|
|
298
|
+
/** Newest landing first, read as the plan's highest order: scheduling tests land in plan order. */
|
|
299
|
+
readLanded(run: IsolationRun, taskIds: readonly string[]): Promise<LandedWork>;
|
|
300
|
+
/** Like git: the turn, then the judgement, then the records go in one step with the tasks back. */
|
|
301
|
+
revert(run: IsolationRun, taskIds: readonly string[], watch: RevertWatch): Promise<IsolationRevertResult>;
|
|
207
302
|
release(run: IsolationRun, taskId: string, opts: {
|
|
208
303
|
keep: boolean;
|
|
209
304
|
}): Promise<IsolationRemoval>;
|
|
@@ -221,4 +316,4 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
|
|
|
221
316
|
sweep(): Promise<void>;
|
|
222
317
|
}
|
|
223
318
|
|
|
224
|
-
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 {
|
|
2
|
-
import {
|
|
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;
|
|
@@ -131,6 +207,9 @@ type FakeIsolationCall = {
|
|
|
131
207
|
} | {
|
|
132
208
|
op: 'discard';
|
|
133
209
|
integration: IntegrationDisposal;
|
|
210
|
+
} | {
|
|
211
|
+
op: 'readLanded' | 'revert';
|
|
212
|
+
taskIds: readonly string[];
|
|
134
213
|
};
|
|
135
214
|
/**
|
|
136
215
|
* An in-memory {@link IWorktreeIsolation} for scheduling tests: no git, no
|
|
@@ -144,6 +223,8 @@ type FakeIsolationCall = {
|
|
|
144
223
|
*/
|
|
145
224
|
declare class FakeWorktreeIsolation implements IWorktreeIsolation {
|
|
146
225
|
availability: IsolationAvailability;
|
|
226
|
+
/** What `gitShortfall` answers. */
|
|
227
|
+
gitShortfallText: string | null;
|
|
147
228
|
/** The repo paths `startRun` groups: a group of one at `.` unless set. */
|
|
148
229
|
repos: string[];
|
|
149
230
|
/** Per task id, the repos it changes; every repo of the group when not listed. */
|
|
@@ -179,23 +260,33 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
|
|
|
179
260
|
removal: IsolationRemoval;
|
|
180
261
|
/** Per task id; a task not listed integrates as `merged`. */
|
|
181
262
|
outcomes: Map<string, IsolationOutcome>;
|
|
263
|
+
/** What `revert` stops with; a revert that goes through when null. */
|
|
264
|
+
revertStop: Exclude<IsolationRevertResult, {
|
|
265
|
+
outcome: 'reverted';
|
|
266
|
+
}> | null;
|
|
267
|
+
/** What `readLanded` finds of a task's work in the user's branch, by task id. */
|
|
268
|
+
inBranch: Map<string, RevertCommand[]>;
|
|
182
269
|
/** Per task id, what `verifyRepair` finds; a task not listed passes. */
|
|
183
270
|
repairEvidence: Map<string, RepairEvidence>;
|
|
184
271
|
calls: FakeIsolationCall[];
|
|
185
272
|
private holds;
|
|
186
273
|
private verifyHolds;
|
|
274
|
+
private revertHold;
|
|
187
275
|
private runCount;
|
|
188
276
|
private log;
|
|
189
277
|
/** Task ids in the order `op` was called for them. */
|
|
190
278
|
taskIdsFor(op: 'prepare' | 'reopen' | 'verifyRepair' | 'integrate' | 'release'): string[];
|
|
191
279
|
/** Make `integrate` for this task wait until the returned function is called. */
|
|
192
280
|
holdIntegration(taskId: string): () => void;
|
|
281
|
+
/** Make the next `revert` wait, past its `refusal`, until the returned function is called. */
|
|
282
|
+
holdRevert(): () => void;
|
|
193
283
|
/** Make `verifyRepair` for this task wait until the returned function is called. */
|
|
194
284
|
holdVerification(taskId: string): () => void;
|
|
285
|
+
gitShortfall(): Promise<string | null>;
|
|
195
286
|
isActive(workspaceRoot: string): Promise<IsolationAvailability>;
|
|
196
287
|
/** Like git: once the tracked changes are stashed, the tree is no longer dirty. */
|
|
197
288
|
stash(workspaceRoot: string): Promise<void>;
|
|
198
|
-
startRun(workspaceRoot: string): Promise<IsolationRun>;
|
|
289
|
+
startRun(workspaceRoot: string, stateRoot?: string): Promise<IsolationRun>;
|
|
199
290
|
prepare(task: Task, run: IsolationRun, _intent?: PrepareIntent, opts?: {
|
|
200
291
|
readOnly?: boolean;
|
|
201
292
|
}): Promise<PreparedTask>;
|
|
@@ -204,6 +295,10 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
|
|
|
204
295
|
verifyRepair(task: Task, _run: IsolationRun): Promise<RepairEvidence>;
|
|
205
296
|
/** Like git: the landing is recorded and persisted before the (held) merge, and cleared once it settles. */
|
|
206
297
|
integrate(task: Task, run: IsolationRun, persist?: () => void, commit?: HandoffCommit): Promise<IsolationOutcome>;
|
|
298
|
+
/** Newest landing first, read as the plan's highest order: scheduling tests land in plan order. */
|
|
299
|
+
readLanded(run: IsolationRun, taskIds: readonly string[]): Promise<LandedWork>;
|
|
300
|
+
/** Like git: the turn, then the judgement, then the records go in one step with the tasks back. */
|
|
301
|
+
revert(run: IsolationRun, taskIds: readonly string[], watch: RevertWatch): Promise<IsolationRevertResult>;
|
|
207
302
|
release(run: IsolationRun, taskId: string, opts: {
|
|
208
303
|
keep: boolean;
|
|
209
304
|
}): Promise<IsolationRemoval>;
|
|
@@ -221,4 +316,4 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
|
|
|
221
316
|
sweep(): Promise<void>;
|
|
222
317
|
}
|
|
223
318
|
|
|
224
|
-
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,
|
|
@@ -45,6 +47,9 @@ function integrationBranchFor(runId) {
|
|
|
45
47
|
function noRemoval() {
|
|
46
48
|
return { preserved: [], refused: [] };
|
|
47
49
|
}
|
|
50
|
+
function landedInRepo(run, record, repoPath) {
|
|
51
|
+
return !!record.repos[repoPath]?.changed || run.landings?.[record.taskId]?.[repoPath] !== void 0;
|
|
52
|
+
}
|
|
48
53
|
function handoffOf(run) {
|
|
49
54
|
const merged = Object.values(run.tasks).filter((r) => r.status === "merged" && !r.inHead).sort((a, b) => a.order - b.order);
|
|
50
55
|
const entry = (r) => ({
|
|
@@ -60,7 +65,7 @@ function handoffOf(run) {
|
|
|
60
65
|
baseRef: repo.baseRef,
|
|
61
66
|
...repo.baseBranch ? { baseBranch: repo.baseBranch } : {},
|
|
62
67
|
...repo.startedDetached ? { startedDetached: true } : {},
|
|
63
|
-
landed: merged.filter((r) => r
|
|
68
|
+
landed: merged.filter((r) => landedInRepo(run, r, repo.path)).map(entry)
|
|
64
69
|
})),
|
|
65
70
|
landed: merged.map(entry)
|
|
66
71
|
};
|
|
@@ -70,6 +75,26 @@ function handoffOf(run) {
|
|
|
70
75
|
var SKILL_NAME = new RegExp(`^${SKILL_NAME_PATTERN}$`);
|
|
71
76
|
var DEFAULT_RUNNERS = ["claude-code"];
|
|
72
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
|
+
|
|
73
98
|
// src/testing.ts
|
|
74
99
|
async function flushMicrotasks(rounds = 20) {
|
|
75
100
|
for (let i = 0; i < rounds; i += 1) await Promise.resolve();
|
|
@@ -107,6 +132,7 @@ function fakeConfig(overrides = {}) {
|
|
|
107
132
|
// Off so a test that is not about the allowlist sees every prompt it raises.
|
|
108
133
|
approvalDefaults: false,
|
|
109
134
|
effectAllowlist: [],
|
|
135
|
+
notifyCommand: [],
|
|
110
136
|
webReadBudget: 40,
|
|
111
137
|
plannerNativeShell: false,
|
|
112
138
|
setProviderModelLists: () => {
|
|
@@ -340,8 +366,121 @@ var FakeRunnerSession = class {
|
|
|
340
366
|
this.emitEvent({ type: "turn_start", text, ...from ? { from } : {} });
|
|
341
367
|
}
|
|
342
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
|
+
};
|
|
343
480
|
var FakeWorktreeIsolation = class {
|
|
344
481
|
availability = { active: true };
|
|
482
|
+
/** What `gitShortfall` answers. */
|
|
483
|
+
gitShortfallText = null;
|
|
345
484
|
/** The repo paths `startRun` groups: a group of one at `.` unless set. */
|
|
346
485
|
repos = [SELF_REPO];
|
|
347
486
|
/** Per task id, the repos it changes; every repo of the group when not listed. */
|
|
@@ -377,11 +516,16 @@ var FakeWorktreeIsolation = class {
|
|
|
377
516
|
removal = noRemoval();
|
|
378
517
|
/** Per task id; a task not listed integrates as `merged`. */
|
|
379
518
|
outcomes = /* @__PURE__ */ new Map();
|
|
519
|
+
/** What `revert` stops with; a revert that goes through when null. */
|
|
520
|
+
revertStop = null;
|
|
521
|
+
/** What `readLanded` finds of a task's work in the user's branch, by task id. */
|
|
522
|
+
inBranch = /* @__PURE__ */ new Map();
|
|
380
523
|
/** Per task id, what `verifyRepair` finds; a task not listed passes. */
|
|
381
524
|
repairEvidence = /* @__PURE__ */ new Map();
|
|
382
525
|
calls = [];
|
|
383
526
|
holds = /* @__PURE__ */ new Map();
|
|
384
527
|
verifyHolds = /* @__PURE__ */ new Map();
|
|
528
|
+
revertHold = null;
|
|
385
529
|
runCount = 0;
|
|
386
530
|
log(call) {
|
|
387
531
|
this.calls.push(call);
|
|
@@ -398,6 +542,14 @@ var FakeWorktreeIsolation = class {
|
|
|
398
542
|
}));
|
|
399
543
|
return open;
|
|
400
544
|
}
|
|
545
|
+
/** Make the next `revert` wait, past its `refusal`, until the returned function is called. */
|
|
546
|
+
holdRevert() {
|
|
547
|
+
let open;
|
|
548
|
+
this.revertHold = new Promise((resolve) => {
|
|
549
|
+
open = resolve;
|
|
550
|
+
});
|
|
551
|
+
return open;
|
|
552
|
+
}
|
|
401
553
|
/** Make `verifyRepair` for this task wait until the returned function is called. */
|
|
402
554
|
holdVerification(taskId) {
|
|
403
555
|
let open;
|
|
@@ -406,6 +558,9 @@ var FakeWorktreeIsolation = class {
|
|
|
406
558
|
}));
|
|
407
559
|
return open;
|
|
408
560
|
}
|
|
561
|
+
async gitShortfall() {
|
|
562
|
+
return this.gitShortfallText;
|
|
563
|
+
}
|
|
409
564
|
async isActive(workspaceRoot) {
|
|
410
565
|
this.log({ op: "isActive", workspaceRoot });
|
|
411
566
|
return this.availability;
|
|
@@ -415,13 +570,14 @@ var FakeWorktreeIsolation = class {
|
|
|
415
570
|
this.log({ op: "stash", workspaceRoot });
|
|
416
571
|
if (!this.availability.active && this.availability.reason === "dirty") this.availability = { active: true };
|
|
417
572
|
}
|
|
418
|
-
async startRun(workspaceRoot) {
|
|
573
|
+
async startRun(workspaceRoot, stateRoot) {
|
|
419
574
|
this.log({ op: "startRun", workspaceRoot });
|
|
420
575
|
if (this.startRunError) throw this.startRunError;
|
|
421
576
|
const id = `run${++this.runCount}`;
|
|
422
577
|
return {
|
|
423
578
|
id,
|
|
424
579
|
workspaceRoot,
|
|
580
|
+
...stateRoot !== void 0 ? { stateRoot } : {},
|
|
425
581
|
repos: this.repos.map((repo) => ({
|
|
426
582
|
path: repo,
|
|
427
583
|
root: `${workspaceRoot}/${repo}`.replace(/\/\.$/, ""),
|
|
@@ -431,7 +587,9 @@ var FakeWorktreeIsolation = class {
|
|
|
431
587
|
})),
|
|
432
588
|
shared: [...this.shared],
|
|
433
589
|
sharedRepos: [...this.sharedRepos],
|
|
434
|
-
tasks: {}
|
|
590
|
+
tasks: {},
|
|
591
|
+
keepsEveryLanding: true,
|
|
592
|
+
carried: {}
|
|
435
593
|
};
|
|
436
594
|
}
|
|
437
595
|
async prepare(task, run, _intent, opts = {}) {
|
|
@@ -494,6 +652,10 @@ var FakeWorktreeIsolation = class {
|
|
|
494
652
|
if (outcome === "merged") {
|
|
495
653
|
delete record.conflictRepo;
|
|
496
654
|
delete record.conflictFiles;
|
|
655
|
+
for (const repo of changed) {
|
|
656
|
+
((run.landings ??= {})[task.id] ??= {})[repo] = [...run.landings?.[task.id]?.[repo] ?? [], `tip-${repo}`];
|
|
657
|
+
(run.landedTasks ??= {})[task.id] = { taskId: task.id, order: task.order, title: task.title };
|
|
658
|
+
}
|
|
497
659
|
} else {
|
|
498
660
|
record.conflictRepo = this.stopsIn.get(task.id) ?? changed[0] ?? SELF_REPO;
|
|
499
661
|
const files = this.conflictFiles.get(task.id);
|
|
@@ -502,6 +664,44 @@ var FakeWorktreeIsolation = class {
|
|
|
502
664
|
}
|
|
503
665
|
return outcome;
|
|
504
666
|
}
|
|
667
|
+
/** Newest landing first, read as the plan's highest order: scheduling tests land in plan order. */
|
|
668
|
+
async readLanded(run, taskIds) {
|
|
669
|
+
this.log({ op: "readLanded", taskIds });
|
|
670
|
+
const records = taskIds.flatMap((id) => run.tasks[id] ?? run.landedTasks?.[id] ?? []).sort((a, b) => b.order - a.order);
|
|
671
|
+
const tasks = records.map((r) => ({
|
|
672
|
+
taskId: r.taskId,
|
|
673
|
+
order: r.order,
|
|
674
|
+
title: r.title,
|
|
675
|
+
repos: Object.keys(run.landings?.[r.taskId] ?? {})
|
|
676
|
+
}));
|
|
677
|
+
return { ok: true, tasks, inBranch: records.flatMap((r) => this.inBranch.get(r.taskId) ?? []) };
|
|
678
|
+
}
|
|
679
|
+
/** Like git: the turn, then the judgement, then the records go in one step with the tasks back. */
|
|
680
|
+
async revert(run, taskIds, watch) {
|
|
681
|
+
this.log({ op: "revert", taskIds });
|
|
682
|
+
const hold = this.revertHold;
|
|
683
|
+
this.revertHold = null;
|
|
684
|
+
await hold;
|
|
685
|
+
const reason = watch.refusal();
|
|
686
|
+
if (reason) return { outcome: "refused", reason };
|
|
687
|
+
if (this.revertStop) return this.revertStop;
|
|
688
|
+
const landed = await this.readLanded(run, taskIds);
|
|
689
|
+
if (!landed.ok) return { outcome: "failed", error: landed.error };
|
|
690
|
+
if (landed.inBranch.length > 0) return { outcome: "in-branch", commands: landed.inBranch };
|
|
691
|
+
run.landing = { taskId: taskIds[0], tips: {}, reverts: {} };
|
|
692
|
+
watch.persist();
|
|
693
|
+
delete run.landing;
|
|
694
|
+
for (const id of taskIds) {
|
|
695
|
+
if (run.tasks[id]?.status === "merged") delete run.tasks[id];
|
|
696
|
+
else if (run.tasks[id]?.status === "conflict") run.tasks[id].status = "kept";
|
|
697
|
+
delete run.landings?.[id];
|
|
698
|
+
delete run.landedTasks?.[id];
|
|
699
|
+
delete run.landedOn?.[id];
|
|
700
|
+
}
|
|
701
|
+
run.reverted = true;
|
|
702
|
+
watch.settled(taskIds);
|
|
703
|
+
return { outcome: "reverted", tasks: landed.tasks };
|
|
704
|
+
}
|
|
505
705
|
async release(run, taskId, opts) {
|
|
506
706
|
this.log({ op: "release", taskId, keep: opts.keep });
|
|
507
707
|
const record = run.tasks[taskId];
|
|
@@ -543,13 +743,16 @@ var FakeWorktreeIsolation = class {
|
|
|
543
743
|
}
|
|
544
744
|
async findInHead(run) {
|
|
545
745
|
this.log({ op: "findInHead" });
|
|
546
|
-
const
|
|
746
|
+
const changed = [];
|
|
547
747
|
for (const record of Object.values(run.tasks)) {
|
|
548
|
-
if (record.status !== "merged"
|
|
549
|
-
|
|
550
|
-
|
|
748
|
+
if (record.status !== "merged") continue;
|
|
749
|
+
const merged = this.mergedByHand.has(record.taskId);
|
|
750
|
+
if (merged === !!record.inHead) continue;
|
|
751
|
+
if (merged) record.inHead = true;
|
|
752
|
+
else delete record.inHead;
|
|
753
|
+
changed.push(record.taskId);
|
|
551
754
|
}
|
|
552
|
-
return
|
|
755
|
+
return changed;
|
|
553
756
|
}
|
|
554
757
|
async snapshotTree() {
|
|
555
758
|
this.log({ op: "snapshotTree" });
|
|
@@ -571,6 +774,8 @@ var FakeWorktreeIsolation = class {
|
|
|
571
774
|
};
|
|
572
775
|
// Annotate the CommonJS export names for ESM import in node:
|
|
573
776
|
0 && (module.exports = {
|
|
777
|
+
FakeEventStore,
|
|
778
|
+
FakeExecutionBackend,
|
|
574
779
|
FakeRunnerSession,
|
|
575
780
|
FakeWorktreeIsolation,
|
|
576
781
|
fakeConfig,
|