@sublang/playbook 15.0.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/README.md +1 -1
- package/docs/embedding.md +9 -7
- package/package.json +3 -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/bin/repository-effects.js +54 -13
- 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 -67
- package/reference/sdlc/decide.playbook/decide.playbook.js +155 -3570
- package/reference/sdlc/decide.playbook/decide.playbook.ts +186 -5061
- 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 +191 -0
- package/slc/prefix.md +203 -0
- package/slc/slc.pin-inputs.json +11 -1
- package/slc/text2gears.md +71 -11
- package/src/xstate-playbook-runtime.d.ts +82 -9
- package/src/xstate-playbook-runtime.js +1224 -283
- package/src/xstate-playbook-runtime.ts +1671 -338
- package/src/xstate-runtime.d.ts +3 -1
- package/src/xstate-runtime.js +9 -2
- package/src/xstate-runtime.ts +12 -2
|
@@ -1,20 +1,19 @@
|
|
|
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
|
import { assign, fromPromise, setup } from 'xstate';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
import { validatePlaybookCallResult } from '@sublang/playbook/xstate-runtime';
|
|
9
|
+
/** One role-id array per parallel group; CODE declares no parallel group. */
|
|
10
|
+
export const concurrentRoleSets = [];
|
|
11
|
+
/** The literal nested-call target shared by both call states and guards. */
|
|
12
|
+
const REVIEW_PLAYBOOK_ID = 'review';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Prompts and result contracts (verbatim from code.gears.md)
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
14
16
|
const FIRST_PHASE_PROMPT = [
|
|
15
|
-
'> Original request: <caller-input>',
|
|
16
|
-
'> Run results: <run-results>',
|
|
17
|
-
'',
|
|
18
17
|
'First determine whether the coding request starts a new coding intent or continues an existing IR with unfinished work.',
|
|
19
18
|
'If the request may continue an existing IR but does not identify it unambiguously, ask Boss before changing files.',
|
|
20
19
|
'',
|
|
@@ -30,320 +29,424 @@ const FIRST_PHASE_PROMPT = [
|
|
|
30
29
|
'',
|
|
31
30
|
'Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.',
|
|
32
31
|
'',
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
'Keep to the original intent and follow what it asks.',
|
|
33
|
+
'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
|
|
34
|
+
"Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
|
|
35
|
+
'Make the commit message explain concisely what changed and why, including relevant verification.',
|
|
36
|
+
'Identify every new commit you make.',
|
|
37
|
+
'Credit every AI that contributed to this commit: Coder <coder-llm>.',
|
|
38
|
+
'',
|
|
36
39
|
'> Original request: <caller-input>',
|
|
37
|
-
'> IR number: <ir-number>',
|
|
38
40
|
'> Run results: <run-results>',
|
|
39
|
-
|
|
41
|
+
].join('\n');
|
|
42
|
+
const IR_TASK_PHASE_PROMPT = [
|
|
40
43
|
'Read the identified IR and implement exactly its next unfinished task, including corresponding tests or specs if any.',
|
|
41
44
|
'Do not implement a later task in this phase.',
|
|
42
45
|
"Mark the IR's progress and deliverables when relevant.",
|
|
43
46
|
'If the IR will be finished after this phase, double-check that all acceptance criteria are met.',
|
|
44
47
|
'',
|
|
45
|
-
|
|
48
|
+
'Keep to the original intent and follow what it asks.',
|
|
49
|
+
'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
|
|
50
|
+
"Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
|
|
51
|
+
'Make the commit message explain concisely what changed and why, including relevant verification.',
|
|
52
|
+
'Identify every new commit you make.',
|
|
53
|
+
'Credit every AI that contributed to this commit: Coder <coder-llm>.',
|
|
54
|
+
'',
|
|
55
|
+
'> Original request: <caller-input>',
|
|
56
|
+
'> IR number: <ir-number>',
|
|
57
|
+
'> Run results: <run-results>',
|
|
58
|
+
].join('\n');
|
|
59
|
+
const REVIEW_NEW_INTENT_PHASE_TEMPLATE = [
|
|
60
|
+
'> Original intent: <caller-input>',
|
|
61
|
+
'> Review scope: the commit <code-commit> from this coding phase and its resulting repository state.',
|
|
62
|
+
'> Coder output: <coder-output>',
|
|
46
63
|
].join('\n');
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
const REVIEW_IR_TASK_PHASE_TEMPLATE = [
|
|
65
|
+
'> Original intent: <caller-input>',
|
|
66
|
+
'> Review scope: the commit <code-commit> from this coding phase and its resulting repository state.',
|
|
67
|
+
'> Coder output: <coder-output>',
|
|
68
|
+
'',
|
|
69
|
+
'> Current IR task: <ir-task>',
|
|
70
|
+
].join('\n');
|
|
71
|
+
const NEEDS_BOSS_REPLY_DESCRIPTION = "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>`.";
|
|
72
|
+
const FIRST_PHASE_RESULT = {
|
|
73
|
+
directCommit: "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>`.",
|
|
74
|
+
irCommit: "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.",
|
|
75
|
+
moreTasks: "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.",
|
|
76
|
+
finalTask: "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.",
|
|
77
|
+
needsBossReply: NEEDS_BOSS_REPLY_DESCRIPTION,
|
|
54
78
|
};
|
|
55
|
-
const
|
|
56
|
-
moreTasks: "Coder
|
|
57
|
-
finalTask: "Coder
|
|
58
|
-
needsBossReply:
|
|
79
|
+
const IR_TASK_PHASE_RESULT = {
|
|
80
|
+
moreTasks: "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.",
|
|
81
|
+
finalTask: "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.",
|
|
82
|
+
needsBossReply: NEEDS_BOSS_REPLY_DESCRIPTION,
|
|
59
83
|
};
|
|
60
84
|
const STATE_DESCRIPTIONS = {
|
|
61
|
-
ready: 'Waiting for a coding
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
awaitBossReply:
|
|
67
|
-
failed: '
|
|
68
|
-
|
|
69
|
-
done: "
|
|
85
|
+
ready: 'Waiting for a coding request.',
|
|
86
|
+
firstPhase: 'Coder runs the first coding phase: a direct implementation, a new IR, or the next task of an existing IR.',
|
|
87
|
+
reviewNewIntentPhase: 'REVIEW examines the commit of the direct implementation or new-IR phase.',
|
|
88
|
+
irTaskPhase: "Coder implements the identified IR's next unfinished task.",
|
|
89
|
+
reviewIrTaskPhase: 'REVIEW examines the commit of the IR-task phase.',
|
|
90
|
+
awaitBossReply: "Waiting for Boss to answer the acting agent's question.",
|
|
91
|
+
failed: 'CODE parked after a control-plane failure and waits for Boss to restart or resume it.',
|
|
92
|
+
reviewFailed: 'CODE stopped because review did not pass a phase and reports the failure with its last code-owned commit.',
|
|
93
|
+
done: "CODE completed: every phase's review passed with no unsettled findings.",
|
|
70
94
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function isRecord(value) {
|
|
93
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
const prototype = Object.getPrototypeOf(value);
|
|
97
|
-
return prototype === Object.prototype || prototype === null;
|
|
98
|
-
}
|
|
99
|
-
function isNonEmptyString(value) {
|
|
100
|
-
return typeof value === 'string' && value.trim().length > 0;
|
|
101
|
-
}
|
|
102
|
-
function playerOutput(event) {
|
|
103
|
-
if (!isRecord(event))
|
|
95
|
+
const RESUME_METADATA = {
|
|
96
|
+
firstPhase: { sourceItem: 'CODE-1' },
|
|
97
|
+
irTaskPhase: { sourceItem: 'CODE-3' },
|
|
98
|
+
};
|
|
99
|
+
const REVIEW_NOT_PASSED = {
|
|
100
|
+
name: 'ReviewNotPassed',
|
|
101
|
+
message: 'review returned a terminal result that does not establish that the supplied scope was evaluated with no unsettled findings.',
|
|
102
|
+
};
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Structural helpers
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
107
|
+
const isNonEmptyString = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
108
|
+
const doneOutputOf = (event) => isRecord(event) ? event.output : undefined;
|
|
109
|
+
const actorErrorOf = (event) => isRecord(event) ? event.error : undefined;
|
|
110
|
+
/** Narrows a player done event to the declared player output contract. */
|
|
111
|
+
function playerOutputOf(event) {
|
|
112
|
+
const output = doneOutputOf(event);
|
|
113
|
+
if (!isRecord(output))
|
|
104
114
|
return undefined;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
function isReviewApprovedValue(value) {
|
|
148
|
-
if (!isRecord(value))
|
|
149
|
-
return false;
|
|
150
|
-
if (Reflect.ownKeys(value).some((key) => typeof key !== 'string' ||
|
|
151
|
-
(key !== 'noUnsettledFindings' && key !== 'evaluatedRevision'))) {
|
|
152
|
-
return false;
|
|
115
|
+
switch (output.guard) {
|
|
116
|
+
case 'directCommit':
|
|
117
|
+
return isNonEmptyString(output.coderOutput) &&
|
|
118
|
+
isNonEmptyString(output.latestCommit)
|
|
119
|
+
? {
|
|
120
|
+
guard: 'directCommit',
|
|
121
|
+
coderOutput: output.coderOutput,
|
|
122
|
+
latestCommit: output.latestCommit,
|
|
123
|
+
}
|
|
124
|
+
: undefined;
|
|
125
|
+
case 'irCommit':
|
|
126
|
+
return isNonEmptyString(output.coderOutput) &&
|
|
127
|
+
isNonEmptyString(output.latestCommit) &&
|
|
128
|
+
isNonEmptyString(output.irNumber)
|
|
129
|
+
? {
|
|
130
|
+
guard: 'irCommit',
|
|
131
|
+
coderOutput: output.coderOutput,
|
|
132
|
+
latestCommit: output.latestCommit,
|
|
133
|
+
irNumber: output.irNumber,
|
|
134
|
+
}
|
|
135
|
+
: undefined;
|
|
136
|
+
case 'moreTasks':
|
|
137
|
+
case 'finalTask':
|
|
138
|
+
return isNonEmptyString(output.coderOutput) &&
|
|
139
|
+
isNonEmptyString(output.latestCommit) &&
|
|
140
|
+
isNonEmptyString(output.irNumber) &&
|
|
141
|
+
isNonEmptyString(output.irTask)
|
|
142
|
+
? {
|
|
143
|
+
guard: output.guard,
|
|
144
|
+
coderOutput: output.coderOutput,
|
|
145
|
+
latestCommit: output.latestCommit,
|
|
146
|
+
irNumber: output.irNumber,
|
|
147
|
+
irTask: output.irTask,
|
|
148
|
+
}
|
|
149
|
+
: undefined;
|
|
150
|
+
case 'needsBossReply':
|
|
151
|
+
return isNonEmptyString(output.question)
|
|
152
|
+
? { guard: 'needsBossReply', question: output.question }
|
|
153
|
+
: undefined;
|
|
154
|
+
default:
|
|
155
|
+
return undefined;
|
|
153
156
|
}
|
|
154
|
-
return (value.noUnsettledFindings === true &&
|
|
155
|
-
isNonEmptyString(value.evaluatedRevision));
|
|
156
|
-
}
|
|
157
|
-
function reviewApprovedFor(phase) {
|
|
158
|
-
return ({ context, event }) => context.phase === phase && isReviewApprovedValue(reviewOutput(event));
|
|
159
157
|
}
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Strict JSON validation: null, booleans, finite numbers, strings, exact
|
|
160
|
+
* arrays, and plain records of enumerable own data properties. Cycle
|
|
161
|
+
* detection tracks only the active recursion path.
|
|
162
|
+
*/
|
|
163
|
+
function isJsonValue(value, path = new Set()) {
|
|
164
|
+
if (value === null)
|
|
162
165
|
return true;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Reflect.ownKeys(value).every((key) => {
|
|
174
|
-
if (key === 'length')
|
|
175
|
-
return true;
|
|
176
|
-
return (typeof key === 'string' &&
|
|
177
|
-
/^(0|[1-9][0-9]*)$/.test(key) &&
|
|
178
|
-
Number(key) < value.length);
|
|
179
|
-
}) &&
|
|
180
|
-
value.every((entry) => isNonEmptyString(entry)) &&
|
|
181
|
-
new Set(value).size === value.length);
|
|
182
|
-
}
|
|
183
|
-
function isPlaybookState(value) {
|
|
184
|
-
if (!isRecord(value))
|
|
185
|
-
return false;
|
|
186
|
-
const allowed = new Set([
|
|
187
|
-
'value',
|
|
188
|
-
'activeStateIds',
|
|
189
|
-
'tags',
|
|
190
|
-
'status',
|
|
191
|
-
'quiescent',
|
|
192
|
-
'stateId',
|
|
193
|
-
]);
|
|
194
|
-
if (Reflect.ownKeys(value).some((key) => typeof key !== 'string' || !allowed.has(key)) ||
|
|
195
|
-
!Object.prototype.hasOwnProperty.call(value, 'value') ||
|
|
196
|
-
!Object.prototype.hasOwnProperty.call(value, 'activeStateIds') ||
|
|
197
|
-
!Object.prototype.hasOwnProperty.call(value, 'tags') ||
|
|
198
|
-
!Object.prototype.hasOwnProperty.call(value, 'status') ||
|
|
199
|
-
!Object.prototype.hasOwnProperty.call(value, 'quiescent')) {
|
|
200
|
-
return false;
|
|
166
|
+
switch (typeof value) {
|
|
167
|
+
case 'boolean':
|
|
168
|
+
case 'string':
|
|
169
|
+
return true;
|
|
170
|
+
case 'number':
|
|
171
|
+
return Number.isFinite(value);
|
|
172
|
+
case 'object':
|
|
173
|
+
break;
|
|
174
|
+
default:
|
|
175
|
+
return false;
|
|
201
176
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
isStringArray(value.tags) &&
|
|
205
|
-
(value.status === 'active' ||
|
|
206
|
-
value.status === 'done' ||
|
|
207
|
-
value.status === 'error' ||
|
|
208
|
-
value.status === 'stopped') &&
|
|
209
|
-
typeof value.quiescent === 'boolean' &&
|
|
210
|
-
(!Object.prototype.hasOwnProperty.call(value, 'stateId') ||
|
|
211
|
-
(isNonEmptyString(value.stateId) &&
|
|
212
|
-
value.activeStateIds.length === 1 &&
|
|
213
|
-
value.activeStateIds[0] === value.stateId)));
|
|
214
|
-
}
|
|
215
|
-
function nestedResultFromError(error) {
|
|
216
|
-
if (!(error instanceof Error))
|
|
217
|
-
return undefined;
|
|
218
|
-
const result = error.result;
|
|
219
|
-
return isRecord(result) ? result : undefined;
|
|
220
|
-
}
|
|
221
|
-
function isFailureTerminalRecord(value) {
|
|
222
|
-
if (!isRecord(value))
|
|
177
|
+
const container = value;
|
|
178
|
+
if (path.has(container))
|
|
223
179
|
return false;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (Object.prototype.hasOwnProperty.call(result, 'childSessionId') &&
|
|
250
|
-
!isNonEmptyString(result.childSessionId)) {
|
|
251
|
-
return undefined;
|
|
252
|
-
}
|
|
253
|
-
if (Object.prototype.hasOwnProperty.call(result, 'state') &&
|
|
254
|
-
!isPlaybookState(result.state)) {
|
|
255
|
-
return undefined;
|
|
256
|
-
}
|
|
257
|
-
if (result.status === 'ok') {
|
|
258
|
-
if (!Object.prototype.hasOwnProperty.call(result, 'terminal') ||
|
|
259
|
-
!isFailureTerminalRecord(result.terminal) ||
|
|
260
|
-
!isNonEmptyString(result.childSessionId)) {
|
|
261
|
-
return undefined;
|
|
180
|
+
path.add(container);
|
|
181
|
+
try {
|
|
182
|
+
if (Array.isArray(container)) {
|
|
183
|
+
if (Object.getPrototypeOf(container) !== Array.prototype)
|
|
184
|
+
return false;
|
|
185
|
+
const keys = Reflect.ownKeys(container);
|
|
186
|
+
if (keys.length !== container.length + 1)
|
|
187
|
+
return false;
|
|
188
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(container, 'length');
|
|
189
|
+
if (lengthDescriptor === undefined ||
|
|
190
|
+
lengthDescriptor.configurable !== false ||
|
|
191
|
+
lengthDescriptor.enumerable !== false ||
|
|
192
|
+
lengthDescriptor.value !== container.length) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
for (let index = 0; index < container.length; index += 1) {
|
|
196
|
+
const descriptor = Object.getOwnPropertyDescriptor(container, String(index));
|
|
197
|
+
if (descriptor === undefined ||
|
|
198
|
+
!descriptor.enumerable ||
|
|
199
|
+
!('value' in descriptor) ||
|
|
200
|
+
!isJsonValue(descriptor.value, path)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
262
205
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
(key !== 'name' && key !== 'message' && key !== 'stack')) ||
|
|
277
|
-
!Object.prototype.hasOwnProperty.call(result.error, 'name') ||
|
|
278
|
-
!Object.prototype.hasOwnProperty.call(result.error, 'message') ||
|
|
279
|
-
!isNonEmptyString(result.error.name) ||
|
|
280
|
-
typeof result.error.message !== 'string' ||
|
|
281
|
-
(Object.prototype.hasOwnProperty.call(result.error, 'stack') &&
|
|
282
|
-
typeof result.error.stack !== 'string')) {
|
|
283
|
-
return undefined;
|
|
206
|
+
const prototype = Object.getPrototypeOf(container);
|
|
207
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
208
|
+
return false;
|
|
209
|
+
for (const key of Reflect.ownKeys(container)) {
|
|
210
|
+
if (typeof key !== 'string')
|
|
211
|
+
return false;
|
|
212
|
+
const descriptor = Object.getOwnPropertyDescriptor(container, key);
|
|
213
|
+
if (descriptor === undefined ||
|
|
214
|
+
!descriptor.enumerable ||
|
|
215
|
+
!('value' in descriptor) ||
|
|
216
|
+
!isJsonValue(descriptor.value, path)) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
284
219
|
}
|
|
285
|
-
|
|
286
|
-
name: result.error.name,
|
|
287
|
-
message: result.error.message,
|
|
288
|
-
};
|
|
220
|
+
return true;
|
|
289
221
|
}
|
|
290
|
-
|
|
291
|
-
|
|
222
|
+
finally {
|
|
223
|
+
path.delete(container);
|
|
292
224
|
}
|
|
293
|
-
return {
|
|
294
|
-
status: result.status,
|
|
295
|
-
...(normalizedError === undefined ? {} : { error: normalizedError }),
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
function authoredReviewFailure({ event }) {
|
|
299
|
-
const error = isRecord(event) ? event.error : undefined;
|
|
300
|
-
return normalizedReviewFailure(error) !== undefined;
|
|
301
225
|
}
|
|
302
|
-
|
|
226
|
+
/** Normalizes any thrown or malformed value to a JSON-safe error record. */
|
|
227
|
+
function errorRecord(value) {
|
|
303
228
|
if (value instanceof Error) {
|
|
304
229
|
return {
|
|
305
230
|
name: value.name || 'Error',
|
|
306
231
|
message: value.message,
|
|
232
|
+
...(typeof value.stack === 'string' ? { stack: value.stack } : {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (isRecord(value) && typeof value.message === 'string') {
|
|
236
|
+
return {
|
|
237
|
+
name: isNonEmptyString(value.name) ? value.name : 'Error',
|
|
238
|
+
message: value.message,
|
|
307
239
|
};
|
|
308
240
|
}
|
|
309
241
|
return { name: 'Error', message: String(value) };
|
|
310
242
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Nested `review` results
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
/**
|
|
247
|
+
* Source-authored acceptance on `review`'s declared public output: the
|
|
248
|
+
* result returns the exact evaluated repository revision and affirmatively
|
|
249
|
+
* establishes that no unsettled findings remain. The shared bridge correlates
|
|
250
|
+
* the result with the supplied review scope.
|
|
251
|
+
*/
|
|
252
|
+
function reviewPassOf(event) {
|
|
253
|
+
const output = doneOutputOf(event);
|
|
254
|
+
if (!isRecord(output))
|
|
255
|
+
return undefined;
|
|
256
|
+
return output.noUnsettledFindings === true &&
|
|
257
|
+
isNonEmptyString(output.evaluatedRevision)
|
|
258
|
+
? { noUnsettledFindings: true, evaluatedRevision: output.evaluatedRevision }
|
|
259
|
+
: undefined;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Recognizes an authored rejected child result: an aborted or error call, or
|
|
263
|
+
* a child that completed at its own authored failure terminal.
|
|
264
|
+
*/
|
|
265
|
+
export function authoredChildResult(error, expectedPlaybookId) {
|
|
266
|
+
if (!(error instanceof Error))
|
|
267
|
+
return undefined;
|
|
268
|
+
try {
|
|
269
|
+
const result = validatePlaybookCallResult(error.result, expectedPlaybookId);
|
|
270
|
+
return result.status !== 'ok' || result.terminal?.kind === 'failure'
|
|
271
|
+
? result
|
|
272
|
+
: undefined;
|
|
316
273
|
}
|
|
317
|
-
|
|
318
|
-
return
|
|
274
|
+
catch {
|
|
275
|
+
return undefined;
|
|
319
276
|
}
|
|
320
|
-
return compactError(outer);
|
|
321
277
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
278
|
+
/** Compact evidence and reported error for an authored review failure. */
|
|
279
|
+
function authoredReviewFailureOf(event) {
|
|
280
|
+
const result = authoredChildResult(actorErrorOf(event), REVIEW_PLAYBOOK_ID);
|
|
281
|
+
if (result === undefined)
|
|
282
|
+
return undefined;
|
|
283
|
+
if (result.status === 'ok') {
|
|
284
|
+
const description = result.terminal?.description;
|
|
285
|
+
return {
|
|
286
|
+
evidence: {
|
|
287
|
+
playbookId: REVIEW_PLAYBOOK_ID,
|
|
288
|
+
status: 'ok',
|
|
289
|
+
...(result.output === undefined ? {} : { output: result.output }),
|
|
290
|
+
},
|
|
291
|
+
error: {
|
|
292
|
+
name: 'ReviewFailed',
|
|
293
|
+
message: isNonEmptyString(description)
|
|
294
|
+
? `review ended at its authored failure terminal: ${description}`
|
|
295
|
+
: 'review ended at its authored failure terminal.',
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const error = result.error !== undefined
|
|
300
|
+
? { name: result.error.name, message: result.error.message }
|
|
301
|
+
: { name: 'AbortError', message: 'The review call was aborted.' };
|
|
302
|
+
return {
|
|
303
|
+
evidence: { playbookId: REVIEW_PLAYBOOK_ID, status: result.status, error },
|
|
304
|
+
error,
|
|
305
|
+
};
|
|
325
306
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// Nested call text composition (quoted relays)
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
const REVIEW_PLACEHOLDER = /<caller-input>|<code-commit>|<coder-output>|<ir-task>/g;
|
|
311
|
+
/**
|
|
312
|
+
* Composes review call text in one callback pass per template line: a quoted
|
|
313
|
+
* relay line whose value is empty contributes no line, a multi-line value is
|
|
314
|
+
* quoted line by line, and an absent value keeps its placeholder literal.
|
|
315
|
+
*/
|
|
316
|
+
function composeReviewText(template, values) {
|
|
317
|
+
const lines = [];
|
|
318
|
+
for (const line of template.split('\n')) {
|
|
319
|
+
const quoted = line.startsWith('> ');
|
|
320
|
+
const tokens = line.match(REVIEW_PLACEHOLDER) ?? [];
|
|
321
|
+
if (quoted && tokens.some((token) => values[token] === ''))
|
|
322
|
+
continue;
|
|
323
|
+
lines.push(line.replace(REVIEW_PLACEHOLDER, (token) => {
|
|
324
|
+
const value = values[token];
|
|
325
|
+
if (value === undefined)
|
|
326
|
+
return token;
|
|
327
|
+
return quoted ? value.split('\n').join('\n> ') : value;
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
while (lines.length > 0 && lines[lines.length - 1] === '')
|
|
331
|
+
lines.pop();
|
|
332
|
+
return lines.join('\n');
|
|
329
333
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
const acceptsOutcome = (outcome) => ({ event }) => playerOutputOf(event)?.guard === outcome;
|
|
335
|
+
const needsBossReplyWithQuestion = ({ event }) => playerOutputOf(event)?.guard === 'needsBossReply';
|
|
336
|
+
// A review state is entered only through an accepting phase arm, which records
|
|
337
|
+
// the phase outcome and its non-empty code-owned commit.
|
|
338
|
+
const reviewPassedAfter = (outcome) => ({ context, event }) => context.phaseOutcome === outcome &&
|
|
339
|
+
isNonEmptyString(context.codeCommit) &&
|
|
340
|
+
reviewPassOf(event) !== undefined;
|
|
341
|
+
const authoredReviewFailure = ({ event }) => authoredReviewFailureOf(event) !== undefined;
|
|
342
|
+
const validStartCode = ({ event }) => event.type === 'START_CODE' &&
|
|
343
|
+
isNonEmptyString(event.callerInput) &&
|
|
344
|
+
(event.runResults === undefined || typeof event.runResults === 'string');
|
|
345
|
+
const repliesToPendingQuestion = ({ context, event }) => event.type === 'BOSS_REPLY' &&
|
|
346
|
+
context.pendingBossQuestion !== undefined &&
|
|
347
|
+
(event.questionId === undefined ||
|
|
348
|
+
event.questionId === context.pendingBossQuestion.questionId);
|
|
349
|
+
const emptyBossReply = (args) => repliesToPendingQuestion(args) &&
|
|
350
|
+
args.event.type === 'BOSS_REPLY' &&
|
|
351
|
+
!isNonEmptyString(args.event.answer);
|
|
352
|
+
const INTERRUPT_PRECONDITIONS = {
|
|
353
|
+
firstPhase: (context) => isNonEmptyString(context.callerInput),
|
|
354
|
+
irTaskPhase: (context) => isNonEmptyString(context.callerInput) &&
|
|
355
|
+
isNonEmptyString(context.irNumber),
|
|
356
|
+
};
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
// Transition helpers
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
/** One guarded root transition per jumpable state. */
|
|
361
|
+
function bossInterrupts(ids) {
|
|
362
|
+
return ids.map((id) => ({
|
|
363
|
+
guard: ({ context, event }) => event.type === 'BOSS_INTERRUPT' &&
|
|
364
|
+
event.targetId === id &&
|
|
365
|
+
INTERRUPT_PRECONDITIONS[id](context),
|
|
366
|
+
target: `#${id}`,
|
|
367
|
+
reenter: true,
|
|
368
|
+
actions: 'resetForInterrupt',
|
|
369
|
+
}));
|
|
336
370
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
371
|
+
/** One guarded `BOSS_REPLY` resume transition per suspended working leaf. */
|
|
372
|
+
function resumableStates(ids) {
|
|
373
|
+
return ids.map((id) => ({
|
|
374
|
+
guard: (args) => repliesToPendingQuestion(args) &&
|
|
375
|
+
args.event.type === 'BOSS_REPLY' &&
|
|
376
|
+
isNonEmptyString(args.event.answer) &&
|
|
377
|
+
args.context.pendingBossQuestion?.resumeStateId === id,
|
|
378
|
+
target: `#${id}`,
|
|
379
|
+
reenter: true,
|
|
380
|
+
actions: 'rememberBossReply',
|
|
381
|
+
}));
|
|
344
382
|
}
|
|
345
|
-
const
|
|
346
|
-
|
|
383
|
+
const bossReplyFields = (context) => ({
|
|
384
|
+
...(context.pendingBossQuestion !== undefined
|
|
385
|
+
? { pendingBossQuestion: context.pendingBossQuestion }
|
|
386
|
+
: {}),
|
|
387
|
+
...(context.bossReply !== undefined ? { bossReply: context.bossReply } : {}),
|
|
388
|
+
});
|
|
389
|
+
const acceptedOutcome = (source, target, outcome) => ({
|
|
390
|
+
type: 'playbook.acceptedOutcome',
|
|
391
|
+
params: { source, target, acceptedOutcome: outcome },
|
|
392
|
+
});
|
|
393
|
+
const acceptPhaseArm = (source, outcome, guard, target) => ({
|
|
394
|
+
guard,
|
|
395
|
+
target: `#${target}`,
|
|
396
|
+
actions: [
|
|
397
|
+
acceptedOutcome(source, target, outcome),
|
|
398
|
+
'rememberPhaseCommit',
|
|
399
|
+
'clearBossReplyContext',
|
|
400
|
+
],
|
|
401
|
+
});
|
|
402
|
+
const suspendArm = (source) => ({
|
|
403
|
+
guard: 'needsBossReplyWithQuestion',
|
|
404
|
+
target: '#awaitBossReply',
|
|
405
|
+
actions: [
|
|
406
|
+
acceptedOutcome(source, 'awaitBossReply', 'needsBossReply'),
|
|
407
|
+
{ type: 'setPendingBossQuestion', params: { resumeStateId: source } },
|
|
408
|
+
],
|
|
409
|
+
});
|
|
410
|
+
const malformedPlayerOutputArm = {
|
|
411
|
+
target: '#failed',
|
|
412
|
+
actions: ['rememberMalformedPlayerOutput', 'clearBossReplyContext'],
|
|
413
|
+
};
|
|
414
|
+
const playerOnError = {
|
|
415
|
+
target: '#failed',
|
|
416
|
+
actions: ['rememberActorError', 'clearBossReplyContext'],
|
|
417
|
+
};
|
|
418
|
+
const reviewOnError = [
|
|
419
|
+
{
|
|
420
|
+
guard: 'authoredReviewFailure',
|
|
421
|
+
target: '#reviewFailed',
|
|
422
|
+
actions: 'rememberAuthoredReviewFailure',
|
|
423
|
+
},
|
|
424
|
+
{ target: '#failed', actions: 'rememberActorError' },
|
|
425
|
+
];
|
|
426
|
+
const meta = (stateId, role) => ({
|
|
427
|
+
playbook: {
|
|
428
|
+
stateId,
|
|
429
|
+
description: STATE_DESCRIPTIONS[stateId],
|
|
430
|
+
...(role === undefined ? {} : { role }),
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
const finalMeta = (stateId, terminal) => ({
|
|
434
|
+
playbook: {
|
|
435
|
+
stateId,
|
|
436
|
+
description: STATE_DESCRIPTIONS[stateId],
|
|
437
|
+
terminal,
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
// Machine
|
|
442
|
+
// ---------------------------------------------------------------------------
|
|
443
|
+
export const codeMachine = setup({
|
|
444
|
+
types: {
|
|
445
|
+
context: {},
|
|
446
|
+
events: {},
|
|
447
|
+
input: {},
|
|
448
|
+
output: {},
|
|
449
|
+
},
|
|
347
450
|
actors: {
|
|
348
451
|
player: fromPromise(async () => {
|
|
349
452
|
throw new Error('player actor must be provided by the runner');
|
|
@@ -352,488 +455,345 @@ const machineSetup = setup({
|
|
|
352
455
|
throw new Error('playbook actor must be provided by the runner');
|
|
353
456
|
}),
|
|
354
457
|
},
|
|
355
|
-
guards: {
|
|
356
|
-
isDirectCommit,
|
|
357
|
-
isIrCommit,
|
|
358
|
-
isMoreTasks,
|
|
359
|
-
isFinalTask,
|
|
360
|
-
needsBossReply,
|
|
361
|
-
reviewApprovedDirect: reviewApprovedFor('direct'),
|
|
362
|
-
reviewApprovedIrCreated: reviewApprovedFor('ir-created'),
|
|
363
|
-
reviewApprovedMoreTasks: reviewApprovedFor('ir-task-more'),
|
|
364
|
-
reviewApprovedFinalTask: reviewApprovedFor('ir-task-final'),
|
|
365
|
-
authoredReviewFailure,
|
|
366
|
-
emptyBossReply: ({ event }) => event.type === 'BOSS_REPLY' && event.answer.trim().length === 0,
|
|
367
|
-
resumesFirstPhase: ({ context, event }) => event.type === 'BOSS_REPLY' &&
|
|
368
|
-
event.answer.trim().length > 0 &&
|
|
369
|
-
context.pendingBossQuestion?.resumeStateId === 'runFirstPhase' &&
|
|
370
|
-
(event.questionId === undefined || event.questionId === 'runFirstPhase'),
|
|
371
|
-
resumesIrTask: ({ context, event }) => event.type === 'BOSS_REPLY' &&
|
|
372
|
-
event.answer.trim().length > 0 &&
|
|
373
|
-
context.pendingBossQuestion?.resumeStateId === 'runIrTask' &&
|
|
374
|
-
(event.questionId === undefined || event.questionId === 'runIrTask'),
|
|
375
|
-
},
|
|
376
458
|
actions: {
|
|
377
459
|
'playbook.acceptedOutcome': (_args, _params) => undefined,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return {};
|
|
381
|
-
return {
|
|
382
|
-
runResults: context.runResults,
|
|
460
|
+
startCode: assign(({ context, event }) => event.type === 'START_CODE'
|
|
461
|
+
? {
|
|
383
462
|
callerInput: event.callerInput,
|
|
463
|
+
runResults: event.runResults ?? context.runResults,
|
|
464
|
+
codeCommit: undefined,
|
|
384
465
|
coderOutput: undefined,
|
|
385
|
-
latestCommit: undefined,
|
|
386
466
|
irNumber: undefined,
|
|
387
467
|
irTask: undefined,
|
|
468
|
+
phaseOutcome: undefined,
|
|
388
469
|
evaluatedRevision: undefined,
|
|
389
|
-
phase: undefined,
|
|
390
|
-
completion: undefined,
|
|
391
470
|
reviewError: undefined,
|
|
471
|
+
reviewEvidence: undefined,
|
|
392
472
|
lastError: undefined,
|
|
393
473
|
pendingBossQuestion: undefined,
|
|
394
474
|
bossReply: undefined,
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
})),
|
|
404
|
-
rememberIrCommit: assign(({ event }) => ({
|
|
405
|
-
coderOutput: outputString(event, 'coderOutput'),
|
|
406
|
-
latestCommit: outputString(event, 'latestCommit'),
|
|
407
|
-
irNumber: outputString(event, 'irNumber'),
|
|
408
|
-
irTask: undefined,
|
|
409
|
-
phase: 'ir-created',
|
|
410
|
-
pendingBossQuestion: undefined,
|
|
411
|
-
bossReply: undefined,
|
|
412
|
-
})),
|
|
413
|
-
rememberMoreTasks: assign(({ event }) => ({
|
|
414
|
-
coderOutput: outputString(event, 'coderOutput'),
|
|
415
|
-
latestCommit: outputString(event, 'latestCommit'),
|
|
416
|
-
irNumber: outputString(event, 'irNumber'),
|
|
417
|
-
irTask: outputString(event, 'irTask'),
|
|
418
|
-
phase: 'ir-task-more',
|
|
419
|
-
pendingBossQuestion: undefined,
|
|
420
|
-
bossReply: undefined,
|
|
421
|
-
})),
|
|
422
|
-
rememberFinalTask: assign(({ event }) => ({
|
|
423
|
-
coderOutput: outputString(event, 'coderOutput'),
|
|
424
|
-
latestCommit: outputString(event, 'latestCommit'),
|
|
425
|
-
irNumber: outputString(event, 'irNumber'),
|
|
426
|
-
irTask: outputString(event, 'irTask'),
|
|
427
|
-
phase: 'ir-task-final',
|
|
475
|
+
}
|
|
476
|
+
: {}),
|
|
477
|
+
resetForInterrupt: assign({
|
|
478
|
+
phaseOutcome: undefined,
|
|
479
|
+
evaluatedRevision: undefined,
|
|
480
|
+
reviewError: undefined,
|
|
481
|
+
reviewEvidence: undefined,
|
|
482
|
+
lastError: undefined,
|
|
428
483
|
pendingBossQuestion: undefined,
|
|
429
484
|
bossReply: undefined,
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (question === undefined || output?.guard !== 'needsBossReply') {
|
|
436
|
-
return { lastError: new Error('Coder question output was malformed') };
|
|
437
|
-
}
|
|
485
|
+
}),
|
|
486
|
+
rememberPhaseCommit: assign(({ event }) => {
|
|
487
|
+
const output = playerOutputOf(event);
|
|
488
|
+
if (output === undefined || output.guard === 'needsBossReply')
|
|
489
|
+
return {};
|
|
438
490
|
return {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
491
|
+
codeCommit: output.latestCommit,
|
|
492
|
+
coderOutput: output.coderOutput,
|
|
493
|
+
phaseOutcome: output.guard,
|
|
494
|
+
irNumber: output.guard === 'directCommit' ? undefined : output.irNumber,
|
|
495
|
+
irTask: output.guard === 'moreTasks' || output.guard === 'finalTask'
|
|
496
|
+
? output.irTask
|
|
497
|
+
: undefined,
|
|
498
|
+
lastError: undefined,
|
|
447
499
|
};
|
|
448
500
|
}),
|
|
501
|
+
setPendingBossQuestion: assign(({ event }, params) => {
|
|
502
|
+
const output = playerOutputOf(event);
|
|
503
|
+
if (output?.guard !== 'needsBossReply')
|
|
504
|
+
return {};
|
|
505
|
+
const pendingBossQuestion = {
|
|
506
|
+
questionId: params.resumeStateId,
|
|
507
|
+
resumeStateId: params.resumeStateId,
|
|
508
|
+
sourceItem: RESUME_METADATA[params.resumeStateId].sourceItem,
|
|
509
|
+
asker: { kind: 'role', roleId: 'coder' },
|
|
510
|
+
question: output.question,
|
|
511
|
+
};
|
|
512
|
+
return { pendingBossQuestion, bossReply: undefined, lastError: undefined };
|
|
513
|
+
}),
|
|
449
514
|
rememberBossReply: assign(({ event }) => event.type === 'BOSS_REPLY' ? { bossReply: event.answer } : {}),
|
|
450
|
-
|
|
451
|
-
lastError: () => new Error('BOSS_REPLY received an empty answer'),
|
|
515
|
+
clearBossReplyContext: assign({
|
|
452
516
|
pendingBossQuestion: undefined,
|
|
453
517
|
bossReply: undefined,
|
|
454
518
|
}),
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
519
|
+
rememberReviewPass: assign(({ event }) => {
|
|
520
|
+
const pass = reviewPassOf(event);
|
|
521
|
+
return pass === undefined
|
|
522
|
+
? {}
|
|
523
|
+
: { evaluatedRevision: pass.evaluatedRevision, lastError: undefined };
|
|
524
|
+
}),
|
|
525
|
+
rememberReviewNotPassed: assign(({ event }) => {
|
|
526
|
+
const output = doneOutputOf(event);
|
|
527
|
+
const reviewEvidence = {
|
|
528
|
+
playbookId: REVIEW_PLAYBOOK_ID,
|
|
529
|
+
status: 'ok',
|
|
530
|
+
...(isJsonValue(output) ? { output } : {}),
|
|
462
531
|
};
|
|
532
|
+
return { reviewEvidence, reviewError: REVIEW_NOT_PASSED, lastError: undefined };
|
|
533
|
+
}),
|
|
534
|
+
rememberAuthoredReviewFailure: assign(({ event }) => {
|
|
535
|
+
const failure = authoredReviewFailureOf(event);
|
|
536
|
+
return failure === undefined
|
|
537
|
+
? {}
|
|
538
|
+
: {
|
|
539
|
+
reviewEvidence: failure.evidence,
|
|
540
|
+
reviewError: failure.error,
|
|
541
|
+
lastError: undefined,
|
|
542
|
+
};
|
|
463
543
|
}),
|
|
464
|
-
completeWithReviewFailure: assign(({ event }) => ({
|
|
465
|
-
completion: 'review-failed',
|
|
466
|
-
reviewError: authoredReviewError(event),
|
|
467
|
-
})),
|
|
468
|
-
completeWithInvalidReviewOutput: assign(({ event }) => ({
|
|
469
|
-
completion: 'review-failed',
|
|
470
|
-
reviewError: {
|
|
471
|
-
name: 'ReviewContractError',
|
|
472
|
-
message: `REVIEW returned an invalid approval result: ` +
|
|
473
|
-
JSON.stringify(reviewOutput(event)),
|
|
474
|
-
},
|
|
475
|
-
})),
|
|
476
544
|
rememberActorError: assign(({ event }) => ({
|
|
477
|
-
lastError: event
|
|
478
|
-
})),
|
|
479
|
-
rememberMalformedPlayerOutput: assign(({ context }) => ({
|
|
480
|
-
lastError: new Error(`Coder result for ${context.irNumber === undefined ? 'CODE-1' : 'CODE-3'} ` +
|
|
481
|
-
'did not match a declared outcome.'),
|
|
545
|
+
lastError: errorRecord(actorErrorOf(event)),
|
|
482
546
|
})),
|
|
547
|
+
rememberMalformedPlayerOutput: assign(({ event }) => {
|
|
548
|
+
const output = doneOutputOf(event);
|
|
549
|
+
return {
|
|
550
|
+
lastError: {
|
|
551
|
+
name: 'MalformedPlayerOutput',
|
|
552
|
+
message: isRecord(output) && output.guard === 'needsBossReply'
|
|
553
|
+
? 'Coder output declared needsBossReply without a question.'
|
|
554
|
+
: 'Coder output did not match an outcome declared for the working state.',
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
}),
|
|
558
|
+
rememberMalformedBossReply: assign({
|
|
559
|
+
lastError: {
|
|
560
|
+
name: 'MalformedBossReply',
|
|
561
|
+
message: 'BOSS_REPLY carried an empty answer.',
|
|
562
|
+
},
|
|
563
|
+
}),
|
|
483
564
|
},
|
|
484
|
-
|
|
485
|
-
|
|
565
|
+
guards: {
|
|
566
|
+
needsBossReplyWithQuestion,
|
|
567
|
+
acceptDirectCommit: acceptsOutcome('directCommit'),
|
|
568
|
+
acceptIrCommit: acceptsOutcome('irCommit'),
|
|
569
|
+
acceptMoreTasks: acceptsOutcome('moreTasks'),
|
|
570
|
+
acceptFinalTask: acceptsOutcome('finalTask'),
|
|
571
|
+
reviewPassedDirect: reviewPassedAfter('directCommit'),
|
|
572
|
+
reviewPassedNewIr: reviewPassedAfter('irCommit'),
|
|
573
|
+
reviewPassedNonfinalTask: reviewPassedAfter('moreTasks'),
|
|
574
|
+
reviewPassedFinalTask: reviewPassedAfter('finalTask'),
|
|
575
|
+
authoredReviewFailure,
|
|
576
|
+
validStartCode,
|
|
577
|
+
emptyBossReply,
|
|
578
|
+
},
|
|
579
|
+
}).createMachine({
|
|
486
580
|
id: 'code',
|
|
487
581
|
initial: 'ready',
|
|
488
582
|
context: ({ input }) => ({
|
|
489
|
-
runResults: typeof input
|
|
583
|
+
runResults: typeof input?.runResults === 'string' ? input.runResults : '',
|
|
490
584
|
}),
|
|
491
585
|
output: ({ context }) => {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
throw new Error('CODE reached a final state without a commit identity');
|
|
586
|
+
if (!isNonEmptyString(context.codeCommit)) {
|
|
587
|
+
throw new Error('CODE reached a final state without its last code-owned commit');
|
|
495
588
|
}
|
|
496
|
-
if (context.
|
|
589
|
+
if (context.reviewError !== undefined) {
|
|
497
590
|
return {
|
|
498
591
|
status: 'review-failed',
|
|
499
|
-
lastCodeCommit,
|
|
500
|
-
error: context.reviewError
|
|
501
|
-
name: 'Error',
|
|
502
|
-
message: 'REVIEW failed without error detail.',
|
|
503
|
-
},
|
|
592
|
+
lastCodeCommit: context.codeCommit,
|
|
593
|
+
error: context.reviewError,
|
|
504
594
|
};
|
|
505
595
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
throw new Error('CODE completed without an evaluated repository revision');
|
|
596
|
+
if (!isNonEmptyString(context.evaluatedRevision)) {
|
|
597
|
+
throw new Error('CODE completed without the final evaluated revision');
|
|
509
598
|
}
|
|
510
599
|
return {
|
|
511
600
|
status: 'complete',
|
|
512
|
-
lastCodeCommit,
|
|
513
|
-
finalEvaluatedRevision,
|
|
601
|
+
lastCodeCommit: context.codeCommit,
|
|
602
|
+
finalEvaluatedRevision: context.evaluatedRevision,
|
|
514
603
|
allReviewsPassed: true,
|
|
515
604
|
};
|
|
516
605
|
},
|
|
606
|
+
on: {
|
|
607
|
+
BOSS_INTERRUPT: bossInterrupts(['firstPhase', 'irTaskPhase']),
|
|
608
|
+
},
|
|
517
609
|
states: {
|
|
518
610
|
ready: {
|
|
519
611
|
id: 'ready',
|
|
612
|
+
tags: 'playbook.parked',
|
|
520
613
|
description: STATE_DESCRIPTIONS.ready,
|
|
521
|
-
meta:
|
|
522
|
-
tags: ['playbook.parked'],
|
|
614
|
+
meta: meta('ready'),
|
|
523
615
|
on: {
|
|
524
|
-
START_CODE: {
|
|
616
|
+
START_CODE: {
|
|
617
|
+
guard: 'validStartCode',
|
|
618
|
+
target: 'firstPhase',
|
|
619
|
+
actions: 'startCode',
|
|
620
|
+
},
|
|
525
621
|
},
|
|
526
622
|
},
|
|
527
|
-
|
|
528
|
-
id: '
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
623
|
+
firstPhase: {
|
|
624
|
+
id: 'firstPhase',
|
|
625
|
+
tags: 'playbook.busy',
|
|
626
|
+
description: STATE_DESCRIPTIONS.firstPhase,
|
|
627
|
+
meta: meta('firstPhase', 'coder'),
|
|
532
628
|
invoke: {
|
|
533
629
|
src: 'player',
|
|
534
630
|
input: ({ context }) => ({
|
|
535
|
-
stateId: '
|
|
631
|
+
stateId: 'firstPhase',
|
|
536
632
|
role: 'coder',
|
|
537
633
|
sourceItem: 'CODE-1',
|
|
538
634
|
prompt: FIRST_PHASE_PROMPT,
|
|
539
|
-
result:
|
|
540
|
-
|
|
635
|
+
result: FIRST_PHASE_RESULT,
|
|
636
|
+
...(context.callerInput !== undefined
|
|
637
|
+
? { callerInput: context.callerInput }
|
|
638
|
+
: {}),
|
|
541
639
|
runResults: context.runResults,
|
|
542
|
-
...(context
|
|
543
|
-
? {}
|
|
544
|
-
: { pendingBossQuestion: context.pendingBossQuestion }),
|
|
545
|
-
...(context.bossReply === undefined
|
|
546
|
-
? {}
|
|
547
|
-
: { bossReply: context.bossReply }),
|
|
640
|
+
...bossReplyFields(context),
|
|
548
641
|
}),
|
|
549
642
|
onDone: [
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
params: {
|
|
557
|
-
source: 'runFirstPhase',
|
|
558
|
-
target: 'reviewFirstCommit',
|
|
559
|
-
acceptedOutcome: 'directCommit',
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
'rememberDirectCommit',
|
|
563
|
-
],
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
guard: 'isIrCommit',
|
|
567
|
-
target: 'reviewFirstCommit',
|
|
568
|
-
actions: [
|
|
569
|
-
{
|
|
570
|
-
type: 'playbook.acceptedOutcome',
|
|
571
|
-
params: {
|
|
572
|
-
source: 'runFirstPhase',
|
|
573
|
-
target: 'reviewFirstCommit',
|
|
574
|
-
acceptedOutcome: 'irCommit',
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
'rememberIrCommit',
|
|
578
|
-
],
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
guard: 'isMoreTasks',
|
|
582
|
-
target: 'reviewIrTask',
|
|
583
|
-
actions: [
|
|
584
|
-
{
|
|
585
|
-
type: 'playbook.acceptedOutcome',
|
|
586
|
-
params: {
|
|
587
|
-
source: 'runFirstPhase',
|
|
588
|
-
target: 'reviewIrTask',
|
|
589
|
-
acceptedOutcome: 'moreTasks',
|
|
590
|
-
},
|
|
591
|
-
},
|
|
592
|
-
'rememberMoreTasks',
|
|
593
|
-
],
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
guard: 'isFinalTask',
|
|
597
|
-
target: 'reviewIrTask',
|
|
598
|
-
actions: [
|
|
599
|
-
{
|
|
600
|
-
type: 'playbook.acceptedOutcome',
|
|
601
|
-
params: {
|
|
602
|
-
source: 'runFirstPhase',
|
|
603
|
-
target: 'reviewIrTask',
|
|
604
|
-
acceptedOutcome: 'finalTask',
|
|
605
|
-
},
|
|
606
|
-
},
|
|
607
|
-
'rememberFinalTask',
|
|
608
|
-
],
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
guard: 'needsBossReply',
|
|
612
|
-
target: 'awaitBossReply',
|
|
613
|
-
actions: [
|
|
614
|
-
{
|
|
615
|
-
type: 'playbook.acceptedOutcome',
|
|
616
|
-
params: {
|
|
617
|
-
source: 'runFirstPhase',
|
|
618
|
-
target: 'awaitBossReply',
|
|
619
|
-
acceptedOutcome: 'needsBossReply',
|
|
620
|
-
},
|
|
621
|
-
},
|
|
622
|
-
'rememberPendingQuestion',
|
|
623
|
-
],
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
target: 'failed',
|
|
627
|
-
actions: 'rememberMalformedPlayerOutput',
|
|
628
|
-
},
|
|
643
|
+
suspendArm('firstPhase'),
|
|
644
|
+
acceptPhaseArm('firstPhase', 'directCommit', 'acceptDirectCommit', 'reviewNewIntentPhase'),
|
|
645
|
+
acceptPhaseArm('firstPhase', 'irCommit', 'acceptIrCommit', 'reviewNewIntentPhase'),
|
|
646
|
+
acceptPhaseArm('firstPhase', 'moreTasks', 'acceptMoreTasks', 'reviewIrTaskPhase'),
|
|
647
|
+
acceptPhaseArm('firstPhase', 'finalTask', 'acceptFinalTask', 'reviewIrTaskPhase'),
|
|
648
|
+
malformedPlayerOutputArm,
|
|
629
649
|
],
|
|
630
|
-
onError:
|
|
650
|
+
onError: playerOnError,
|
|
631
651
|
},
|
|
632
652
|
},
|
|
633
|
-
|
|
634
|
-
id: '
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
653
|
+
reviewNewIntentPhase: {
|
|
654
|
+
id: 'reviewNewIntentPhase',
|
|
655
|
+
tags: 'playbook.suspended',
|
|
656
|
+
description: STATE_DESCRIPTIONS.reviewNewIntentPhase,
|
|
657
|
+
meta: meta('reviewNewIntentPhase'),
|
|
638
658
|
invoke: {
|
|
639
659
|
src: 'playbook',
|
|
640
660
|
input: ({ context }) => ({
|
|
641
|
-
stateId: '
|
|
661
|
+
stateId: 'reviewNewIntentPhase',
|
|
642
662
|
sourceItem: 'CODE-2',
|
|
643
|
-
playbookId:
|
|
644
|
-
text:
|
|
663
|
+
playbookId: REVIEW_PLAYBOOK_ID,
|
|
664
|
+
text: composeReviewText(REVIEW_NEW_INTENT_PHASE_TEMPLATE, {
|
|
665
|
+
'<caller-input>': context.callerInput,
|
|
666
|
+
'<code-commit>': context.codeCommit,
|
|
667
|
+
'<coder-output>': context.coderOutput,
|
|
668
|
+
}),
|
|
645
669
|
}),
|
|
646
670
|
onDone: [
|
|
647
671
|
{
|
|
648
|
-
guard: '
|
|
672
|
+
guard: 'reviewPassedDirect',
|
|
649
673
|
target: 'done',
|
|
650
|
-
actions: '
|
|
674
|
+
actions: 'rememberReviewPass',
|
|
651
675
|
},
|
|
652
676
|
{
|
|
653
|
-
guard: '
|
|
654
|
-
target: '
|
|
677
|
+
guard: 'reviewPassedNewIr',
|
|
678
|
+
target: 'irTaskPhase',
|
|
679
|
+
actions: 'rememberReviewPass',
|
|
655
680
|
},
|
|
656
|
-
{
|
|
657
|
-
target: 'reportedReviewFailure',
|
|
658
|
-
actions: 'completeWithInvalidReviewOutput',
|
|
659
|
-
},
|
|
660
|
-
],
|
|
661
|
-
onError: [
|
|
662
|
-
{
|
|
663
|
-
guard: 'authoredReviewFailure',
|
|
664
|
-
target: 'reportedReviewFailure',
|
|
665
|
-
actions: 'completeWithReviewFailure',
|
|
666
|
-
},
|
|
667
|
-
{ target: 'failed', actions: 'rememberActorError' },
|
|
681
|
+
{ target: 'reviewFailed', actions: 'rememberReviewNotPassed' },
|
|
668
682
|
],
|
|
683
|
+
onError: reviewOnError,
|
|
669
684
|
},
|
|
670
685
|
},
|
|
671
|
-
|
|
672
|
-
id: '
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
686
|
+
irTaskPhase: {
|
|
687
|
+
id: 'irTaskPhase',
|
|
688
|
+
tags: 'playbook.busy',
|
|
689
|
+
description: STATE_DESCRIPTIONS.irTaskPhase,
|
|
690
|
+
meta: meta('irTaskPhase', 'coder'),
|
|
676
691
|
invoke: {
|
|
677
692
|
src: 'player',
|
|
678
693
|
input: ({ context }) => ({
|
|
679
|
-
stateId: '
|
|
694
|
+
stateId: 'irTaskPhase',
|
|
680
695
|
role: 'coder',
|
|
681
696
|
sourceItem: 'CODE-3',
|
|
682
|
-
prompt:
|
|
683
|
-
result:
|
|
684
|
-
|
|
697
|
+
prompt: IR_TASK_PHASE_PROMPT,
|
|
698
|
+
result: IR_TASK_PHASE_RESULT,
|
|
699
|
+
...(context.callerInput !== undefined
|
|
700
|
+
? { callerInput: context.callerInput }
|
|
701
|
+
: {}),
|
|
702
|
+
...(context.irNumber !== undefined
|
|
703
|
+
? { irNumber: context.irNumber }
|
|
704
|
+
: {}),
|
|
685
705
|
runResults: context.runResults,
|
|
686
|
-
...(context
|
|
687
|
-
? {}
|
|
688
|
-
: { irNumber: context.irNumber }),
|
|
689
|
-
...(context.pendingBossQuestion === undefined
|
|
690
|
-
? {}
|
|
691
|
-
: { pendingBossQuestion: context.pendingBossQuestion }),
|
|
692
|
-
...(context.bossReply === undefined
|
|
693
|
-
? {}
|
|
694
|
-
: { bossReply: context.bossReply }),
|
|
706
|
+
...bossReplyFields(context),
|
|
695
707
|
}),
|
|
696
708
|
onDone: [
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
{
|
|
702
|
-
type: 'playbook.acceptedOutcome',
|
|
703
|
-
params: {
|
|
704
|
-
source: 'runIrTask',
|
|
705
|
-
target: 'reviewIrTask',
|
|
706
|
-
acceptedOutcome: 'moreTasks',
|
|
707
|
-
},
|
|
708
|
-
},
|
|
709
|
-
'rememberMoreTasks',
|
|
710
|
-
],
|
|
711
|
-
},
|
|
712
|
-
{
|
|
713
|
-
guard: 'isFinalTask',
|
|
714
|
-
target: 'reviewIrTask',
|
|
715
|
-
actions: [
|
|
716
|
-
{
|
|
717
|
-
type: 'playbook.acceptedOutcome',
|
|
718
|
-
params: {
|
|
719
|
-
source: 'runIrTask',
|
|
720
|
-
target: 'reviewIrTask',
|
|
721
|
-
acceptedOutcome: 'finalTask',
|
|
722
|
-
},
|
|
723
|
-
},
|
|
724
|
-
'rememberFinalTask',
|
|
725
|
-
],
|
|
726
|
-
},
|
|
727
|
-
{
|
|
728
|
-
guard: 'needsBossReply',
|
|
729
|
-
target: 'awaitBossReply',
|
|
730
|
-
actions: [
|
|
731
|
-
{
|
|
732
|
-
type: 'playbook.acceptedOutcome',
|
|
733
|
-
params: {
|
|
734
|
-
source: 'runIrTask',
|
|
735
|
-
target: 'awaitBossReply',
|
|
736
|
-
acceptedOutcome: 'needsBossReply',
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
|
-
'rememberPendingQuestion',
|
|
740
|
-
],
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
target: 'failed',
|
|
744
|
-
actions: 'rememberMalformedPlayerOutput',
|
|
745
|
-
},
|
|
709
|
+
suspendArm('irTaskPhase'),
|
|
710
|
+
acceptPhaseArm('irTaskPhase', 'moreTasks', 'acceptMoreTasks', 'reviewIrTaskPhase'),
|
|
711
|
+
acceptPhaseArm('irTaskPhase', 'finalTask', 'acceptFinalTask', 'reviewIrTaskPhase'),
|
|
712
|
+
malformedPlayerOutputArm,
|
|
746
713
|
],
|
|
747
|
-
onError:
|
|
714
|
+
onError: playerOnError,
|
|
748
715
|
},
|
|
749
716
|
},
|
|
750
|
-
|
|
751
|
-
id: '
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
717
|
+
reviewIrTaskPhase: {
|
|
718
|
+
id: 'reviewIrTaskPhase',
|
|
719
|
+
tags: 'playbook.suspended',
|
|
720
|
+
description: STATE_DESCRIPTIONS.reviewIrTaskPhase,
|
|
721
|
+
meta: meta('reviewIrTaskPhase'),
|
|
755
722
|
invoke: {
|
|
756
723
|
src: 'playbook',
|
|
757
724
|
input: ({ context }) => ({
|
|
758
|
-
stateId: '
|
|
725
|
+
stateId: 'reviewIrTaskPhase',
|
|
759
726
|
sourceItem: 'CODE-4',
|
|
760
|
-
playbookId:
|
|
761
|
-
text:
|
|
727
|
+
playbookId: REVIEW_PLAYBOOK_ID,
|
|
728
|
+
text: composeReviewText(REVIEW_IR_TASK_PHASE_TEMPLATE, {
|
|
729
|
+
'<caller-input>': context.callerInput,
|
|
730
|
+
'<code-commit>': context.codeCommit,
|
|
731
|
+
'<coder-output>': context.coderOutput,
|
|
732
|
+
'<ir-task>': context.irTask,
|
|
733
|
+
}),
|
|
762
734
|
}),
|
|
763
735
|
onDone: [
|
|
764
736
|
{
|
|
765
|
-
guard: '
|
|
766
|
-
target: '
|
|
737
|
+
guard: 'reviewPassedNonfinalTask',
|
|
738
|
+
target: 'irTaskPhase',
|
|
739
|
+
actions: 'rememberReviewPass',
|
|
767
740
|
},
|
|
768
741
|
{
|
|
769
|
-
guard: '
|
|
742
|
+
guard: 'reviewPassedFinalTask',
|
|
770
743
|
target: 'done',
|
|
771
|
-
actions: '
|
|
744
|
+
actions: 'rememberReviewPass',
|
|
772
745
|
},
|
|
773
|
-
{
|
|
774
|
-
target: 'reportedReviewFailure',
|
|
775
|
-
actions: 'completeWithInvalidReviewOutput',
|
|
776
|
-
},
|
|
777
|
-
],
|
|
778
|
-
onError: [
|
|
779
|
-
{
|
|
780
|
-
guard: 'authoredReviewFailure',
|
|
781
|
-
target: 'reportedReviewFailure',
|
|
782
|
-
actions: 'completeWithReviewFailure',
|
|
783
|
-
},
|
|
784
|
-
{ target: 'failed', actions: 'rememberActorError' },
|
|
746
|
+
{ target: 'reviewFailed', actions: 'rememberReviewNotPassed' },
|
|
785
747
|
],
|
|
748
|
+
onError: reviewOnError,
|
|
786
749
|
},
|
|
787
750
|
},
|
|
788
751
|
awaitBossReply: {
|
|
789
752
|
id: 'awaitBossReply',
|
|
753
|
+
tags: 'playbook.parked',
|
|
790
754
|
description: STATE_DESCRIPTIONS.awaitBossReply,
|
|
791
|
-
meta:
|
|
792
|
-
tags: ['playbook.parked'],
|
|
755
|
+
meta: meta('awaitBossReply'),
|
|
793
756
|
on: {
|
|
794
757
|
BOSS_REPLY: [
|
|
795
758
|
{
|
|
796
759
|
guard: 'emptyBossReply',
|
|
797
|
-
target: '
|
|
798
|
-
actions: '
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
guard: 'resumesFirstPhase',
|
|
802
|
-
target: '#runFirstPhase',
|
|
803
|
-
reenter: true,
|
|
804
|
-
actions: 'rememberBossReply',
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
guard: 'resumesIrTask',
|
|
808
|
-
target: '#runIrTask',
|
|
809
|
-
reenter: true,
|
|
810
|
-
actions: 'rememberBossReply',
|
|
760
|
+
target: 'failed',
|
|
761
|
+
actions: ['rememberMalformedBossReply', 'clearBossReplyContext'],
|
|
811
762
|
},
|
|
763
|
+
...resumableStates(['firstPhase', 'irTaskPhase']),
|
|
812
764
|
],
|
|
813
|
-
START_CODE: {
|
|
765
|
+
START_CODE: {
|
|
766
|
+
guard: 'validStartCode',
|
|
767
|
+
target: 'firstPhase',
|
|
768
|
+
actions: 'startCode',
|
|
769
|
+
},
|
|
814
770
|
},
|
|
815
771
|
},
|
|
816
772
|
failed: {
|
|
817
773
|
id: 'failed',
|
|
774
|
+
tags: 'playbook.parked',
|
|
818
775
|
description: STATE_DESCRIPTIONS.failed,
|
|
819
|
-
meta:
|
|
820
|
-
tags: ['playbook.parked'],
|
|
776
|
+
meta: meta('failed'),
|
|
821
777
|
on: {
|
|
822
|
-
START_CODE: {
|
|
778
|
+
START_CODE: {
|
|
779
|
+
guard: 'validStartCode',
|
|
780
|
+
target: 'firstPhase',
|
|
781
|
+
actions: 'startCode',
|
|
782
|
+
},
|
|
823
783
|
},
|
|
824
784
|
},
|
|
825
|
-
|
|
826
|
-
id: '
|
|
827
|
-
description: STATE_DESCRIPTIONS.reportedReviewFailure,
|
|
828
|
-
meta: terminalMeta('reportedReviewFailure', 'failure'),
|
|
785
|
+
reviewFailed: {
|
|
786
|
+
id: 'reviewFailed',
|
|
829
787
|
type: 'final',
|
|
788
|
+
description: STATE_DESCRIPTIONS.reviewFailed,
|
|
789
|
+
meta: finalMeta('reviewFailed', 'failure'),
|
|
830
790
|
},
|
|
831
791
|
done: {
|
|
832
792
|
id: 'done',
|
|
833
|
-
description: STATE_DESCRIPTIONS.done,
|
|
834
|
-
meta: terminalMeta('done', 'success'),
|
|
835
793
|
type: 'final',
|
|
794
|
+
description: STATE_DESCRIPTIONS.done,
|
|
795
|
+
meta: finalMeta('done', 'success'),
|
|
836
796
|
},
|
|
837
797
|
},
|
|
838
798
|
});
|
|
839
|
-
export default
|
|
799
|
+
export default codeMachine;
|