@ordewell/core 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/{IFileSystem-DpaaFdit.d.ts → IFileSystem-BCJBHFwB.d.ts} +3 -1
  2. package/dist/{IFileSystem-_m7HdoxJ.d.mts → IFileSystem-Bj6fy1cG.d.mts} +3 -1
  3. package/dist/{IRunner-BiQio8gJ.d.ts → IRunner-C7_SSpzG.d.ts} +614 -77
  4. package/dist/{IRunner-CxiymkP_.d.mts → IRunner-hWAJC4hg.d.mts} +614 -77
  5. package/dist/ModeResolver-m1d8Cb-j.d.mts +35 -0
  6. package/dist/ModeResolver-u5ZhRC8x.d.ts +35 -0
  7. package/dist/{Task-1Q19y7sZ.d.mts → Task-CxwnCJLi.d.mts} +346 -127
  8. package/dist/{Task-1Q19y7sZ.d.ts → Task-CxwnCJLi.d.ts} +346 -127
  9. package/dist/{chunk-UUU46PVQ.mjs → chunk-2IIRSBEQ.mjs} +109 -142
  10. package/dist/chunk-2IIRSBEQ.mjs.map +1 -0
  11. package/dist/{chunk-QUARDKVT.mjs → chunk-CH5IGWFS.mjs} +546 -967
  12. package/dist/chunk-CH5IGWFS.mjs.map +1 -0
  13. package/dist/chunk-DJAPDO6E.mjs +1358 -0
  14. package/dist/chunk-DJAPDO6E.mjs.map +1 -0
  15. package/dist/chunk-DNFT4SQG.mjs +97 -0
  16. package/dist/chunk-DNFT4SQG.mjs.map +1 -0
  17. package/dist/index.d.mts +3204 -3926
  18. package/dist/index.d.ts +3204 -3926
  19. package/dist/index.js +22283 -16472
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +20220 -14690
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/parsing.d.mts +55 -3
  24. package/dist/parsing.d.ts +55 -3
  25. package/dist/parsing.js +311 -124
  26. package/dist/parsing.js.map +1 -1
  27. package/dist/parsing.mjs +86 -6
  28. package/dist/parsing.mjs.map +1 -1
  29. package/dist/plan-utils-B6yJdpgi.d.ts +2902 -0
  30. package/dist/plan-utils-CKXdD1AD.d.mts +2902 -0
  31. package/dist/plan-utils.d.mts +4 -5
  32. package/dist/plan-utils.d.ts +4 -5
  33. package/dist/plan-utils.js +471 -86
  34. package/dist/plan-utils.js.map +1 -1
  35. package/dist/plan-utils.mjs +68 -19
  36. package/dist/testing.d.mts +22 -4
  37. package/dist/testing.d.ts +22 -4
  38. package/dist/testing.js +46 -9
  39. package/dist/testing.js.map +1 -1
  40. package/dist/testing.mjs +47 -11
  41. package/dist/testing.mjs.map +1 -1
  42. package/package.json +2 -10
  43. package/skills/tdd/SKILL.md +0 -1
  44. package/dist/ModeResolver-DE-o2lRF.d.mts +0 -48
  45. package/dist/ModeResolver-EWlpmVAy.d.ts +0 -48
  46. package/dist/chunk-2M4X3VB7.mjs +0 -432
  47. package/dist/chunk-2M4X3VB7.mjs.map +0 -1
  48. package/dist/chunk-H5O74L77.mjs +0 -185
  49. package/dist/chunk-H5O74L77.mjs.map +0 -1
  50. package/dist/chunk-QUARDKVT.mjs.map +0 -1
  51. package/dist/chunk-UUU46PVQ.mjs.map +0 -1
  52. package/dist/chunk-V3JHS7AX.mjs +0 -27
  53. package/dist/chunk-V3JHS7AX.mjs.map +0 -1
  54. package/dist/order-labels.d.mts +0 -30
  55. package/dist/order-labels.d.ts +0 -30
  56. package/dist/order-labels.js +0 -54
  57. package/dist/order-labels.js.map +0 -1
  58. package/dist/order-labels.mjs +0 -13
  59. package/dist/order-labels.mjs.map +0 -1
  60. package/dist/parsing-CZdp2p_n.d.ts +0 -154
  61. package/dist/parsing-GgUJoYKs.d.mts +0 -154
  62. package/dist/plan-utils-6rUr7ZOx.d.ts +0 -1222
  63. package/dist/plan-utils-P3PIEwNa.d.mts +0 -1222
@@ -2,28 +2,29 @@ import {
2
2
  CHECKPOINT_TRUNCATE_LENGTH,
3
3
  EMPTY_CONVERSATION,
4
4
  EMPTY_HOLD,
5
+ EMPTY_RUN,
5
6
  EMPTY_TASK_LOG,
6
- NOT_STARTED,
7
7
  NO_TURN,
8
+ SUPERVISOR_LEVEL_LABEL,
9
+ addAttemptUsage,
10
+ addModelUsage,
11
+ addPlannerUsage,
12
+ addUsage,
13
+ addUsageTotals,
8
14
  aheadOfDraft,
9
- approvalLabel,
10
- awaitingLabel,
11
- canMergeTasks,
12
- canSetDependencies,
13
- canSplitTask,
15
+ blockingFailures,
14
16
  capConflictFiles,
15
- dependencyCandidates,
16
17
  dependentsNotice,
17
- dependentsOf,
18
18
  diffRows,
19
19
  diffStat,
20
20
  diffSummary,
21
21
  drainNext,
22
+ endAttemptUsage,
22
23
  followTurn,
23
24
  fromTranscript,
24
25
  hasHiddenDetail,
25
26
  holdPrompt,
26
- isTaskRunning,
27
+ isMeasured,
27
28
  markAction,
28
29
  markRequestFor,
29
30
  namedRepos,
@@ -31,45 +32,77 @@ import {
31
32
  opsEditable,
32
33
  outputLines,
33
34
  outputPreview,
35
+ parseCount,
36
+ partedPromptUsage,
34
37
  pastGateConfirmation,
38
+ planUsage,
39
+ plannerContextFill,
35
40
  reduceConversation,
36
41
  reduceTaskLog,
37
42
  replayTaskLog,
38
43
  runnerToolSubject,
44
+ startAttemptUsage,
39
45
  stopTurn,
40
46
  summarizeToolCall,
47
+ takeAt,
41
48
  taskRowActions,
42
49
  taskRowView,
43
50
  taskStartedNotice,
44
- taskStatusKind,
45
51
  titledRefs,
46
52
  toolHeadline,
47
53
  truncateCheckpointSummary,
48
54
  unsendAll,
49
- unsendLatest
50
- } from "./chunk-QUARDKVT.mjs";
51
- import "./chunk-H5O74L77.mjs";
55
+ unsendLatest,
56
+ usageLine
57
+ } from "./chunk-CH5IGWFS.mjs";
58
+ import {
59
+ NOT_STARTED,
60
+ approvalLabel,
61
+ awaitingLabel,
62
+ blockedByLabel,
63
+ canMergeTasks,
64
+ canSetDependencies,
65
+ canSplitTask,
66
+ dependencyCandidates,
67
+ dependentsOf,
68
+ isTaskRunning,
69
+ resumeLabel,
70
+ resumeTime,
71
+ skippedLabel,
72
+ taskStatusKind
73
+ } from "./chunk-DJAPDO6E.mjs";
52
74
  import {
53
75
  DEFAULT_RUNNERS,
54
76
  loadedSkillTokens,
55
- skillTokens
56
- } from "./chunk-UUU46PVQ.mjs";
57
- import {
77
+ pausedOnLimit,
78
+ resolveTaskRef,
79
+ skillTokens,
58
80
  taskOrderLabel,
59
81
  taskRef,
60
- titledTaskRef
61
- } from "./chunk-V3JHS7AX.mjs";
82
+ taskRefOf,
83
+ titledTaskRef,
84
+ titledTaskRefOf
85
+ } from "./chunk-2IIRSBEQ.mjs";
62
86
  export {
63
87
  CHECKPOINT_TRUNCATE_LENGTH,
64
88
  DEFAULT_RUNNERS,
65
89
  EMPTY_CONVERSATION,
66
90
  EMPTY_HOLD,
91
+ EMPTY_RUN,
67
92
  EMPTY_TASK_LOG,
68
93
  NOT_STARTED,
69
94
  NO_TURN,
95
+ SUPERVISOR_LEVEL_LABEL,
96
+ addAttemptUsage,
97
+ addModelUsage,
98
+ addPlannerUsage,
99
+ addUsage,
100
+ addUsageTotals,
70
101
  aheadOfDraft,
71
102
  approvalLabel,
72
103
  awaitingLabel,
104
+ blockedByLabel,
105
+ blockingFailures,
73
106
  canMergeTasks,
74
107
  canSetDependencies,
75
108
  canSplitTask,
@@ -81,10 +114,12 @@ export {
81
114
  diffStat,
82
115
  diffSummary,
83
116
  drainNext,
117
+ endAttemptUsage,
84
118
  followTurn,
85
119
  fromTranscript,
86
120
  hasHiddenDetail,
87
121
  holdPrompt,
122
+ isMeasured,
88
123
  isTaskRunning,
89
124
  loadedSkillTokens,
90
125
  markAction,
@@ -94,25 +129,39 @@ export {
94
129
  opsEditable,
95
130
  outputLines,
96
131
  outputPreview,
132
+ parseCount,
133
+ partedPromptUsage,
97
134
  pastGateConfirmation,
135
+ pausedOnLimit,
136
+ planUsage,
137
+ plannerContextFill,
98
138
  reduceConversation,
99
139
  reduceTaskLog,
100
140
  replayTaskLog,
141
+ resolveTaskRef,
142
+ resumeLabel,
143
+ resumeTime,
101
144
  runnerToolSubject,
102
145
  skillTokens,
146
+ skippedLabel,
147
+ startAttemptUsage,
103
148
  stopTurn,
104
149
  summarizeToolCall,
150
+ takeAt,
105
151
  taskOrderLabel,
106
152
  taskRef,
153
+ taskRefOf,
107
154
  taskRowActions,
108
155
  taskRowView,
109
156
  taskStartedNotice,
110
157
  taskStatusKind,
111
158
  titledRefs,
112
159
  titledTaskRef,
160
+ titledTaskRefOf,
113
161
  toolHeadline,
114
162
  truncateCheckpointSummary,
115
163
  unsendAll,
116
- unsendLatest
164
+ unsendLatest,
165
+ usageLine
117
166
  };
118
167
  //# sourceMappingURL=plan-utils.mjs.map
@@ -1,6 +1,6 @@
1
- import { w as IRunnerSession, a0 as QueuedTaskMessage, aa as StructuredTurnEnd, a9 as StructuredEvent, ae as TaskCompleteArgs, p as CheckpointAnswer, h as ApprovalDecision, u as IConfig } from './IRunner-CxiymkP_.mjs';
2
- import { I as IFileSystem } from './IFileSystem-_m7HdoxJ.mjs';
3
- import { d as IntegrationDisposal, I as IWorktreeIsolation, f as IsolationAvailability, n as IsolationMergeResult, am as TreeSnapshot, p as IsolationPruneResult, q as IsolationRemoval, o as IsolationOutcome, K as RepairEvidence, s as IsolationRun, a9 as Task, H as PreparedTask, g as IsolationHandoff } from './Task-1Q19y7sZ.mjs';
1
+ import { B as IRunnerSession, aa as QueuedTaskMessage, au as StructuredTurnEnd, at as StructuredEvent, az as TaskCompleteArgs, r as CheckpointAnswer, aB as TaskMessageFrom, ah as RunnerCommand, H as InvokedCommand, j as ApprovalDecision, y as IConfig } from './IRunner-hWAJC4hg.mjs';
2
+ import { I as IFileSystem } from './IFileSystem-Bj6fy1cG.mjs';
3
+ import { g as IntegrationDisposal, I as IWorktreeIsolation, i as IsolationAvailability, q as IsolationMergeResult, am as TreeSnapshot, a0 as SetupFailure, s as IsolationPruneResult, t as IsolationRemoval, r as IsolationOutcome, T as RepairEvidence, v as IsolationRun, ac as Task, Q as PreparedTask, j as IsolationHandoff } from './Task-CxwnCJLi.mjs';
4
4
  import 'child_process';
5
5
  import 'zod/v4';
6
6
 
@@ -29,6 +29,8 @@ declare class FakeRunnerSession implements IRunnerSession {
29
29
  messages: QueuedTaskMessage[];
30
30
  /** Every message a turn was started with, in order. */
31
31
  delivered: string[];
32
+ /** The ones of {@link delivered} Ordewell sent: the completion reminder (ADR-0018, W1). */
33
+ fromOrdewell: string[];
32
34
  interrupts: number;
33
35
  private messageCount;
34
36
  private turnEndCbs;
@@ -52,7 +54,7 @@ declare class FakeRunnerSession implements IRunnerSession {
52
54
  onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void;
53
55
  /** The runner calls `checkpoint`, as `StructuredSession` relays it; settles with the answer. */
54
56
  callCheckpoint(question: string, signal?: AbortSignal): Promise<CheckpointAnswer>;
55
- sendMessage(text: string): string;
57
+ sendMessage(text: string, from?: TaskMessageFrom): string;
56
58
  /** Interrupts, and the turn that replaces the interrupted one opens with the forced message. */
57
59
  forceSend(text: string): string;
58
60
  forceSendQueued(id: string): boolean;
@@ -61,6 +63,13 @@ declare class FakeRunnerSession implements IRunnerSession {
61
63
  queued(): QueuedTaskMessage[];
62
64
  interrupt(): Promise<void>;
63
65
  nativeSessionId(): string | null;
66
+ /** What `commands()` reports: the runner's own commands (#66). */
67
+ runnerCommands: RunnerCommand[];
68
+ /** Every runner command invoked, in order. */
69
+ invoked: InvokedCommand[];
70
+ commands(): RunnerCommand[];
71
+ /** Runs it as a message that starts with it, which is what a runner reads. */
72
+ invokeCommand(name: string, args?: string): string;
64
73
  /** Every answer this session was given, in order. */
65
74
  answers: Array<{
66
75
  id: string;
@@ -75,6 +84,11 @@ declare class FakeRunnerSession implements IRunnerSession {
75
84
  emitEvent(event: StructuredEvent): void;
76
85
  /** The runner reads a queued message inside the running turn, as `StructuredSession` reports it (ADR-0023). */
77
86
  deliverMidTurn(id: string): void;
87
+ /**
88
+ * The turn ends without a `task_complete` call, and so does the turn the
89
+ * completion reminder opens if one was sent (ADR-0018, W1): the task waits for input.
90
+ */
91
+ waitForInput(): void;
78
92
  emitTurnEnd(reason: StructuredTurnEnd): void;
79
93
  private deliver;
80
94
  }
@@ -135,6 +149,7 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
135
149
  shared: string[];
136
150
  sharedRepos: string[];
137
151
  copied: string[];
152
+ setupFailed: SetupFailure[];
138
153
  /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */
139
154
  startRunError: Error | null;
140
155
  /** Set to make `discard` / `sweep` throw after logging the call. */
@@ -153,12 +168,15 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
153
168
  repairEvidence: Map<string, RepairEvidence>;
154
169
  calls: FakeIsolationCall[];
155
170
  private holds;
171
+ private verifyHolds;
156
172
  private runCount;
157
173
  private log;
158
174
  /** Task ids in the order `op` was called for them. */
159
175
  taskIdsFor(op: 'prepare' | 'reopen' | 'verifyRepair' | 'integrate' | 'release'): string[];
160
176
  /** Make `integrate` for this task wait until the returned function is called. */
161
177
  holdIntegration(taskId: string): () => void;
178
+ /** Make `verifyRepair` for this task wait until the returned function is called. */
179
+ holdVerification(taskId: string): () => void;
162
180
  isActive(workspaceRoot: string): Promise<IsolationAvailability>;
163
181
  /** Like git: once the tracked changes are stashed, the tree is no longer dirty. */
164
182
  stash(workspaceRoot: string): Promise<void>;
package/dist/testing.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { w as IRunnerSession, a0 as QueuedTaskMessage, aa as StructuredTurnEnd, a9 as StructuredEvent, ae as TaskCompleteArgs, p as CheckpointAnswer, h as ApprovalDecision, u as IConfig } from './IRunner-BiQio8gJ.js';
2
- import { I as IFileSystem } from './IFileSystem-DpaaFdit.js';
3
- import { d as IntegrationDisposal, I as IWorktreeIsolation, f as IsolationAvailability, n as IsolationMergeResult, am as TreeSnapshot, p as IsolationPruneResult, q as IsolationRemoval, o as IsolationOutcome, K as RepairEvidence, s as IsolationRun, a9 as Task, H as PreparedTask, g as IsolationHandoff } from './Task-1Q19y7sZ.js';
1
+ import { B as IRunnerSession, aa as QueuedTaskMessage, au as StructuredTurnEnd, at as StructuredEvent, az as TaskCompleteArgs, r as CheckpointAnswer, aB as TaskMessageFrom, ah as RunnerCommand, H as InvokedCommand, j as ApprovalDecision, y as IConfig } from './IRunner-C7_SSpzG.js';
2
+ import { I as IFileSystem } from './IFileSystem-BCJBHFwB.js';
3
+ import { g as IntegrationDisposal, I as IWorktreeIsolation, i as IsolationAvailability, q as IsolationMergeResult, am as TreeSnapshot, a0 as SetupFailure, s as IsolationPruneResult, t as IsolationRemoval, r as IsolationOutcome, T as RepairEvidence, v as IsolationRun, ac as Task, Q as PreparedTask, j as IsolationHandoff } from './Task-CxwnCJLi.js';
4
4
  import 'child_process';
5
5
  import 'zod/v4';
6
6
 
@@ -29,6 +29,8 @@ declare class FakeRunnerSession implements IRunnerSession {
29
29
  messages: QueuedTaskMessage[];
30
30
  /** Every message a turn was started with, in order. */
31
31
  delivered: string[];
32
+ /** The ones of {@link delivered} Ordewell sent: the completion reminder (ADR-0018, W1). */
33
+ fromOrdewell: string[];
32
34
  interrupts: number;
33
35
  private messageCount;
34
36
  private turnEndCbs;
@@ -52,7 +54,7 @@ declare class FakeRunnerSession implements IRunnerSession {
52
54
  onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void;
53
55
  /** The runner calls `checkpoint`, as `StructuredSession` relays it; settles with the answer. */
54
56
  callCheckpoint(question: string, signal?: AbortSignal): Promise<CheckpointAnswer>;
55
- sendMessage(text: string): string;
57
+ sendMessage(text: string, from?: TaskMessageFrom): string;
56
58
  /** Interrupts, and the turn that replaces the interrupted one opens with the forced message. */
57
59
  forceSend(text: string): string;
58
60
  forceSendQueued(id: string): boolean;
@@ -61,6 +63,13 @@ declare class FakeRunnerSession implements IRunnerSession {
61
63
  queued(): QueuedTaskMessage[];
62
64
  interrupt(): Promise<void>;
63
65
  nativeSessionId(): string | null;
66
+ /** What `commands()` reports: the runner's own commands (#66). */
67
+ runnerCommands: RunnerCommand[];
68
+ /** Every runner command invoked, in order. */
69
+ invoked: InvokedCommand[];
70
+ commands(): RunnerCommand[];
71
+ /** Runs it as a message that starts with it, which is what a runner reads. */
72
+ invokeCommand(name: string, args?: string): string;
64
73
  /** Every answer this session was given, in order. */
65
74
  answers: Array<{
66
75
  id: string;
@@ -75,6 +84,11 @@ declare class FakeRunnerSession implements IRunnerSession {
75
84
  emitEvent(event: StructuredEvent): void;
76
85
  /** The runner reads a queued message inside the running turn, as `StructuredSession` reports it (ADR-0023). */
77
86
  deliverMidTurn(id: string): void;
87
+ /**
88
+ * The turn ends without a `task_complete` call, and so does the turn the
89
+ * completion reminder opens if one was sent (ADR-0018, W1): the task waits for input.
90
+ */
91
+ waitForInput(): void;
78
92
  emitTurnEnd(reason: StructuredTurnEnd): void;
79
93
  private deliver;
80
94
  }
@@ -135,6 +149,7 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
135
149
  shared: string[];
136
150
  sharedRepos: string[];
137
151
  copied: string[];
152
+ setupFailed: SetupFailure[];
138
153
  /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */
139
154
  startRunError: Error | null;
140
155
  /** Set to make `discard` / `sweep` throw after logging the call. */
@@ -153,12 +168,15 @@ declare class FakeWorktreeIsolation implements IWorktreeIsolation {
153
168
  repairEvidence: Map<string, RepairEvidence>;
154
169
  calls: FakeIsolationCall[];
155
170
  private holds;
171
+ private verifyHolds;
156
172
  private runCount;
157
173
  private log;
158
174
  /** Task ids in the order `op` was called for them. */
159
175
  taskIdsFor(op: 'prepare' | 'reopen' | 'verifyRepair' | 'integrate' | 'release'): string[];
160
176
  /** Make `integrate` for this task wait until the returned function is called. */
161
177
  holdIntegration(taskId: string): () => void;
178
+ /** Make `verifyRepair` for this task wait until the returned function is called. */
179
+ holdVerification(taskId: string): () => void;
162
180
  isActive(workspaceRoot: string): Promise<IsolationAvailability>;
163
181
  /** Like git: once the tracked changes are stashed, the tree is no longer dirty. */
164
182
  stash(workspaceRoot: string): Promise<void>;
package/dist/testing.js CHANGED
@@ -102,6 +102,7 @@ function fakeConfig(overrides = {}) {
102
102
  approvalPreApproved: [],
103
103
  // Off so a test that is not about the allowlist sees every prompt it raises.
104
104
  approvalDefaults: false,
105
+ webReadBudget: 40,
105
106
  setProviderModelLists: () => {
106
107
  },
107
108
  getProviderBaseUrl: () => "",
@@ -141,6 +142,8 @@ var FakeRunnerSession = class {
141
142
  messages = [];
142
143
  /** Every message a turn was started with, in order. */
143
144
  delivered = [];
145
+ /** The ones of {@link delivered} Ordewell sent: the completion reminder (ADR-0018, W1). */
146
+ fromOrdewell = [];
144
147
  interrupts = 0;
145
148
  messageCount = 0;
146
149
  turnEndCbs = [];
@@ -195,11 +198,11 @@ var FakeRunnerSession = class {
195
198
  if (!this.checkpointHandler) throw new Error("no checkpoint handler attached");
196
199
  return this.checkpointHandler(question, signal);
197
200
  }
198
- sendMessage(text) {
201
+ sendMessage(text, from) {
199
202
  this.messageCount += 1;
200
203
  const id = `msg-${this.messageCount}`;
201
- if (this.state === "idle") this.deliver(text);
202
- else this.messages.push({ id, text });
204
+ if (this.state === "idle") this.deliver(text, from);
205
+ else this.messages.push({ id, text, ...from ? { from } : {} });
203
206
  return id;
204
207
  }
205
208
  /** Interrupts, and the turn that replaces the interrupted one opens with the forced message. */
@@ -213,7 +216,8 @@ var FakeRunnerSession = class {
213
216
  }
214
217
  forceSendQueued(id) {
215
218
  const message = this.messages.find((m) => m.id === id);
216
- if (!message || message.handedOver) return false;
219
+ if (!message) return false;
220
+ if (message.forced) return true;
217
221
  this.messages = this.messages.filter((m) => m !== message);
218
222
  this.putForced({ ...message, forced: true });
219
223
  if (this.state === "working") void this.interrupt();
@@ -238,6 +242,18 @@ var FakeRunnerSession = class {
238
242
  nativeSessionId() {
239
243
  return this.sessionId;
240
244
  }
245
+ /** What `commands()` reports: the runner's own commands (#66). */
246
+ runnerCommands = [];
247
+ /** Every runner command invoked, in order. */
248
+ invoked = [];
249
+ commands() {
250
+ return [...this.runnerCommands];
251
+ }
252
+ /** Runs it as a message that starts with it, which is what a runner reads. */
253
+ invokeCommand(name, args = "") {
254
+ this.invoked.push({ name, ...args.trim() ? { args: args.trim() } : {} });
255
+ return this.sendMessage(args.trim() ? `/${name} ${args.trim()}` : `/${name}`);
256
+ }
241
257
  /** Every answer this session was given, in order. */
242
258
  answers = [];
243
259
  openPermissions = /* @__PURE__ */ new Set();
@@ -264,18 +280,28 @@ var FakeRunnerSession = class {
264
280
  const message = this.messages.find((m) => m.id === id);
265
281
  if (!message) return;
266
282
  this.messages = this.messages.filter((m) => m !== message);
267
- this.emitEvent({ type: "message_delivered", messageId: id, text: message.text });
283
+ this.emitEvent({ type: "message_delivered", messageId: id, text: message.text, ...message.from ? { from: message.from } : {} });
284
+ }
285
+ /**
286
+ * The turn ends without a `task_complete` call, and so does the turn the
287
+ * completion reminder opens if one was sent (ADR-0018, W1): the task waits for input.
288
+ */
289
+ waitForInput() {
290
+ const reminders = this.fromOrdewell.length;
291
+ this.emitTurnEnd("completed");
292
+ if (this.fromOrdewell.length > reminders) this.emitTurnEnd("completed");
268
293
  }
269
294
  emitTurnEnd(reason) {
270
295
  const next = this.messages.shift();
271
296
  if (!next) this.state = "idle";
272
297
  for (const cb of this.turnEndCbs) cb(reason);
273
- if (next) this.deliver(next.text);
298
+ if (next) this.deliver(next.text, next.from);
274
299
  }
275
- deliver(text) {
300
+ deliver(text, from) {
276
301
  this.state = "working";
277
302
  this.delivered.push(text);
278
- this.emitEvent({ type: "turn_start", text });
303
+ if (from === "ordewell") this.fromOrdewell.push(text);
304
+ this.emitEvent({ type: "turn_start", text, ...from ? { from } : {} });
279
305
  }
280
306
  };
281
307
  var FakeWorktreeIsolation = class {
@@ -300,6 +326,7 @@ var FakeWorktreeIsolation = class {
300
326
  shared = [];
301
327
  sharedRepos = [];
302
328
  copied = [];
329
+ setupFailed = [];
303
330
  /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */
304
331
  startRunError = null;
305
332
  /** Set to make `discard` / `sweep` throw after logging the call. */
@@ -318,6 +345,7 @@ var FakeWorktreeIsolation = class {
318
345
  repairEvidence = /* @__PURE__ */ new Map();
319
346
  calls = [];
320
347
  holds = /* @__PURE__ */ new Map();
348
+ verifyHolds = /* @__PURE__ */ new Map();
321
349
  runCount = 0;
322
350
  log(call) {
323
351
  this.calls.push(call);
@@ -334,6 +362,14 @@ var FakeWorktreeIsolation = class {
334
362
  }));
335
363
  return open;
336
364
  }
365
+ /** Make `verifyRepair` for this task wait until the returned function is called. */
366
+ holdVerification(taskId) {
367
+ let open;
368
+ this.verifyHolds.set(taskId, new Promise((resolve) => {
369
+ open = resolve;
370
+ }));
371
+ return open;
372
+ }
337
373
  async isActive(workspaceRoot) {
338
374
  this.log({ op: "isActive", workspaceRoot });
339
375
  return this.availability;
@@ -376,7 +412,7 @@ var FakeWorktreeIsolation = class {
376
412
  status: "active",
377
413
  repos: Object.fromEntries(run.repos.map((r) => [r.path, { worktree: r.path === SELF_REPO ? cwd : `${cwd}/${r.path}`, linked: [] }]))
378
414
  };
379
- return { cwd, branch, copied: [...this.copied] };
415
+ return { cwd, branch, copied: [...this.copied], ...this.setupFailed.length > 0 ? { setupFailed: [...this.setupFailed] } : {} };
380
416
  }
381
417
  /** Like git: the same cwd, each changed repo's tip recorded, the repair counted. */
382
418
  async reopen(task, run) {
@@ -393,6 +429,7 @@ var FakeWorktreeIsolation = class {
393
429
  }
394
430
  async verifyRepair(task, _run) {
395
431
  this.log({ op: "verifyRepair", taskId: task.id });
432
+ await this.verifyHolds.get(task.id);
396
433
  return this.repairEvidence.get(task.id) ?? { ok: true };
397
434
  }
398
435
  /** Like git: the landing is recorded and persisted before the (held) merge, and cleared once it settles. */