@sublang/playbook 15.1.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/embedding.md +2 -2
- package/package.json +1 -1
- package/reference/sdlc/branch.playbook/branch.fsm.d.ts +63 -33
- package/reference/sdlc/branch.playbook/branch.fsm.js +190 -108
- package/reference/sdlc/branch.playbook/branch.fsm.ts +269 -147
- package/reference/sdlc/branch.playbook/branch.gears.md +7 -17
- package/reference/sdlc/branch.playbook/branch.playbook.d.ts +12 -16
- package/reference/sdlc/branch.playbook/branch.playbook.js +112 -101
- package/reference/sdlc/branch.playbook/branch.playbook.ts +128 -171
- package/reference/sdlc/branch.playbook/branch.registry.d.ts +3 -2
- package/reference/sdlc/branch.playbook/branch.registry.ts +6 -2
- package/reference/sdlc/code.playbook/code.fsm.d.ts +143 -90
- package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +8 -8
- package/reference/sdlc/code.playbook/code.fsm.introspect.ts +13 -13
- package/reference/sdlc/code.playbook/code.fsm.js +609 -649
- package/reference/sdlc/code.playbook/code.fsm.ts +890 -821
- package/reference/sdlc/code.playbook/code.gears.md +39 -29
- package/reference/sdlc/code.playbook/code.playbook.d.ts +13 -16
- package/reference/sdlc/code.playbook/code.playbook.js +166 -147
- package/reference/sdlc/code.playbook/code.playbook.ts +180 -229
- package/reference/sdlc/code.playbook/code.registry.d.ts +3 -2
- package/reference/sdlc/code.playbook/code.registry.ts +6 -2
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +42 -0
- package/reference/sdlc/code.playbook/playbook-captain.js +829 -194
- package/reference/sdlc/code.playbook/playbook-captain.ts +1227 -220
- package/reference/sdlc/decide.md +1 -0
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +270 -154
- package/reference/sdlc/decide.playbook/decide.fsm.js +765 -732
- package/reference/sdlc/decide.playbook/decide.fsm.ts +1167 -945
- package/reference/sdlc/decide.playbook/decide.gears.md +28 -21
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +11 -59
- package/reference/sdlc/decide.playbook/decide.playbook.js +155 -3696
- package/reference/sdlc/decide.playbook/decide.playbook.ts +186 -5244
- package/reference/sdlc/decide.playbook/decide.registry.d.ts +8 -4
- package/reference/sdlc/decide.playbook/decide.registry.js +2 -2
- package/reference/sdlc/decide.playbook/decide.registry.ts +13 -6
- package/reference/sdlc/dev.playbook/dev.fsm.d.ts +113 -131
- package/reference/sdlc/dev.playbook/dev.fsm.js +517 -575
- package/reference/sdlc/dev.playbook/dev.fsm.ts +667 -787
- package/reference/sdlc/dev.playbook/dev.gears.md +52 -54
- package/reference/sdlc/dev.playbook/dev.playbook.d.ts +13 -10
- package/reference/sdlc/dev.playbook/dev.playbook.js +128 -120
- package/reference/sdlc/dev.playbook/dev.playbook.ts +143 -193
- package/reference/sdlc/dev.playbook/dev.registry.d.ts +3 -2
- package/reference/sdlc/dev.playbook/dev.registry.ts +6 -2
- package/reference/sdlc/pr.playbook/pr.fsm.d.ts +92 -85
- package/reference/sdlc/pr.playbook/pr.fsm.js +461 -518
- package/reference/sdlc/pr.playbook/pr.fsm.ts +533 -663
- package/reference/sdlc/pr.playbook/pr.gears.md +47 -63
- package/reference/sdlc/pr.playbook/pr.playbook.d.ts +13 -23
- package/reference/sdlc/pr.playbook/pr.playbook.js +118 -112
- package/reference/sdlc/pr.playbook/pr.playbook.ts +136 -190
- package/reference/sdlc/pr.playbook/pr.registry.d.ts +3 -2
- package/reference/sdlc/pr.playbook/pr.registry.ts +4 -2
- package/reference/sdlc/review.md +3 -3
- package/reference/sdlc/review.playbook/review.fsm.d.ts +150 -116
- package/reference/sdlc/review.playbook/review.fsm.js +479 -550
- package/reference/sdlc/review.playbook/review.fsm.ts +717 -662
- package/reference/sdlc/review.playbook/review.gears.md +32 -29
- package/reference/sdlc/review.playbook/review.playbook.d.ts +12 -12
- package/reference/sdlc/review.playbook/review.playbook.js +180 -155
- package/reference/sdlc/review.playbook/review.playbook.ts +190 -230
- package/reference/sdlc/review.playbook/review.registry.d.ts +6 -5
- package/reference/sdlc/review.playbook/review.registry.js +3 -3
- package/reference/sdlc/review.playbook/review.registry.ts +9 -5
- package/slc/gears2fsm.md +72 -3
- package/slc/link.md +50 -27
- package/slc/materialize-link.mjs +51 -13
- package/slc/optimize.md +4 -1
- package/slc/prefix-prompts.mjs +29 -28
- package/slc/prefix.md +27 -24
- package/slc/slc.pin-inputs.json +11 -2
- package/slc/text2gears.md +71 -11
- package/src/xstate-playbook-runtime.d.ts +54 -15
- package/src/xstate-playbook-runtime.js +1154 -248
- package/src/xstate-playbook-runtime.ts +1603 -317
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// FSM object artifact compiled from ./code.gears.md by gears2fsm.
|
|
5
|
+
// It declares only the machine, its actor contracts, and typed inputs; the
|
|
6
|
+
// linked runtime supplies the `player` and `playbook` actor implementations.
|
|
5
7
|
|
|
6
8
|
import { assign, fromPromise, setup } from 'xstate';
|
|
9
|
+
import { validatePlaybookCallResult } from '@sublang/playbook/xstate-runtime';
|
|
10
|
+
import type { PlaybookCallResult } from '@sublang/playbook/runtime';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Shared JSON value type (exact readonly variance of the shared boundary)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
7
15
|
|
|
8
16
|
export type JsonValue =
|
|
9
17
|
| null
|
|
@@ -13,40 +21,83 @@ export type JsonValue =
|
|
|
13
21
|
| readonly JsonValue[]
|
|
14
22
|
| { readonly [key: string]: JsonValue };
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Identities
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/** Canonical lowercase local id of the source role Coder. */
|
|
29
|
+
export type CodeRole = 'coder';
|
|
30
|
+
|
|
31
|
+
/** One role-id array per parallel group; CODE declares no parallel group. */
|
|
32
|
+
export const concurrentRoleSets: readonly (readonly CodeRole[])[] = [];
|
|
33
|
+
|
|
34
|
+
/** Delegated-player working leaves (CODE-1, CODE-3). */
|
|
35
|
+
export type WorkingStateId = 'firstPhase' | 'irTaskPhase';
|
|
36
|
+
|
|
37
|
+
/** Working leaves that may suspend for, and resume from, a Boss reply. */
|
|
38
|
+
export type ResumableStateId = WorkingStateId;
|
|
39
|
+
|
|
40
|
+
/** Root `BOSS_INTERRUPT` targets. */
|
|
41
|
+
export type JumpableStateId = WorkingStateId;
|
|
42
|
+
|
|
43
|
+
/** Nested `review` call states (CODE-2, CODE-4). */
|
|
44
|
+
export type ReviewStateId = 'reviewNewIntentPhase' | 'reviewIrTaskPhase';
|
|
21
45
|
|
|
22
46
|
export type CodeSourceItem = 'CODE-1' | 'CODE-2' | 'CODE-3' | 'CODE-4';
|
|
23
47
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
| '
|
|
27
|
-
| '
|
|
28
|
-
| '
|
|
48
|
+
/** The accepted phase outcome whose `review` call decides the next step. */
|
|
49
|
+
export type PhaseOutcome =
|
|
50
|
+
| 'directCommit'
|
|
51
|
+
| 'irCommit'
|
|
52
|
+
| 'moreTasks'
|
|
53
|
+
| 'finalTask';
|
|
54
|
+
|
|
55
|
+
/** The literal nested-call target shared by both call states and guards. */
|
|
56
|
+
const REVIEW_PLAYBOOK_ID = 'review';
|
|
29
57
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Boss-reply suspension (scalar form: one active player task at a time)
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
export interface PendingBossQuestion {
|
|
63
|
+
readonly questionId: ResumableStateId;
|
|
64
|
+
readonly resumeStateId: ResumableStateId;
|
|
33
65
|
readonly sourceItem: 'CODE-1' | 'CODE-3';
|
|
34
|
-
readonly asker: { readonly kind: 'role'; readonly roleId:
|
|
66
|
+
readonly asker: { readonly kind: 'role'; readonly roleId: CodeRole };
|
|
35
67
|
readonly question: string;
|
|
36
|
-
}
|
|
68
|
+
}
|
|
37
69
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Delegated player actor contract
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
interface PlayerInputBase {
|
|
75
|
+
readonly role: CodeRole;
|
|
76
|
+
/** The source item's full final prompt, verbatim. */
|
|
42
77
|
readonly prompt: string;
|
|
78
|
+
/** This state's local result contract: guard name → description. */
|
|
43
79
|
readonly result: Readonly<Record<string, string>>;
|
|
44
|
-
|
|
80
|
+
/** `<caller-input>`: the caller's complete coding request. */
|
|
81
|
+
readonly callerInput?: string;
|
|
82
|
+
/** `<run-results>`: relevant run results; empty when none were supplied. */
|
|
45
83
|
readonly runResults: string;
|
|
46
|
-
readonly irNumber?: string;
|
|
47
84
|
readonly pendingBossQuestion?: PendingBossQuestion;
|
|
48
85
|
readonly bossReply?: string;
|
|
49
|
-
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface FirstPhasePlayerInput extends PlayerInputBase {
|
|
89
|
+
readonly stateId: 'firstPhase';
|
|
90
|
+
readonly sourceItem: 'CODE-1';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface IrTaskPhasePlayerInput extends PlayerInputBase {
|
|
94
|
+
readonly stateId: 'irTaskPhase';
|
|
95
|
+
readonly sourceItem: 'CODE-3';
|
|
96
|
+
/** `<ir-number>`: the IR whose next unfinished task this phase implements. */
|
|
97
|
+
readonly irNumber?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type PlayerInput = FirstPhasePlayerInput | IrTaskPhasePlayerInput;
|
|
50
101
|
|
|
51
102
|
export type PlayerOutput =
|
|
52
103
|
| {
|
|
@@ -74,91 +125,119 @@ export type PlayerOutput =
|
|
|
74
125
|
readonly irNumber: string;
|
|
75
126
|
readonly irTask: string;
|
|
76
127
|
}
|
|
77
|
-
| {
|
|
78
|
-
readonly guard: 'needsBossReply';
|
|
79
|
-
readonly question: string;
|
|
80
|
-
};
|
|
128
|
+
| { readonly guard: 'needsBossReply'; readonly question: string };
|
|
81
129
|
|
|
82
|
-
|
|
83
|
-
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Nested playbook actor contract
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
export interface PlaybookInput {
|
|
135
|
+
readonly stateId: ReviewStateId;
|
|
84
136
|
readonly sourceItem: 'CODE-2' | 'CODE-4';
|
|
85
|
-
readonly playbookId:
|
|
137
|
+
readonly playbookId: typeof REVIEW_PLAYBOOK_ID;
|
|
86
138
|
readonly text: string;
|
|
87
|
-
}
|
|
139
|
+
}
|
|
88
140
|
|
|
89
|
-
|
|
141
|
+
/** A successful call yields the child's own JSON-safe machine output. */
|
|
142
|
+
export type PlaybookOutput = JsonValue | undefined;
|
|
143
|
+
|
|
144
|
+
/** Public output interface of the packaged builtin `review`. */
|
|
145
|
+
export interface ReviewPassOutput {
|
|
90
146
|
readonly noUnsettledFindings: true;
|
|
91
|
-
/**
|
|
92
|
-
* Exact repository revision at which the review scope was evaluated:
|
|
93
|
-
* the last review-fix commit when one landed, otherwise the clean
|
|
94
|
-
* round's receipt-observed HEAD (DR-045). Always present.
|
|
95
|
-
*/
|
|
96
147
|
readonly evaluatedRevision: string;
|
|
97
|
-
}
|
|
148
|
+
}
|
|
98
149
|
|
|
99
|
-
export
|
|
150
|
+
export interface CompactError {
|
|
100
151
|
readonly name: string;
|
|
101
152
|
readonly message: string;
|
|
102
|
-
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface ErrorRecord {
|
|
156
|
+
readonly name: string;
|
|
157
|
+
readonly message: string;
|
|
158
|
+
readonly stack?: string;
|
|
159
|
+
}
|
|
103
160
|
|
|
104
|
-
|
|
161
|
+
/** Sanitized completed-result evidence of the `review` call that ended CODE. */
|
|
162
|
+
export type CompletedReviewResult =
|
|
163
|
+
| {
|
|
164
|
+
readonly playbookId: typeof REVIEW_PLAYBOOK_ID;
|
|
165
|
+
readonly status: 'ok';
|
|
166
|
+
readonly output?: JsonValue;
|
|
167
|
+
}
|
|
168
|
+
| {
|
|
169
|
+
readonly playbookId: typeof REVIEW_PLAYBOOK_ID;
|
|
170
|
+
readonly status: 'aborted' | 'error';
|
|
171
|
+
readonly error: CompactError;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Machine input, context, events, and output
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
/** Public output interface of the packaged builtin `code`. */
|
|
179
|
+
export type CodeOutput =
|
|
105
180
|
| {
|
|
106
181
|
readonly status: 'complete';
|
|
107
|
-
/** Exact identity of the last CODE-owned commit. */
|
|
108
182
|
readonly lastCodeCommit: string;
|
|
109
|
-
/** Exact repository revision the final passing review evaluated. */
|
|
110
183
|
readonly finalEvaluatedRevision: string;
|
|
111
|
-
/** Every phase's review passed with no unsettled findings. */
|
|
112
184
|
readonly allReviewsPassed: true;
|
|
113
185
|
}
|
|
114
186
|
| {
|
|
115
187
|
readonly status: 'review-failed';
|
|
116
|
-
/** Exact identity of the last CODE-owned commit. */
|
|
117
188
|
readonly lastCodeCommit: string;
|
|
118
189
|
readonly error: CompactError;
|
|
119
190
|
};
|
|
120
191
|
|
|
121
|
-
export
|
|
192
|
+
export interface CodeInput {
|
|
193
|
+
/** Optional seed for `<run-results>`; `START_CODE` may supply its own. */
|
|
122
194
|
readonly runResults?: string;
|
|
123
|
-
}
|
|
195
|
+
}
|
|
124
196
|
|
|
125
|
-
export
|
|
197
|
+
export interface CodeContext {
|
|
198
|
+
/** `<run-results>`: relevant run results relayed to every phase. */
|
|
126
199
|
readonly runResults: string;
|
|
200
|
+
/** `<caller-input>`: the caller's coding request and relevant context. */
|
|
127
201
|
readonly callerInput?: string;
|
|
202
|
+
/** `<code-commit>`: the accepted `latestCommit` of the latest phase. */
|
|
203
|
+
readonly codeCommit?: string;
|
|
204
|
+
/** `<coder-output>`: Coder's verbatim final text for the latest phase. */
|
|
128
205
|
readonly coderOutput?: string;
|
|
129
|
-
|
|
206
|
+
/** `<ir-number>`: the created or continued IR. */
|
|
130
207
|
readonly irNumber?: string;
|
|
208
|
+
/** `<ir-task>`: the IR task the latest IR-task phase implemented. */
|
|
131
209
|
readonly irTask?: string;
|
|
210
|
+
/** The accepted outcome of the phase under review. */
|
|
211
|
+
readonly phaseOutcome?: PhaseOutcome;
|
|
212
|
+
/** Revision evaluated by the latest passing review. */
|
|
132
213
|
readonly evaluatedRevision?: string;
|
|
133
|
-
|
|
134
|
-
readonly completion?: 'complete' | 'review-failed';
|
|
214
|
+
/** Compact failure CODE reports when review did not pass a phase. */
|
|
135
215
|
readonly reviewError?: CompactError;
|
|
136
|
-
|
|
216
|
+
/** Sanitized evidence of the review result that ended CODE. */
|
|
217
|
+
readonly reviewEvidence?: CompletedReviewResult;
|
|
218
|
+
readonly lastError?: ErrorRecord;
|
|
137
219
|
readonly pendingBossQuestion?: PendingBossQuestion;
|
|
138
220
|
readonly bossReply?: string;
|
|
139
|
-
}
|
|
221
|
+
}
|
|
140
222
|
|
|
141
|
-
export type
|
|
142
|
-
| {
|
|
223
|
+
export type CodeEvent =
|
|
224
|
+
| {
|
|
225
|
+
readonly type: 'START_CODE';
|
|
226
|
+
readonly callerInput: string;
|
|
227
|
+
readonly runResults?: string;
|
|
228
|
+
}
|
|
229
|
+
| { readonly type: 'BOSS_INTERRUPT'; readonly targetId: JumpableStateId }
|
|
143
230
|
| {
|
|
144
231
|
readonly type: 'BOSS_REPLY';
|
|
145
232
|
readonly answer: string;
|
|
146
|
-
readonly questionId?:
|
|
233
|
+
readonly questionId?: ResumableStateId;
|
|
147
234
|
};
|
|
148
235
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
"Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
|
|
153
|
-
'Make the commit message explain concisely what changed and why, including relevant verification.',
|
|
154
|
-
'Identify every new commit you make.',
|
|
155
|
-
'Credit every AI that contributed to this commit: Coder <coder-llm>.',
|
|
156
|
-
];
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Prompts and result contracts (verbatim from code.gears.md)
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
157
239
|
|
|
158
240
|
const FIRST_PHASE_PROMPT = [
|
|
159
|
-
'> Original request: <caller-input>',
|
|
160
|
-
'> Run results: <run-results>',
|
|
161
|
-
'',
|
|
162
241
|
'First determine whether the coding request starts a new coding intent or continues an existing IR with unfinished work.',
|
|
163
242
|
'If the request may continue an existing IR but does not identify it unambiguously, ask Boss before changing files.',
|
|
164
243
|
'',
|
|
@@ -174,473 +253,556 @@ const FIRST_PHASE_PROMPT = [
|
|
|
174
253
|
'',
|
|
175
254
|
'Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.',
|
|
176
255
|
'',
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
256
|
+
'Keep to the original intent and follow what it asks.',
|
|
257
|
+
'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
|
|
258
|
+
"Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
|
|
259
|
+
'Make the commit message explain concisely what changed and why, including relevant verification.',
|
|
260
|
+
'Identify every new commit you make.',
|
|
261
|
+
'Credit every AI that contributed to this commit: Coder <coder-llm>.',
|
|
262
|
+
'',
|
|
181
263
|
'> Original request: <caller-input>',
|
|
182
|
-
'> IR number: <ir-number>',
|
|
183
264
|
'> Run results: <run-results>',
|
|
184
|
-
|
|
265
|
+
].join('\n');
|
|
266
|
+
|
|
267
|
+
const IR_TASK_PHASE_PROMPT = [
|
|
185
268
|
'Read the identified IR and implement exactly its next unfinished task, including corresponding tests or specs if any.',
|
|
186
269
|
'Do not implement a later task in this phase.',
|
|
187
270
|
"Mark the IR's progress and deliverables when relevant.",
|
|
188
271
|
'If the IR will be finished after this phase, double-check that all acceptance criteria are met.',
|
|
189
272
|
'',
|
|
190
|
-
|
|
273
|
+
'Keep to the original intent and follow what it asks.',
|
|
274
|
+
'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
|
|
275
|
+
"Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
|
|
276
|
+
'Make the commit message explain concisely what changed and why, including relevant verification.',
|
|
277
|
+
'Identify every new commit you make.',
|
|
278
|
+
'Credit every AI that contributed to this commit: Coder <coder-llm>.',
|
|
279
|
+
'',
|
|
280
|
+
'> Original request: <caller-input>',
|
|
281
|
+
'> IR number: <ir-number>',
|
|
282
|
+
'> Run results: <run-results>',
|
|
283
|
+
].join('\n');
|
|
284
|
+
|
|
285
|
+
const REVIEW_NEW_INTENT_PHASE_TEMPLATE = [
|
|
286
|
+
'> Original intent: <caller-input>',
|
|
287
|
+
'> Review scope: the commit <code-commit> from this coding phase and its resulting repository state.',
|
|
288
|
+
'> Coder output: <coder-output>',
|
|
289
|
+
].join('\n');
|
|
290
|
+
|
|
291
|
+
const REVIEW_IR_TASK_PHASE_TEMPLATE = [
|
|
292
|
+
'> Original intent: <caller-input>',
|
|
293
|
+
'> Review scope: the commit <code-commit> from this coding phase and its resulting repository state.',
|
|
294
|
+
'> Coder output: <coder-output>',
|
|
295
|
+
'',
|
|
296
|
+
'> Current IR task: <ir-task>',
|
|
191
297
|
].join('\n');
|
|
192
298
|
|
|
193
|
-
const
|
|
299
|
+
const NEEDS_BOSS_REPLY_DESCRIPTION =
|
|
194
300
|
"The acting agent's prose surfaces a clarifying question for Boss that the agent cannot answer alone. Output shall include `question: <verbatim question text from the acting agent's prose>`.";
|
|
195
301
|
|
|
196
|
-
const
|
|
302
|
+
const FIRST_PHASE_RESULT = {
|
|
197
303
|
directCommit:
|
|
198
|
-
|
|
304
|
+
"Coder completed the new coding intent as one direct implementation phase and made its one new commit; Coder's result affirmatively supports this outcome, and no fixed presentation format of the reply is required. Output shall include `coderOutput: <verbatim final text>` and `latestCommit: <commit identity>`.",
|
|
199
305
|
irCommit:
|
|
200
|
-
|
|
306
|
+
"Coder decomposed the new coding intent into a new IR, implemented no IR task, and made its one new commit; Coder's result affirmatively supports this outcome, and no fixed presentation format of the reply is required. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, and `irNumber` identifying the created IR.",
|
|
201
307
|
moreTasks:
|
|
202
|
-
|
|
308
|
+
"Coder continued an existing IR, implemented exactly its next unfinished task, made its one new commit, and at least one IR task remains unfinished; Coder's result affirmatively supports this outcome, and no fixed presentation format of the reply is required. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber` identifying the continued IR, and `irTask` naming the implemented task.",
|
|
203
309
|
finalTask:
|
|
204
|
-
|
|
205
|
-
needsBossReply:
|
|
310
|
+
"Coder continued an existing IR, implemented its final unfinished task, and made its one new commit; Coder's result affirmatively supports this outcome, and no fixed presentation format of the reply is required. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber` identifying the continued IR, and `irTask` naming the implemented task.",
|
|
311
|
+
needsBossReply: NEEDS_BOSS_REPLY_DESCRIPTION,
|
|
206
312
|
} as const;
|
|
207
313
|
|
|
208
|
-
const
|
|
314
|
+
const IR_TASK_PHASE_RESULT = {
|
|
209
315
|
moreTasks:
|
|
210
|
-
"Coder
|
|
316
|
+
"Coder implemented exactly the IR's next unfinished task, made its one new commit, and at least one IR task remains unfinished; Coder's result affirmatively supports this outcome, and no fixed presentation format of the reply is required. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber` identifying the IR, and `irTask` naming the implemented task.",
|
|
211
317
|
finalTask:
|
|
212
|
-
"Coder
|
|
213
|
-
needsBossReply:
|
|
318
|
+
"Coder implemented the IR's final unfinished task and made its one new commit; Coder's result affirmatively supports this outcome, and no fixed presentation format of the reply is required. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber` identifying the IR, and `irTask` naming the implemented task.",
|
|
319
|
+
needsBossReply: NEEDS_BOSS_REPLY_DESCRIPTION,
|
|
214
320
|
} as const;
|
|
215
321
|
|
|
216
322
|
const STATE_DESCRIPTIONS = {
|
|
217
|
-
ready: 'Waiting for a coding
|
|
218
|
-
|
|
219
|
-
'Coder
|
|
220
|
-
|
|
221
|
-
'
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
awaitBossReply:
|
|
225
|
-
failed:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
323
|
+
ready: 'Waiting for a coding request.',
|
|
324
|
+
firstPhase:
|
|
325
|
+
'Coder runs the first coding phase: a direct implementation, a new IR, or the next task of an existing IR.',
|
|
326
|
+
reviewNewIntentPhase:
|
|
327
|
+
'REVIEW examines the commit of the direct implementation or new-IR phase.',
|
|
328
|
+
irTaskPhase: "Coder implements the identified IR's next unfinished task.",
|
|
329
|
+
reviewIrTaskPhase: 'REVIEW examines the commit of the IR-task phase.',
|
|
330
|
+
awaitBossReply: "Waiting for Boss to answer the acting agent's question.",
|
|
331
|
+
failed:
|
|
332
|
+
'CODE parked after a control-plane failure and waits for Boss to restart or resume it.',
|
|
333
|
+
reviewFailed:
|
|
334
|
+
'CODE stopped because review did not pass a phase and reports the failure with its last code-owned commit.',
|
|
335
|
+
done: "CODE completed: every phase's review passed with no unsettled findings.",
|
|
229
336
|
} as const;
|
|
230
337
|
|
|
231
|
-
|
|
232
|
-
stateId: StateId,
|
|
233
|
-
role?: 'coder',
|
|
234
|
-
) {
|
|
235
|
-
return {
|
|
236
|
-
playbook: {
|
|
237
|
-
stateId,
|
|
238
|
-
description: STATE_DESCRIPTIONS[stateId],
|
|
239
|
-
...(role === undefined ? {} : { role }),
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
}
|
|
338
|
+
type StateKey = keyof typeof STATE_DESCRIPTIONS;
|
|
243
339
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
playbook: {
|
|
253
|
-
stateId,
|
|
254
|
-
description: STATE_DESCRIPTIONS[stateId],
|
|
255
|
-
terminal,
|
|
256
|
-
},
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
261
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
const prototype = Object.getPrototypeOf(value);
|
|
265
|
-
return prototype === Object.prototype || prototype === null;
|
|
266
|
-
}
|
|
340
|
+
const RESUME_METADATA = {
|
|
341
|
+
firstPhase: { sourceItem: 'CODE-1' },
|
|
342
|
+
irTaskPhase: { sourceItem: 'CODE-3' },
|
|
343
|
+
} as const satisfies Record<
|
|
344
|
+
ResumableStateId,
|
|
345
|
+
{ readonly sourceItem: PendingBossQuestion['sourceItem'] }
|
|
346
|
+
>;
|
|
267
347
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (!isRecord(event)) return undefined;
|
|
274
|
-
return isRecord(event.output) ? event.output : undefined;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function outputGuard(event: unknown, guard: PlayerOutput['guard']): boolean {
|
|
278
|
-
return playerOutput(event)?.guard === guard;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function outputString(event: unknown, field: string): string | undefined {
|
|
282
|
-
const value = playerOutput(event)?.[field];
|
|
283
|
-
return isNonEmptyString(value) ? value : undefined;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function hasCommittedOutput(event: unknown): boolean {
|
|
287
|
-
return (
|
|
288
|
-
outputString(event, 'coderOutput') !== undefined &&
|
|
289
|
-
outputString(event, 'latestCommit') !== undefined
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function hasIrTaskOutput(event: unknown): boolean {
|
|
294
|
-
return (
|
|
295
|
-
hasCommittedOutput(event) &&
|
|
296
|
-
outputString(event, 'irNumber') !== undefined &&
|
|
297
|
-
outputString(event, 'irTask') !== undefined
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function isDirectCommit({ event }: { event: unknown }): boolean {
|
|
302
|
-
return (
|
|
303
|
-
outputGuard(event, 'directCommit') &&
|
|
304
|
-
hasCommittedOutput(event)
|
|
305
|
-
);
|
|
306
|
-
}
|
|
348
|
+
const REVIEW_NOT_PASSED: CompactError = {
|
|
349
|
+
name: 'ReviewNotPassed',
|
|
350
|
+
message:
|
|
351
|
+
'review returned a terminal result that does not establish that the supplied scope was evaluated with no unsettled findings.',
|
|
352
|
+
};
|
|
307
353
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
hasCommittedOutput(event) &&
|
|
312
|
-
outputString(event, 'irNumber') !== undefined
|
|
313
|
-
);
|
|
314
|
-
}
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
// Structural helpers
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
315
357
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
);
|
|
321
|
-
}
|
|
358
|
+
const isRecord = (
|
|
359
|
+
value: unknown,
|
|
360
|
+
): value is Readonly<Record<string, unknown>> =>
|
|
361
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
322
362
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
outputGuard(event, 'finalTask') &&
|
|
326
|
-
hasIrTaskOutput(event)
|
|
327
|
-
);
|
|
328
|
-
}
|
|
363
|
+
const isNonEmptyString = (value: unknown): value is string =>
|
|
364
|
+
typeof value === 'string' && value.trim().length > 0;
|
|
329
365
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
outputGuard(event, 'needsBossReply') &&
|
|
333
|
-
outputString(event, 'question') !== undefined
|
|
334
|
-
);
|
|
335
|
-
}
|
|
366
|
+
const doneOutputOf = (event: unknown): unknown =>
|
|
367
|
+
isRecord(event) ? event.output : undefined;
|
|
336
368
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
369
|
+
const actorErrorOf = (event: unknown): unknown =>
|
|
370
|
+
isRecord(event) ? event.error : undefined;
|
|
340
371
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
372
|
+
/** Narrows a player done event to the declared player output contract. */
|
|
373
|
+
function playerOutputOf(event: unknown): PlayerOutput | undefined {
|
|
374
|
+
const output = doneOutputOf(event);
|
|
375
|
+
if (!isRecord(output)) return undefined;
|
|
376
|
+
switch (output.guard) {
|
|
377
|
+
case 'directCommit':
|
|
378
|
+
return isNonEmptyString(output.coderOutput) &&
|
|
379
|
+
isNonEmptyString(output.latestCommit)
|
|
380
|
+
? {
|
|
381
|
+
guard: 'directCommit',
|
|
382
|
+
coderOutput: output.coderOutput,
|
|
383
|
+
latestCommit: output.latestCommit,
|
|
384
|
+
}
|
|
385
|
+
: undefined;
|
|
386
|
+
case 'irCommit':
|
|
387
|
+
return isNonEmptyString(output.coderOutput) &&
|
|
388
|
+
isNonEmptyString(output.latestCommit) &&
|
|
389
|
+
isNonEmptyString(output.irNumber)
|
|
390
|
+
? {
|
|
391
|
+
guard: 'irCommit',
|
|
392
|
+
coderOutput: output.coderOutput,
|
|
393
|
+
latestCommit: output.latestCommit,
|
|
394
|
+
irNumber: output.irNumber,
|
|
395
|
+
}
|
|
396
|
+
: undefined;
|
|
397
|
+
case 'moreTasks':
|
|
398
|
+
case 'finalTask':
|
|
399
|
+
return isNonEmptyString(output.coderOutput) &&
|
|
400
|
+
isNonEmptyString(output.latestCommit) &&
|
|
401
|
+
isNonEmptyString(output.irNumber) &&
|
|
402
|
+
isNonEmptyString(output.irTask)
|
|
403
|
+
? {
|
|
404
|
+
guard: output.guard,
|
|
405
|
+
coderOutput: output.coderOutput,
|
|
406
|
+
latestCommit: output.latestCommit,
|
|
407
|
+
irNumber: output.irNumber,
|
|
408
|
+
irTask: output.irTask,
|
|
409
|
+
}
|
|
410
|
+
: undefined;
|
|
411
|
+
case 'needsBossReply':
|
|
412
|
+
return isNonEmptyString(output.question)
|
|
413
|
+
? { guard: 'needsBossReply', question: output.question }
|
|
414
|
+
: undefined;
|
|
415
|
+
default:
|
|
416
|
+
return undefined;
|
|
351
417
|
}
|
|
352
|
-
return (
|
|
353
|
-
value.noUnsettledFindings === true &&
|
|
354
|
-
isNonEmptyString(value.evaluatedRevision)
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function reviewApprovedFor(
|
|
359
|
-
phase: CodePhase,
|
|
360
|
-
): (args: { context: CodingContext; event: unknown }) => boolean {
|
|
361
|
-
return ({ context, event }) =>
|
|
362
|
-
context.phase === phase && isReviewApprovedValue(reviewOutput(event));
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function isStateValue(value: unknown, ancestors = new Set<object>()): boolean {
|
|
366
|
-
if (typeof value === 'string') return true;
|
|
367
|
-
if (!isRecord(value) || ancestors.has(value)) return false;
|
|
368
|
-
const next = new Set(ancestors).add(value);
|
|
369
|
-
return Reflect.ownKeys(value).every(
|
|
370
|
-
(key) =>
|
|
371
|
-
typeof key === 'string' &&
|
|
372
|
-
Object.prototype.propertyIsEnumerable.call(value, key) &&
|
|
373
|
-
isStateValue(value[key], next),
|
|
374
|
-
);
|
|
375
418
|
}
|
|
376
419
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
420
|
+
/**
|
|
421
|
+
* Strict JSON validation: null, booleans, finite numbers, strings, exact
|
|
422
|
+
* arrays, and plain records of enumerable own data properties. Cycle
|
|
423
|
+
* detection tracks only the active recursion path.
|
|
424
|
+
*/
|
|
425
|
+
function isJsonValue(value: unknown, path: Set<object> = new Set()): value is JsonValue {
|
|
426
|
+
if (value === null) return true;
|
|
427
|
+
switch (typeof value) {
|
|
428
|
+
case 'boolean':
|
|
429
|
+
case 'string':
|
|
430
|
+
return true;
|
|
431
|
+
case 'number':
|
|
432
|
+
return Number.isFinite(value);
|
|
433
|
+
case 'object':
|
|
434
|
+
break;
|
|
435
|
+
default:
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
const container = value as object;
|
|
439
|
+
if (path.has(container)) return false;
|
|
440
|
+
path.add(container);
|
|
441
|
+
try {
|
|
442
|
+
if (Array.isArray(container)) {
|
|
443
|
+
if (Object.getPrototypeOf(container) !== Array.prototype) return false;
|
|
444
|
+
const keys = Reflect.ownKeys(container);
|
|
445
|
+
if (keys.length !== container.length + 1) return false;
|
|
446
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(container, 'length');
|
|
447
|
+
if (
|
|
448
|
+
lengthDescriptor === undefined ||
|
|
449
|
+
lengthDescriptor.configurable !== false ||
|
|
450
|
+
lengthDescriptor.enumerable !== false ||
|
|
451
|
+
lengthDescriptor.value !== container.length
|
|
452
|
+
) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
for (let index = 0; index < container.length; index += 1) {
|
|
456
|
+
const descriptor = Object.getOwnPropertyDescriptor(container, String(index));
|
|
457
|
+
if (
|
|
458
|
+
descriptor === undefined ||
|
|
459
|
+
!descriptor.enumerable ||
|
|
460
|
+
!('value' in descriptor) ||
|
|
461
|
+
!isJsonValue(descriptor.value, path)
|
|
462
|
+
) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
const prototype = Object.getPrototypeOf(container);
|
|
469
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
470
|
+
for (const key of Reflect.ownKeys(container)) {
|
|
471
|
+
if (typeof key !== 'string') return false;
|
|
472
|
+
const descriptor = Object.getOwnPropertyDescriptor(container, key);
|
|
473
|
+
if (
|
|
474
|
+
descriptor === undefined ||
|
|
475
|
+
!descriptor.enumerable ||
|
|
476
|
+
!('value' in descriptor) ||
|
|
477
|
+
!isJsonValue(descriptor.value, path)
|
|
478
|
+
) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return true;
|
|
483
|
+
} finally {
|
|
484
|
+
path.delete(container);
|
|
415
485
|
}
|
|
416
|
-
return (
|
|
417
|
-
isStateValue(value.value) &&
|
|
418
|
-
isStringArray(value.activeStateIds) &&
|
|
419
|
-
isStringArray(value.tags) &&
|
|
420
|
-
(value.status === 'active' ||
|
|
421
|
-
value.status === 'done' ||
|
|
422
|
-
value.status === 'error' ||
|
|
423
|
-
value.status === 'stopped') &&
|
|
424
|
-
typeof value.quiescent === 'boolean' &&
|
|
425
|
-
(!Object.prototype.hasOwnProperty.call(value, 'stateId') ||
|
|
426
|
-
(isNonEmptyString(value.stateId) &&
|
|
427
|
-
value.activeStateIds.length === 1 &&
|
|
428
|
-
value.activeStateIds[0] === value.stateId))
|
|
429
|
-
);
|
|
430
486
|
}
|
|
431
487
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
488
|
+
/** Normalizes any thrown or malformed value to a JSON-safe error record. */
|
|
489
|
+
function errorRecord(value: unknown): ErrorRecord {
|
|
490
|
+
if (value instanceof Error) {
|
|
491
|
+
return {
|
|
492
|
+
name: value.name || 'Error',
|
|
493
|
+
message: value.message,
|
|
494
|
+
...(typeof value.stack === 'string' ? { stack: value.stack } : {}),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (isRecord(value) && typeof value.message === 'string') {
|
|
498
|
+
return {
|
|
499
|
+
name: isNonEmptyString(value.name) ? value.name : 'Error',
|
|
500
|
+
message: value.message,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
return { name: 'Error', message: String(value) };
|
|
436
504
|
}
|
|
437
505
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
readonly error?: CompactError;
|
|
442
|
-
}
|
|
443
|
-
// DR-048: a child that completed at an authored failure terminal. The
|
|
444
|
-
// bridge rejects it through the same error path, so CODE recognizes the
|
|
445
|
-
// failure from REVIEW's own machine and never from its output fields.
|
|
446
|
-
| {
|
|
447
|
-
readonly status: 'ok';
|
|
448
|
-
readonly output?: JsonValue;
|
|
449
|
-
};
|
|
506
|
+
// ---------------------------------------------------------------------------
|
|
507
|
+
// Nested `review` results
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
450
509
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
510
|
+
/**
|
|
511
|
+
* Source-authored acceptance on `review`'s declared public output: the
|
|
512
|
+
* result returns the exact evaluated repository revision and affirmatively
|
|
513
|
+
* establishes that no unsettled findings remain. The shared bridge correlates
|
|
514
|
+
* the result with the supplied review scope.
|
|
515
|
+
*/
|
|
516
|
+
function reviewPassOf(event: unknown): ReviewPassOutput | undefined {
|
|
517
|
+
const output = doneOutputOf(event);
|
|
518
|
+
if (!isRecord(output)) return undefined;
|
|
519
|
+
return output.noUnsettledFindings === true &&
|
|
520
|
+
isNonEmptyString(output.evaluatedRevision)
|
|
521
|
+
? { noUnsettledFindings: true, evaluatedRevision: output.evaluatedRevision }
|
|
522
|
+
: undefined;
|
|
463
523
|
}
|
|
464
524
|
|
|
465
|
-
|
|
525
|
+
/**
|
|
526
|
+
* Recognizes an authored rejected child result: an aborted or error call, or
|
|
527
|
+
* a child that completed at its own authored failure terminal.
|
|
528
|
+
*/
|
|
529
|
+
export function authoredChildResult(
|
|
466
530
|
error: unknown,
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
(key) => typeof key !== 'string' || !allowed.has(key),
|
|
480
|
-
) ||
|
|
481
|
-
(result.status !== 'aborted' &&
|
|
482
|
-
result.status !== 'error' &&
|
|
483
|
-
result.status !== 'ok') ||
|
|
484
|
-
result.playbookId !== 'review'
|
|
485
|
-
) {
|
|
486
|
-
return undefined;
|
|
487
|
-
}
|
|
488
|
-
if (
|
|
489
|
-
Object.prototype.hasOwnProperty.call(result, 'childSessionId') &&
|
|
490
|
-
!isNonEmptyString(result.childSessionId)
|
|
491
|
-
) {
|
|
492
|
-
return undefined;
|
|
493
|
-
}
|
|
494
|
-
if (
|
|
495
|
-
Object.prototype.hasOwnProperty.call(result, 'state') &&
|
|
496
|
-
!isPlaybookState(result.state)
|
|
497
|
-
) {
|
|
531
|
+
expectedPlaybookId: string,
|
|
532
|
+
): PlaybookCallResult | undefined {
|
|
533
|
+
if (!(error instanceof Error)) return undefined;
|
|
534
|
+
try {
|
|
535
|
+
const result = validatePlaybookCallResult(
|
|
536
|
+
(error as Error & { result?: unknown }).result,
|
|
537
|
+
expectedPlaybookId,
|
|
538
|
+
);
|
|
539
|
+
return result.status !== 'ok' || result.terminal?.kind === 'failure'
|
|
540
|
+
? result
|
|
541
|
+
: undefined;
|
|
542
|
+
} catch {
|
|
498
543
|
return undefined;
|
|
499
544
|
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** Compact evidence and reported error for an authored review failure. */
|
|
548
|
+
function authoredReviewFailureOf(
|
|
549
|
+
event: unknown,
|
|
550
|
+
):
|
|
551
|
+
| { readonly evidence: CompletedReviewResult; readonly error: CompactError }
|
|
552
|
+
| undefined {
|
|
553
|
+
const result = authoredChildResult(actorErrorOf(event), REVIEW_PLAYBOOK_ID);
|
|
554
|
+
if (result === undefined) return undefined;
|
|
500
555
|
if (result.status === 'ok') {
|
|
501
|
-
|
|
502
|
-
!Object.prototype.hasOwnProperty.call(result, 'terminal') ||
|
|
503
|
-
!isFailureTerminalRecord(result.terminal) ||
|
|
504
|
-
!isNonEmptyString(result.childSessionId)
|
|
505
|
-
) {
|
|
506
|
-
return undefined;
|
|
507
|
-
}
|
|
556
|
+
const description = result.terminal?.description;
|
|
508
557
|
return {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
: { output: result.output
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
errorKeys.some(
|
|
521
|
-
(key) =>
|
|
522
|
-
typeof key !== 'string' ||
|
|
523
|
-
(key !== 'name' && key !== 'message' && key !== 'stack'),
|
|
524
|
-
) ||
|
|
525
|
-
!Object.prototype.hasOwnProperty.call(result.error, 'name') ||
|
|
526
|
-
!Object.prototype.hasOwnProperty.call(result.error, 'message') ||
|
|
527
|
-
!isNonEmptyString(result.error.name) ||
|
|
528
|
-
typeof result.error.message !== 'string' ||
|
|
529
|
-
(Object.prototype.hasOwnProperty.call(result.error, 'stack') &&
|
|
530
|
-
typeof result.error.stack !== 'string')
|
|
531
|
-
) {
|
|
532
|
-
return undefined;
|
|
533
|
-
}
|
|
534
|
-
normalizedError = {
|
|
535
|
-
name: result.error.name,
|
|
536
|
-
message: result.error.message,
|
|
558
|
+
evidence: {
|
|
559
|
+
playbookId: REVIEW_PLAYBOOK_ID,
|
|
560
|
+
status: 'ok',
|
|
561
|
+
...(result.output === undefined ? {} : { output: result.output }),
|
|
562
|
+
},
|
|
563
|
+
error: {
|
|
564
|
+
name: 'ReviewFailed',
|
|
565
|
+
message: isNonEmptyString(description)
|
|
566
|
+
? `review ended at its authored failure terminal: ${description}`
|
|
567
|
+
: 'review ended at its authored failure terminal.',
|
|
568
|
+
},
|
|
537
569
|
};
|
|
538
|
-
} else if (result.status === 'error') {
|
|
539
|
-
return undefined;
|
|
540
570
|
}
|
|
571
|
+
const error: CompactError =
|
|
572
|
+
result.error !== undefined
|
|
573
|
+
? { name: result.error.name, message: result.error.message }
|
|
574
|
+
: { name: 'AbortError', message: 'The review call was aborted.' };
|
|
541
575
|
return {
|
|
542
|
-
status: result.status,
|
|
543
|
-
|
|
576
|
+
evidence: { playbookId: REVIEW_PLAYBOOK_ID, status: result.status, error },
|
|
577
|
+
error,
|
|
544
578
|
};
|
|
545
579
|
}
|
|
546
580
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
581
|
+
// ---------------------------------------------------------------------------
|
|
582
|
+
// Nested call text composition (quoted relays)
|
|
583
|
+
// ---------------------------------------------------------------------------
|
|
551
584
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return {
|
|
555
|
-
name: value.name || 'Error',
|
|
556
|
-
message: value.message,
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
return { name: 'Error', message: String(value) };
|
|
560
|
-
}
|
|
585
|
+
const REVIEW_PLACEHOLDER =
|
|
586
|
+
/<caller-input>|<code-commit>|<coder-output>|<ir-task>/g;
|
|
561
587
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
588
|
+
/**
|
|
589
|
+
* Composes review call text in one callback pass per template line: a quoted
|
|
590
|
+
* relay line whose value is empty contributes no line, a multi-line value is
|
|
591
|
+
* quoted line by line, and an absent value keeps its placeholder literal.
|
|
592
|
+
*/
|
|
593
|
+
function composeReviewText(
|
|
594
|
+
template: string,
|
|
595
|
+
values: Readonly<Record<string, string | undefined>>,
|
|
596
|
+
): string {
|
|
597
|
+
const lines: string[] = [];
|
|
598
|
+
for (const line of template.split('\n')) {
|
|
599
|
+
const quoted = line.startsWith('> ');
|
|
600
|
+
const tokens = line.match(REVIEW_PLACEHOLDER) ?? [];
|
|
601
|
+
if (quoted && tokens.some((token) => values[token] === '')) continue;
|
|
602
|
+
lines.push(
|
|
603
|
+
line.replace(REVIEW_PLACEHOLDER, (token: string) => {
|
|
604
|
+
const value = values[token];
|
|
605
|
+
if (value === undefined) return token;
|
|
606
|
+
return quoted ? value.split('\n').join('\n> ') : value;
|
|
607
|
+
}),
|
|
608
|
+
);
|
|
570
609
|
}
|
|
571
|
-
|
|
610
|
+
while (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
|
|
611
|
+
return lines.join('\n');
|
|
572
612
|
}
|
|
573
613
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
'\n',
|
|
578
|
-
);
|
|
579
|
-
}
|
|
614
|
+
// ---------------------------------------------------------------------------
|
|
615
|
+
// Guards
|
|
616
|
+
// ---------------------------------------------------------------------------
|
|
580
617
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
618
|
+
type EventArgs = { readonly event: unknown };
|
|
619
|
+
type ContextEventArgs = {
|
|
620
|
+
readonly context: CodeContext;
|
|
621
|
+
readonly event: unknown;
|
|
622
|
+
};
|
|
623
|
+
type MachineArgs = {
|
|
624
|
+
readonly context: CodeContext;
|
|
625
|
+
readonly event: CodeEvent;
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const acceptsOutcome =
|
|
629
|
+
(outcome: PhaseOutcome) =>
|
|
630
|
+
({ event }: EventArgs): boolean =>
|
|
631
|
+
playerOutputOf(event)?.guard === outcome;
|
|
632
|
+
|
|
633
|
+
const needsBossReplyWithQuestion = ({ event }: EventArgs): boolean =>
|
|
634
|
+
playerOutputOf(event)?.guard === 'needsBossReply';
|
|
635
|
+
|
|
636
|
+
// A review state is entered only through an accepting phase arm, which records
|
|
637
|
+
// the phase outcome and its non-empty code-owned commit.
|
|
638
|
+
const reviewPassedAfter =
|
|
639
|
+
(outcome: PhaseOutcome) =>
|
|
640
|
+
({ context, event }: ContextEventArgs): boolean =>
|
|
641
|
+
context.phaseOutcome === outcome &&
|
|
642
|
+
isNonEmptyString(context.codeCommit) &&
|
|
643
|
+
reviewPassOf(event) !== undefined;
|
|
644
|
+
|
|
645
|
+
const authoredReviewFailure = ({ event }: EventArgs): boolean =>
|
|
646
|
+
authoredReviewFailureOf(event) !== undefined;
|
|
647
|
+
|
|
648
|
+
const validStartCode = ({ event }: MachineArgs): boolean =>
|
|
649
|
+
event.type === 'START_CODE' &&
|
|
650
|
+
isNonEmptyString(event.callerInput) &&
|
|
651
|
+
(event.runResults === undefined || typeof event.runResults === 'string');
|
|
652
|
+
|
|
653
|
+
const repliesToPendingQuestion = ({ context, event }: MachineArgs): boolean =>
|
|
654
|
+
event.type === 'BOSS_REPLY' &&
|
|
655
|
+
context.pendingBossQuestion !== undefined &&
|
|
656
|
+
(event.questionId === undefined ||
|
|
657
|
+
event.questionId === context.pendingBossQuestion.questionId);
|
|
658
|
+
|
|
659
|
+
const emptyBossReply = (args: MachineArgs): boolean =>
|
|
660
|
+
repliesToPendingQuestion(args) &&
|
|
661
|
+
args.event.type === 'BOSS_REPLY' &&
|
|
662
|
+
!isNonEmptyString(args.event.answer);
|
|
587
663
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
664
|
+
const INTERRUPT_PRECONDITIONS = {
|
|
665
|
+
firstPhase: (context: CodeContext) => isNonEmptyString(context.callerInput),
|
|
666
|
+
irTaskPhase: (context: CodeContext) =>
|
|
667
|
+
isNonEmptyString(context.callerInput) &&
|
|
668
|
+
isNonEmptyString(context.irNumber),
|
|
669
|
+
} as const satisfies Record<JumpableStateId, (context: CodeContext) => boolean>;
|
|
670
|
+
|
|
671
|
+
// ---------------------------------------------------------------------------
|
|
672
|
+
// Transition helpers
|
|
673
|
+
// ---------------------------------------------------------------------------
|
|
674
|
+
|
|
675
|
+
/** One guarded root transition per jumpable state. */
|
|
676
|
+
function bossInterrupts<const Ids extends readonly JumpableStateId[]>(
|
|
677
|
+
ids: Ids,
|
|
678
|
+
) {
|
|
679
|
+
return ids.map((id) => ({
|
|
680
|
+
guard: ({ context, event }: MachineArgs) =>
|
|
681
|
+
event.type === 'BOSS_INTERRUPT' &&
|
|
682
|
+
event.targetId === id &&
|
|
683
|
+
INTERRUPT_PRECONDITIONS[id](context),
|
|
684
|
+
target: `#${id}` as const,
|
|
685
|
+
reenter: true,
|
|
686
|
+
actions: 'resetForInterrupt' as const,
|
|
687
|
+
}));
|
|
594
688
|
}
|
|
595
689
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
690
|
+
/** One guarded `BOSS_REPLY` resume transition per suspended working leaf. */
|
|
691
|
+
function resumableStates<const Ids extends readonly ResumableStateId[]>(
|
|
692
|
+
ids: Ids,
|
|
693
|
+
) {
|
|
694
|
+
return ids.map((id) => ({
|
|
695
|
+
guard: (args: MachineArgs) =>
|
|
696
|
+
repliesToPendingQuestion(args) &&
|
|
697
|
+
args.event.type === 'BOSS_REPLY' &&
|
|
698
|
+
isNonEmptyString(args.event.answer) &&
|
|
699
|
+
args.context.pendingBossQuestion?.resumeStateId === id,
|
|
700
|
+
target: `#${id}` as const,
|
|
701
|
+
reenter: true,
|
|
702
|
+
actions: 'rememberBossReply' as const,
|
|
703
|
+
}));
|
|
603
704
|
}
|
|
604
705
|
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
706
|
+
const bossReplyFields = (context: CodeContext) => ({
|
|
707
|
+
...(context.pendingBossQuestion !== undefined
|
|
708
|
+
? { pendingBossQuestion: context.pendingBossQuestion }
|
|
709
|
+
: {}),
|
|
710
|
+
...(context.bossReply !== undefined ? { bossReply: context.bossReply } : {}),
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const acceptedOutcome = (
|
|
714
|
+
source: WorkingStateId,
|
|
715
|
+
target: string,
|
|
716
|
+
outcome: PhaseOutcome | 'needsBossReply',
|
|
717
|
+
) =>
|
|
718
|
+
({
|
|
719
|
+
type: 'playbook.acceptedOutcome',
|
|
720
|
+
params: { source, target, acceptedOutcome: outcome },
|
|
721
|
+
}) as const;
|
|
722
|
+
|
|
723
|
+
const acceptPhaseArm = (
|
|
724
|
+
source: WorkingStateId,
|
|
725
|
+
outcome: PhaseOutcome,
|
|
726
|
+
guard: 'acceptDirectCommit' | 'acceptIrCommit' | 'acceptMoreTasks' | 'acceptFinalTask',
|
|
727
|
+
target: ReviewStateId,
|
|
728
|
+
) =>
|
|
729
|
+
({
|
|
730
|
+
guard,
|
|
731
|
+
target: `#${target}`,
|
|
732
|
+
actions: [
|
|
733
|
+
acceptedOutcome(source, target, outcome),
|
|
734
|
+
'rememberPhaseCommit',
|
|
735
|
+
'clearBossReplyContext',
|
|
736
|
+
],
|
|
737
|
+
}) as const;
|
|
738
|
+
|
|
739
|
+
const suspendArm = (source: WorkingStateId) =>
|
|
740
|
+
({
|
|
741
|
+
guard: 'needsBossReplyWithQuestion',
|
|
742
|
+
target: '#awaitBossReply',
|
|
743
|
+
actions: [
|
|
744
|
+
acceptedOutcome(source, 'awaitBossReply', 'needsBossReply'),
|
|
745
|
+
{ type: 'setPendingBossQuestion', params: { resumeStateId: source } },
|
|
746
|
+
],
|
|
747
|
+
}) as const;
|
|
748
|
+
|
|
749
|
+
const malformedPlayerOutputArm = {
|
|
750
|
+
target: '#failed',
|
|
751
|
+
actions: ['rememberMalformedPlayerOutput', 'clearBossReplyContext'],
|
|
752
|
+
} as const;
|
|
753
|
+
|
|
754
|
+
const playerOnError = {
|
|
755
|
+
target: '#failed',
|
|
756
|
+
actions: ['rememberActorError', 'clearBossReplyContext'],
|
|
757
|
+
} as const;
|
|
758
|
+
|
|
759
|
+
const reviewOnError = [
|
|
760
|
+
{
|
|
761
|
+
guard: 'authoredReviewFailure',
|
|
762
|
+
target: '#reviewFailed',
|
|
763
|
+
actions: 'rememberAuthoredReviewFailure',
|
|
764
|
+
},
|
|
765
|
+
{ target: '#failed', actions: 'rememberActorError' },
|
|
766
|
+
] as const;
|
|
767
|
+
|
|
768
|
+
const meta = (stateId: StateKey, role?: CodeRole) => ({
|
|
769
|
+
playbook: {
|
|
770
|
+
stateId,
|
|
771
|
+
description: STATE_DESCRIPTIONS[stateId],
|
|
772
|
+
...(role === undefined ? {} : { role }),
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
const finalMeta = (
|
|
777
|
+
stateId: 'reviewFailed' | 'done',
|
|
778
|
+
terminal: 'success' | 'failure',
|
|
779
|
+
) => ({
|
|
780
|
+
playbook: {
|
|
781
|
+
stateId,
|
|
782
|
+
description: STATE_DESCRIPTIONS[stateId],
|
|
783
|
+
terminal,
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
// Machine
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
|
|
791
|
+
export const codeMachine = setup({
|
|
792
|
+
types: {
|
|
793
|
+
context: {} as CodeContext,
|
|
794
|
+
events: {} as CodeEvent,
|
|
795
|
+
input: {} as CodeInput,
|
|
796
|
+
output: {} as CodeOutput,
|
|
611
797
|
},
|
|
612
798
|
actors: {
|
|
613
799
|
player: fromPromise<PlayerOutput, PlayerInput>(async () => {
|
|
614
800
|
throw new Error('player actor must be provided by the runner');
|
|
615
801
|
}),
|
|
616
|
-
playbook: fromPromise<
|
|
802
|
+
playbook: fromPromise<PlaybookOutput, PlaybookInput>(async () => {
|
|
617
803
|
throw new Error('playbook actor must be provided by the runner');
|
|
618
804
|
}),
|
|
619
805
|
},
|
|
620
|
-
guards: {
|
|
621
|
-
isDirectCommit,
|
|
622
|
-
isIrCommit,
|
|
623
|
-
isMoreTasks,
|
|
624
|
-
isFinalTask,
|
|
625
|
-
needsBossReply,
|
|
626
|
-
reviewApprovedDirect: reviewApprovedFor('direct'),
|
|
627
|
-
reviewApprovedIrCreated: reviewApprovedFor('ir-created'),
|
|
628
|
-
reviewApprovedMoreTasks: reviewApprovedFor('ir-task-more'),
|
|
629
|
-
reviewApprovedFinalTask: reviewApprovedFor('ir-task-final'),
|
|
630
|
-
authoredReviewFailure,
|
|
631
|
-
emptyBossReply: ({ event }) =>
|
|
632
|
-
event.type === 'BOSS_REPLY' && event.answer.trim().length === 0,
|
|
633
|
-
resumesFirstPhase: ({ context, event }) =>
|
|
634
|
-
event.type === 'BOSS_REPLY' &&
|
|
635
|
-
event.answer.trim().length > 0 &&
|
|
636
|
-
context.pendingBossQuestion?.resumeStateId === 'runFirstPhase' &&
|
|
637
|
-
(event.questionId === undefined || event.questionId === 'runFirstPhase'),
|
|
638
|
-
resumesIrTask: ({ context, event }) =>
|
|
639
|
-
event.type === 'BOSS_REPLY' &&
|
|
640
|
-
event.answer.trim().length > 0 &&
|
|
641
|
-
context.pendingBossQuestion?.resumeStateId === 'runIrTask' &&
|
|
642
|
-
(event.questionId === undefined || event.questionId === 'runIrTask'),
|
|
643
|
-
},
|
|
644
806
|
actions: {
|
|
645
807
|
'playbook.acceptedOutcome': (
|
|
646
808
|
_args,
|
|
@@ -650,475 +812,382 @@ const machineSetup = setup({
|
|
|
650
812
|
readonly acceptedOutcome: string;
|
|
651
813
|
},
|
|
652
814
|
) => undefined,
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
rememberIrCommit: assign(({ event }) => ({
|
|
679
|
-
coderOutput: outputString(event, 'coderOutput'),
|
|
680
|
-
latestCommit: outputString(event, 'latestCommit'),
|
|
681
|
-
irNumber: outputString(event, 'irNumber'),
|
|
682
|
-
irTask: undefined,
|
|
683
|
-
phase: 'ir-created' as const,
|
|
684
|
-
pendingBossQuestion: undefined,
|
|
685
|
-
bossReply: undefined,
|
|
686
|
-
})),
|
|
687
|
-
rememberMoreTasks: assign(({ event }) => ({
|
|
688
|
-
coderOutput: outputString(event, 'coderOutput'),
|
|
689
|
-
latestCommit: outputString(event, 'latestCommit'),
|
|
690
|
-
irNumber: outputString(event, 'irNumber'),
|
|
691
|
-
irTask: outputString(event, 'irTask'),
|
|
692
|
-
phase: 'ir-task-more' as const,
|
|
693
|
-
pendingBossQuestion: undefined,
|
|
694
|
-
bossReply: undefined,
|
|
695
|
-
})),
|
|
696
|
-
rememberFinalTask: assign(({ event }) => ({
|
|
697
|
-
coderOutput: outputString(event, 'coderOutput'),
|
|
698
|
-
latestCommit: outputString(event, 'latestCommit'),
|
|
699
|
-
irNumber: outputString(event, 'irNumber'),
|
|
700
|
-
irTask: outputString(event, 'irTask'),
|
|
701
|
-
phase: 'ir-task-final' as const,
|
|
815
|
+
startCode: assign(({ context, event }) =>
|
|
816
|
+
event.type === 'START_CODE'
|
|
817
|
+
? {
|
|
818
|
+
callerInput: event.callerInput,
|
|
819
|
+
runResults: event.runResults ?? context.runResults,
|
|
820
|
+
codeCommit: undefined,
|
|
821
|
+
coderOutput: undefined,
|
|
822
|
+
irNumber: undefined,
|
|
823
|
+
irTask: undefined,
|
|
824
|
+
phaseOutcome: undefined,
|
|
825
|
+
evaluatedRevision: undefined,
|
|
826
|
+
reviewError: undefined,
|
|
827
|
+
reviewEvidence: undefined,
|
|
828
|
+
lastError: undefined,
|
|
829
|
+
pendingBossQuestion: undefined,
|
|
830
|
+
bossReply: undefined,
|
|
831
|
+
}
|
|
832
|
+
: {},
|
|
833
|
+
),
|
|
834
|
+
resetForInterrupt: assign({
|
|
835
|
+
phaseOutcome: undefined,
|
|
836
|
+
evaluatedRevision: undefined,
|
|
837
|
+
reviewError: undefined,
|
|
838
|
+
reviewEvidence: undefined,
|
|
839
|
+
lastError: undefined,
|
|
702
840
|
pendingBossQuestion: undefined,
|
|
703
841
|
bossReply: undefined,
|
|
704
|
-
})
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
const stateId =
|
|
709
|
-
context.irNumber === undefined ? 'runFirstPhase' : 'runIrTask';
|
|
710
|
-
if (question === undefined || output?.guard !== 'needsBossReply') {
|
|
711
|
-
return { lastError: new Error('Coder question output was malformed') };
|
|
712
|
-
}
|
|
842
|
+
}),
|
|
843
|
+
rememberPhaseCommit: assign(({ event }) => {
|
|
844
|
+
const output = playerOutputOf(event);
|
|
845
|
+
if (output === undefined || output.guard === 'needsBossReply') return {};
|
|
713
846
|
return {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
847
|
+
codeCommit: output.latestCommit,
|
|
848
|
+
coderOutput: output.coderOutput,
|
|
849
|
+
phaseOutcome: output.guard,
|
|
850
|
+
irNumber: output.guard === 'directCommit' ? undefined : output.irNumber,
|
|
851
|
+
irTask:
|
|
852
|
+
output.guard === 'moreTasks' || output.guard === 'finalTask'
|
|
853
|
+
? output.irTask
|
|
854
|
+
: undefined,
|
|
855
|
+
lastError: undefined,
|
|
722
856
|
};
|
|
723
857
|
}),
|
|
858
|
+
setPendingBossQuestion: assign(
|
|
859
|
+
({ event }, params: { readonly resumeStateId: ResumableStateId }) => {
|
|
860
|
+
const output = playerOutputOf(event);
|
|
861
|
+
if (output?.guard !== 'needsBossReply') return {};
|
|
862
|
+
const pendingBossQuestion: PendingBossQuestion = {
|
|
863
|
+
questionId: params.resumeStateId,
|
|
864
|
+
resumeStateId: params.resumeStateId,
|
|
865
|
+
sourceItem: RESUME_METADATA[params.resumeStateId].sourceItem,
|
|
866
|
+
asker: { kind: 'role', roleId: 'coder' },
|
|
867
|
+
question: output.question,
|
|
868
|
+
};
|
|
869
|
+
return { pendingBossQuestion, bossReply: undefined, lastError: undefined };
|
|
870
|
+
},
|
|
871
|
+
),
|
|
724
872
|
rememberBossReply: assign(({ event }) =>
|
|
725
873
|
event.type === 'BOSS_REPLY' ? { bossReply: event.answer } : {},
|
|
726
874
|
),
|
|
727
|
-
|
|
728
|
-
lastError: () => new Error('BOSS_REPLY received an empty answer'),
|
|
875
|
+
clearBossReplyContext: assign({
|
|
729
876
|
pendingBossQuestion: undefined,
|
|
730
877
|
bossReply: undefined,
|
|
731
878
|
}),
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
879
|
+
rememberReviewPass: assign(({ event }) => {
|
|
880
|
+
const pass = reviewPassOf(event);
|
|
881
|
+
return pass === undefined
|
|
882
|
+
? {}
|
|
883
|
+
: { evaluatedRevision: pass.evaluatedRevision, lastError: undefined };
|
|
884
|
+
}),
|
|
885
|
+
rememberReviewNotPassed: assign(({ event }) => {
|
|
886
|
+
const output = doneOutputOf(event);
|
|
887
|
+
const reviewEvidence: CompletedReviewResult = {
|
|
888
|
+
playbookId: REVIEW_PLAYBOOK_ID,
|
|
889
|
+
status: 'ok',
|
|
890
|
+
...(isJsonValue(output) ? { output } : {}),
|
|
739
891
|
};
|
|
892
|
+
return { reviewEvidence, reviewError: REVIEW_NOT_PASSED, lastError: undefined };
|
|
893
|
+
}),
|
|
894
|
+
rememberAuthoredReviewFailure: assign(({ event }) => {
|
|
895
|
+
const failure = authoredReviewFailureOf(event);
|
|
896
|
+
return failure === undefined
|
|
897
|
+
? {}
|
|
898
|
+
: {
|
|
899
|
+
reviewEvidence: failure.evidence,
|
|
900
|
+
reviewError: failure.error,
|
|
901
|
+
lastError: undefined,
|
|
902
|
+
};
|
|
740
903
|
}),
|
|
741
|
-
completeWithReviewFailure: assign(({ event }) => ({
|
|
742
|
-
completion: 'review-failed' as const,
|
|
743
|
-
reviewError: authoredReviewError(event),
|
|
744
|
-
})),
|
|
745
|
-
completeWithInvalidReviewOutput: assign(({ event }) => ({
|
|
746
|
-
completion: 'review-failed' as const,
|
|
747
|
-
reviewError: {
|
|
748
|
-
name: 'ReviewContractError',
|
|
749
|
-
message:
|
|
750
|
-
`REVIEW returned an invalid approval result: ` +
|
|
751
|
-
JSON.stringify(reviewOutput(event)),
|
|
752
|
-
},
|
|
753
|
-
})),
|
|
754
904
|
rememberActorError: assign(({ event }) => ({
|
|
755
|
-
lastError: (event
|
|
756
|
-
})),
|
|
757
|
-
rememberMalformedPlayerOutput: assign(({ context }) => ({
|
|
758
|
-
lastError: new Error(
|
|
759
|
-
`Coder result for ${context.irNumber === undefined ? 'CODE-1' : 'CODE-3'} ` +
|
|
760
|
-
'did not match a declared outcome.',
|
|
761
|
-
),
|
|
905
|
+
lastError: errorRecord(actorErrorOf(event)),
|
|
762
906
|
})),
|
|
907
|
+
rememberMalformedPlayerOutput: assign(({ event }) => {
|
|
908
|
+
const output = doneOutputOf(event);
|
|
909
|
+
return {
|
|
910
|
+
lastError: {
|
|
911
|
+
name: 'MalformedPlayerOutput',
|
|
912
|
+
message:
|
|
913
|
+
isRecord(output) && output.guard === 'needsBossReply'
|
|
914
|
+
? 'Coder output declared needsBossReply without a question.'
|
|
915
|
+
: 'Coder output did not match an outcome declared for the working state.',
|
|
916
|
+
},
|
|
917
|
+
};
|
|
918
|
+
}),
|
|
919
|
+
rememberMalformedBossReply: assign({
|
|
920
|
+
lastError: {
|
|
921
|
+
name: 'MalformedBossReply',
|
|
922
|
+
message: 'BOSS_REPLY carried an empty answer.',
|
|
923
|
+
},
|
|
924
|
+
}),
|
|
763
925
|
},
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
926
|
+
guards: {
|
|
927
|
+
needsBossReplyWithQuestion,
|
|
928
|
+
acceptDirectCommit: acceptsOutcome('directCommit'),
|
|
929
|
+
acceptIrCommit: acceptsOutcome('irCommit'),
|
|
930
|
+
acceptMoreTasks: acceptsOutcome('moreTasks'),
|
|
931
|
+
acceptFinalTask: acceptsOutcome('finalTask'),
|
|
932
|
+
reviewPassedDirect: reviewPassedAfter('directCommit'),
|
|
933
|
+
reviewPassedNewIr: reviewPassedAfter('irCommit'),
|
|
934
|
+
reviewPassedNonfinalTask: reviewPassedAfter('moreTasks'),
|
|
935
|
+
reviewPassedFinalTask: reviewPassedAfter('finalTask'),
|
|
936
|
+
authoredReviewFailure,
|
|
937
|
+
validStartCode,
|
|
938
|
+
emptyBossReply,
|
|
939
|
+
},
|
|
940
|
+
}).createMachine({
|
|
767
941
|
id: 'code',
|
|
768
942
|
initial: 'ready',
|
|
769
|
-
context: ({ input }) => ({
|
|
770
|
-
runResults: typeof input
|
|
943
|
+
context: ({ input }): CodeContext => ({
|
|
944
|
+
runResults: typeof input?.runResults === 'string' ? input.runResults : '',
|
|
771
945
|
}),
|
|
772
|
-
output: ({ context }):
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
946
|
+
output: ({ context }): CodeOutput => {
|
|
947
|
+
if (!isNonEmptyString(context.codeCommit)) {
|
|
948
|
+
throw new Error(
|
|
949
|
+
'CODE reached a final state without its last code-owned commit',
|
|
950
|
+
);
|
|
776
951
|
}
|
|
777
|
-
if (context.
|
|
952
|
+
if (context.reviewError !== undefined) {
|
|
778
953
|
return {
|
|
779
954
|
status: 'review-failed',
|
|
780
|
-
lastCodeCommit,
|
|
781
|
-
error:
|
|
782
|
-
context.reviewError ?? {
|
|
783
|
-
name: 'Error',
|
|
784
|
-
message: 'REVIEW failed without error detail.',
|
|
785
|
-
},
|
|
955
|
+
lastCodeCommit: context.codeCommit,
|
|
956
|
+
error: context.reviewError,
|
|
786
957
|
};
|
|
787
958
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
throw new Error(
|
|
791
|
-
'CODE completed without an evaluated repository revision',
|
|
792
|
-
);
|
|
959
|
+
if (!isNonEmptyString(context.evaluatedRevision)) {
|
|
960
|
+
throw new Error('CODE completed without the final evaluated revision');
|
|
793
961
|
}
|
|
794
962
|
return {
|
|
795
963
|
status: 'complete',
|
|
796
|
-
lastCodeCommit,
|
|
797
|
-
finalEvaluatedRevision,
|
|
964
|
+
lastCodeCommit: context.codeCommit,
|
|
965
|
+
finalEvaluatedRevision: context.evaluatedRevision,
|
|
798
966
|
allReviewsPassed: true,
|
|
799
967
|
};
|
|
800
968
|
},
|
|
969
|
+
on: {
|
|
970
|
+
BOSS_INTERRUPT: bossInterrupts(['firstPhase', 'irTaskPhase'] as const),
|
|
971
|
+
},
|
|
801
972
|
states: {
|
|
802
973
|
ready: {
|
|
803
974
|
id: 'ready',
|
|
975
|
+
tags: 'playbook.parked',
|
|
804
976
|
description: STATE_DESCRIPTIONS.ready,
|
|
805
|
-
meta:
|
|
806
|
-
tags: ['playbook.parked'],
|
|
977
|
+
meta: meta('ready'),
|
|
807
978
|
on: {
|
|
808
|
-
START_CODE: {
|
|
979
|
+
START_CODE: {
|
|
980
|
+
guard: 'validStartCode',
|
|
981
|
+
target: 'firstPhase',
|
|
982
|
+
actions: 'startCode',
|
|
983
|
+
},
|
|
809
984
|
},
|
|
810
985
|
},
|
|
811
|
-
|
|
812
|
-
id: '
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
986
|
+
firstPhase: {
|
|
987
|
+
id: 'firstPhase',
|
|
988
|
+
tags: 'playbook.busy',
|
|
989
|
+
description: STATE_DESCRIPTIONS.firstPhase,
|
|
990
|
+
meta: meta('firstPhase', 'coder'),
|
|
816
991
|
invoke: {
|
|
817
992
|
src: 'player',
|
|
818
|
-
input: ({ context }):
|
|
819
|
-
stateId: '
|
|
993
|
+
input: ({ context }): FirstPhasePlayerInput => ({
|
|
994
|
+
stateId: 'firstPhase',
|
|
820
995
|
role: 'coder',
|
|
821
996
|
sourceItem: 'CODE-1',
|
|
822
997
|
prompt: FIRST_PHASE_PROMPT,
|
|
823
|
-
result:
|
|
824
|
-
|
|
998
|
+
result: FIRST_PHASE_RESULT,
|
|
999
|
+
...(context.callerInput !== undefined
|
|
1000
|
+
? { callerInput: context.callerInput }
|
|
1001
|
+
: {}),
|
|
825
1002
|
runResults: context.runResults,
|
|
826
|
-
...(context
|
|
827
|
-
? {}
|
|
828
|
-
: { pendingBossQuestion: context.pendingBossQuestion }),
|
|
829
|
-
...(context.bossReply === undefined
|
|
830
|
-
? {}
|
|
831
|
-
: { bossReply: context.bossReply }),
|
|
1003
|
+
...bossReplyFields(context),
|
|
832
1004
|
}),
|
|
833
1005
|
onDone: [
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
},
|
|
861
|
-
'rememberIrCommit',
|
|
862
|
-
],
|
|
863
|
-
},
|
|
864
|
-
{
|
|
865
|
-
guard: 'isMoreTasks',
|
|
866
|
-
target: 'reviewIrTask',
|
|
867
|
-
actions: [
|
|
868
|
-
{
|
|
869
|
-
type: 'playbook.acceptedOutcome',
|
|
870
|
-
params: {
|
|
871
|
-
source: 'runFirstPhase',
|
|
872
|
-
target: 'reviewIrTask',
|
|
873
|
-
acceptedOutcome: 'moreTasks',
|
|
874
|
-
},
|
|
875
|
-
},
|
|
876
|
-
'rememberMoreTasks',
|
|
877
|
-
],
|
|
878
|
-
},
|
|
879
|
-
{
|
|
880
|
-
guard: 'isFinalTask',
|
|
881
|
-
target: 'reviewIrTask',
|
|
882
|
-
actions: [
|
|
883
|
-
{
|
|
884
|
-
type: 'playbook.acceptedOutcome',
|
|
885
|
-
params: {
|
|
886
|
-
source: 'runFirstPhase',
|
|
887
|
-
target: 'reviewIrTask',
|
|
888
|
-
acceptedOutcome: 'finalTask',
|
|
889
|
-
},
|
|
890
|
-
},
|
|
891
|
-
'rememberFinalTask',
|
|
892
|
-
],
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
guard: 'needsBossReply',
|
|
896
|
-
target: 'awaitBossReply',
|
|
897
|
-
actions: [
|
|
898
|
-
{
|
|
899
|
-
type: 'playbook.acceptedOutcome',
|
|
900
|
-
params: {
|
|
901
|
-
source: 'runFirstPhase',
|
|
902
|
-
target: 'awaitBossReply',
|
|
903
|
-
acceptedOutcome: 'needsBossReply',
|
|
904
|
-
},
|
|
905
|
-
},
|
|
906
|
-
'rememberPendingQuestion',
|
|
907
|
-
],
|
|
908
|
-
},
|
|
909
|
-
{
|
|
910
|
-
target: 'failed',
|
|
911
|
-
actions: 'rememberMalformedPlayerOutput',
|
|
912
|
-
},
|
|
1006
|
+
suspendArm('firstPhase'),
|
|
1007
|
+
acceptPhaseArm(
|
|
1008
|
+
'firstPhase',
|
|
1009
|
+
'directCommit',
|
|
1010
|
+
'acceptDirectCommit',
|
|
1011
|
+
'reviewNewIntentPhase',
|
|
1012
|
+
),
|
|
1013
|
+
acceptPhaseArm(
|
|
1014
|
+
'firstPhase',
|
|
1015
|
+
'irCommit',
|
|
1016
|
+
'acceptIrCommit',
|
|
1017
|
+
'reviewNewIntentPhase',
|
|
1018
|
+
),
|
|
1019
|
+
acceptPhaseArm(
|
|
1020
|
+
'firstPhase',
|
|
1021
|
+
'moreTasks',
|
|
1022
|
+
'acceptMoreTasks',
|
|
1023
|
+
'reviewIrTaskPhase',
|
|
1024
|
+
),
|
|
1025
|
+
acceptPhaseArm(
|
|
1026
|
+
'firstPhase',
|
|
1027
|
+
'finalTask',
|
|
1028
|
+
'acceptFinalTask',
|
|
1029
|
+
'reviewIrTaskPhase',
|
|
1030
|
+
),
|
|
1031
|
+
malformedPlayerOutputArm,
|
|
913
1032
|
],
|
|
914
|
-
onError:
|
|
1033
|
+
onError: playerOnError,
|
|
915
1034
|
},
|
|
916
1035
|
},
|
|
917
|
-
|
|
918
|
-
id: '
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1036
|
+
reviewNewIntentPhase: {
|
|
1037
|
+
id: 'reviewNewIntentPhase',
|
|
1038
|
+
tags: 'playbook.suspended',
|
|
1039
|
+
description: STATE_DESCRIPTIONS.reviewNewIntentPhase,
|
|
1040
|
+
meta: meta('reviewNewIntentPhase'),
|
|
922
1041
|
invoke: {
|
|
923
1042
|
src: 'playbook',
|
|
924
1043
|
input: ({ context }): PlaybookInput => ({
|
|
925
|
-
stateId: '
|
|
1044
|
+
stateId: 'reviewNewIntentPhase',
|
|
926
1045
|
sourceItem: 'CODE-2',
|
|
927
|
-
playbookId:
|
|
928
|
-
text:
|
|
1046
|
+
playbookId: REVIEW_PLAYBOOK_ID,
|
|
1047
|
+
text: composeReviewText(REVIEW_NEW_INTENT_PHASE_TEMPLATE, {
|
|
1048
|
+
'<caller-input>': context.callerInput,
|
|
1049
|
+
'<code-commit>': context.codeCommit,
|
|
1050
|
+
'<coder-output>': context.coderOutput,
|
|
1051
|
+
}),
|
|
929
1052
|
}),
|
|
930
1053
|
onDone: [
|
|
931
1054
|
{
|
|
932
|
-
guard: '
|
|
1055
|
+
guard: 'reviewPassedDirect',
|
|
933
1056
|
target: 'done',
|
|
934
|
-
actions: '
|
|
935
|
-
},
|
|
936
|
-
{
|
|
937
|
-
guard: 'reviewApprovedIrCreated',
|
|
938
|
-
target: 'runIrTask',
|
|
939
|
-
},
|
|
940
|
-
{
|
|
941
|
-
target: 'reportedReviewFailure',
|
|
942
|
-
actions: 'completeWithInvalidReviewOutput',
|
|
1057
|
+
actions: 'rememberReviewPass',
|
|
943
1058
|
},
|
|
944
|
-
],
|
|
945
|
-
onError: [
|
|
946
1059
|
{
|
|
947
|
-
guard: '
|
|
948
|
-
target: '
|
|
949
|
-
actions: '
|
|
1060
|
+
guard: 'reviewPassedNewIr',
|
|
1061
|
+
target: 'irTaskPhase',
|
|
1062
|
+
actions: 'rememberReviewPass',
|
|
950
1063
|
},
|
|
951
|
-
{ target: '
|
|
1064
|
+
{ target: 'reviewFailed', actions: 'rememberReviewNotPassed' },
|
|
952
1065
|
],
|
|
1066
|
+
onError: reviewOnError,
|
|
953
1067
|
},
|
|
954
1068
|
},
|
|
955
|
-
|
|
956
|
-
id: '
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1069
|
+
irTaskPhase: {
|
|
1070
|
+
id: 'irTaskPhase',
|
|
1071
|
+
tags: 'playbook.busy',
|
|
1072
|
+
description: STATE_DESCRIPTIONS.irTaskPhase,
|
|
1073
|
+
meta: meta('irTaskPhase', 'coder'),
|
|
960
1074
|
invoke: {
|
|
961
1075
|
src: 'player',
|
|
962
|
-
input: ({ context }):
|
|
963
|
-
stateId: '
|
|
1076
|
+
input: ({ context }): IrTaskPhasePlayerInput => ({
|
|
1077
|
+
stateId: 'irTaskPhase',
|
|
964
1078
|
role: 'coder',
|
|
965
1079
|
sourceItem: 'CODE-3',
|
|
966
|
-
prompt:
|
|
967
|
-
result:
|
|
968
|
-
|
|
1080
|
+
prompt: IR_TASK_PHASE_PROMPT,
|
|
1081
|
+
result: IR_TASK_PHASE_RESULT,
|
|
1082
|
+
...(context.callerInput !== undefined
|
|
1083
|
+
? { callerInput: context.callerInput }
|
|
1084
|
+
: {}),
|
|
1085
|
+
...(context.irNumber !== undefined
|
|
1086
|
+
? { irNumber: context.irNumber }
|
|
1087
|
+
: {}),
|
|
969
1088
|
runResults: context.runResults,
|
|
970
|
-
...(context
|
|
971
|
-
? {}
|
|
972
|
-
: { irNumber: context.irNumber }),
|
|
973
|
-
...(context.pendingBossQuestion === undefined
|
|
974
|
-
? {}
|
|
975
|
-
: { pendingBossQuestion: context.pendingBossQuestion }),
|
|
976
|
-
...(context.bossReply === undefined
|
|
977
|
-
? {}
|
|
978
|
-
: { bossReply: context.bossReply }),
|
|
1089
|
+
...bossReplyFields(context),
|
|
979
1090
|
}),
|
|
980
1091
|
onDone: [
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
},
|
|
996
|
-
{
|
|
997
|
-
guard: 'isFinalTask',
|
|
998
|
-
target: 'reviewIrTask',
|
|
999
|
-
actions: [
|
|
1000
|
-
{
|
|
1001
|
-
type: 'playbook.acceptedOutcome',
|
|
1002
|
-
params: {
|
|
1003
|
-
source: 'runIrTask',
|
|
1004
|
-
target: 'reviewIrTask',
|
|
1005
|
-
acceptedOutcome: 'finalTask',
|
|
1006
|
-
},
|
|
1007
|
-
},
|
|
1008
|
-
'rememberFinalTask',
|
|
1009
|
-
],
|
|
1010
|
-
},
|
|
1011
|
-
{
|
|
1012
|
-
guard: 'needsBossReply',
|
|
1013
|
-
target: 'awaitBossReply',
|
|
1014
|
-
actions: [
|
|
1015
|
-
{
|
|
1016
|
-
type: 'playbook.acceptedOutcome',
|
|
1017
|
-
params: {
|
|
1018
|
-
source: 'runIrTask',
|
|
1019
|
-
target: 'awaitBossReply',
|
|
1020
|
-
acceptedOutcome: 'needsBossReply',
|
|
1021
|
-
},
|
|
1022
|
-
},
|
|
1023
|
-
'rememberPendingQuestion',
|
|
1024
|
-
],
|
|
1025
|
-
},
|
|
1026
|
-
{
|
|
1027
|
-
target: 'failed',
|
|
1028
|
-
actions: 'rememberMalformedPlayerOutput',
|
|
1029
|
-
},
|
|
1092
|
+
suspendArm('irTaskPhase'),
|
|
1093
|
+
acceptPhaseArm(
|
|
1094
|
+
'irTaskPhase',
|
|
1095
|
+
'moreTasks',
|
|
1096
|
+
'acceptMoreTasks',
|
|
1097
|
+
'reviewIrTaskPhase',
|
|
1098
|
+
),
|
|
1099
|
+
acceptPhaseArm(
|
|
1100
|
+
'irTaskPhase',
|
|
1101
|
+
'finalTask',
|
|
1102
|
+
'acceptFinalTask',
|
|
1103
|
+
'reviewIrTaskPhase',
|
|
1104
|
+
),
|
|
1105
|
+
malformedPlayerOutputArm,
|
|
1030
1106
|
],
|
|
1031
|
-
onError:
|
|
1107
|
+
onError: playerOnError,
|
|
1032
1108
|
},
|
|
1033
1109
|
},
|
|
1034
|
-
|
|
1035
|
-
id: '
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1110
|
+
reviewIrTaskPhase: {
|
|
1111
|
+
id: 'reviewIrTaskPhase',
|
|
1112
|
+
tags: 'playbook.suspended',
|
|
1113
|
+
description: STATE_DESCRIPTIONS.reviewIrTaskPhase,
|
|
1114
|
+
meta: meta('reviewIrTaskPhase'),
|
|
1039
1115
|
invoke: {
|
|
1040
1116
|
src: 'playbook',
|
|
1041
1117
|
input: ({ context }): PlaybookInput => ({
|
|
1042
|
-
stateId: '
|
|
1118
|
+
stateId: 'reviewIrTaskPhase',
|
|
1043
1119
|
sourceItem: 'CODE-4',
|
|
1044
|
-
playbookId:
|
|
1045
|
-
text:
|
|
1120
|
+
playbookId: REVIEW_PLAYBOOK_ID,
|
|
1121
|
+
text: composeReviewText(REVIEW_IR_TASK_PHASE_TEMPLATE, {
|
|
1122
|
+
'<caller-input>': context.callerInput,
|
|
1123
|
+
'<code-commit>': context.codeCommit,
|
|
1124
|
+
'<coder-output>': context.coderOutput,
|
|
1125
|
+
'<ir-task>': context.irTask,
|
|
1126
|
+
}),
|
|
1046
1127
|
}),
|
|
1047
1128
|
onDone: [
|
|
1048
1129
|
{
|
|
1049
|
-
guard: '
|
|
1050
|
-
target: '
|
|
1130
|
+
guard: 'reviewPassedNonfinalTask',
|
|
1131
|
+
target: 'irTaskPhase',
|
|
1132
|
+
actions: 'rememberReviewPass',
|
|
1051
1133
|
},
|
|
1052
1134
|
{
|
|
1053
|
-
guard: '
|
|
1135
|
+
guard: 'reviewPassedFinalTask',
|
|
1054
1136
|
target: 'done',
|
|
1055
|
-
actions: '
|
|
1056
|
-
},
|
|
1057
|
-
{
|
|
1058
|
-
target: 'reportedReviewFailure',
|
|
1059
|
-
actions: 'completeWithInvalidReviewOutput',
|
|
1060
|
-
},
|
|
1061
|
-
],
|
|
1062
|
-
onError: [
|
|
1063
|
-
{
|
|
1064
|
-
guard: 'authoredReviewFailure',
|
|
1065
|
-
target: 'reportedReviewFailure',
|
|
1066
|
-
actions: 'completeWithReviewFailure',
|
|
1137
|
+
actions: 'rememberReviewPass',
|
|
1067
1138
|
},
|
|
1068
|
-
{ target: '
|
|
1139
|
+
{ target: 'reviewFailed', actions: 'rememberReviewNotPassed' },
|
|
1069
1140
|
],
|
|
1141
|
+
onError: reviewOnError,
|
|
1070
1142
|
},
|
|
1071
1143
|
},
|
|
1072
1144
|
awaitBossReply: {
|
|
1073
1145
|
id: 'awaitBossReply',
|
|
1146
|
+
tags: 'playbook.parked',
|
|
1074
1147
|
description: STATE_DESCRIPTIONS.awaitBossReply,
|
|
1075
|
-
meta:
|
|
1076
|
-
tags: ['playbook.parked'],
|
|
1148
|
+
meta: meta('awaitBossReply'),
|
|
1077
1149
|
on: {
|
|
1078
1150
|
BOSS_REPLY: [
|
|
1079
1151
|
{
|
|
1080
1152
|
guard: 'emptyBossReply',
|
|
1081
|
-
target: '
|
|
1082
|
-
actions: '
|
|
1083
|
-
},
|
|
1084
|
-
{
|
|
1085
|
-
guard: 'resumesFirstPhase',
|
|
1086
|
-
target: '#runFirstPhase',
|
|
1087
|
-
reenter: true,
|
|
1088
|
-
actions: 'rememberBossReply',
|
|
1089
|
-
},
|
|
1090
|
-
{
|
|
1091
|
-
guard: 'resumesIrTask',
|
|
1092
|
-
target: '#runIrTask',
|
|
1093
|
-
reenter: true,
|
|
1094
|
-
actions: 'rememberBossReply',
|
|
1153
|
+
target: 'failed',
|
|
1154
|
+
actions: ['rememberMalformedBossReply', 'clearBossReplyContext'],
|
|
1095
1155
|
},
|
|
1156
|
+
...resumableStates(['firstPhase', 'irTaskPhase'] as const),
|
|
1096
1157
|
],
|
|
1097
|
-
START_CODE: {
|
|
1158
|
+
START_CODE: {
|
|
1159
|
+
guard: 'validStartCode',
|
|
1160
|
+
target: 'firstPhase',
|
|
1161
|
+
actions: 'startCode',
|
|
1162
|
+
},
|
|
1098
1163
|
},
|
|
1099
1164
|
},
|
|
1100
1165
|
failed: {
|
|
1101
1166
|
id: 'failed',
|
|
1167
|
+
tags: 'playbook.parked',
|
|
1102
1168
|
description: STATE_DESCRIPTIONS.failed,
|
|
1103
|
-
meta:
|
|
1104
|
-
tags: ['playbook.parked'],
|
|
1169
|
+
meta: meta('failed'),
|
|
1105
1170
|
on: {
|
|
1106
|
-
START_CODE: {
|
|
1171
|
+
START_CODE: {
|
|
1172
|
+
guard: 'validStartCode',
|
|
1173
|
+
target: 'firstPhase',
|
|
1174
|
+
actions: 'startCode',
|
|
1175
|
+
},
|
|
1107
1176
|
},
|
|
1108
1177
|
},
|
|
1109
|
-
|
|
1110
|
-
id: '
|
|
1111
|
-
description: STATE_DESCRIPTIONS.reportedReviewFailure,
|
|
1112
|
-
meta: terminalMeta('reportedReviewFailure', 'failure'),
|
|
1178
|
+
reviewFailed: {
|
|
1179
|
+
id: 'reviewFailed',
|
|
1113
1180
|
type: 'final',
|
|
1181
|
+
description: STATE_DESCRIPTIONS.reviewFailed,
|
|
1182
|
+
meta: finalMeta('reviewFailed', 'failure'),
|
|
1114
1183
|
},
|
|
1115
1184
|
done: {
|
|
1116
1185
|
id: 'done',
|
|
1117
|
-
description: STATE_DESCRIPTIONS.done,
|
|
1118
|
-
meta: terminalMeta('done', 'success'),
|
|
1119
1186
|
type: 'final',
|
|
1187
|
+
description: STATE_DESCRIPTIONS.done,
|
|
1188
|
+
meta: finalMeta('done', 'success'),
|
|
1120
1189
|
},
|
|
1121
1190
|
},
|
|
1122
1191
|
});
|
|
1123
1192
|
|
|
1124
|
-
export default
|
|
1193
|
+
export default codeMachine;
|