@sublang/playbook 11.0.0 → 12.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/cli.md +9 -6
- package/docs/configuration.md +15 -1
- package/package.json +22 -3
- package/reference/sdlc/code.playbook/code.fsm.d.ts +22 -19
- package/reference/sdlc/code.playbook/code.fsm.js +116 -52
- package/reference/sdlc/code.playbook/code.fsm.ts +149 -64
- package/reference/sdlc/code.playbook/code.gears.md +40 -20
- package/reference/sdlc/code.playbook/code.playbook.js +23 -2
- package/reference/sdlc/code.playbook/code.playbook.ts +23 -2
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +18 -2
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +13 -6
- package/reference/sdlc/decide.playbook/decide.fsm.js +54 -27
- package/reference/sdlc/decide.playbook/decide.fsm.ts +68 -29
- package/reference/sdlc/decide.playbook/decide.gears.md +25 -19
- package/reference/sdlc/decide.playbook/decide.playbook.js +11 -3
- package/reference/sdlc/decide.playbook/decide.playbook.ts +11 -3
- package/reference/sdlc/decide.playbook/decide.registry.js +1 -1
- package/reference/sdlc/decide.playbook/decide.registry.ts +1 -1
- package/reference/sdlc/dev.md +52 -0
- package/reference/sdlc/dev.playbook/dev.fsm.d.ts +261 -0
- package/reference/sdlc/dev.playbook/dev.fsm.js +723 -0
- package/reference/sdlc/dev.playbook/dev.fsm.ts +988 -0
- package/reference/sdlc/dev.playbook/dev.gears.md +91 -0
- package/reference/sdlc/dev.playbook/dev.playbook.d.ts +21 -0
- package/reference/sdlc/dev.playbook/dev.playbook.js +143 -0
- package/reference/sdlc/dev.playbook/dev.playbook.ts +246 -0
- package/reference/sdlc/dev.playbook/dev.registry.d.ts +40 -0
- package/reference/sdlc/dev.playbook/dev.registry.js +64 -0
- package/reference/sdlc/dev.playbook/dev.registry.ts +120 -0
- package/reference/sdlc/review.playbook/review.fsm.d.ts +15 -2
- package/reference/sdlc/review.playbook/review.fsm.js +77 -27
- package/reference/sdlc/review.playbook/review.fsm.ts +96 -30
- package/reference/sdlc/review.playbook/review.gears.md +52 -26
- package/reference/sdlc/review.playbook/review.playbook.js +17 -7
- package/reference/sdlc/review.playbook/review.playbook.ts +17 -7
- package/reference/sdlc/review.playbook/review.registry.js +1 -1
- package/reference/sdlc/review.playbook/review.registry.ts +1 -1
- package/slc/link.md +12 -5
- package/slc/text2gears.md +3 -0
- package/src/xstate-playbook-runtime.js +5 -2
- package/src/xstate-playbook-runtime.ts +5 -2
- package/src/xstate-runtime.js +13 -1
- package/src/xstate-runtime.ts +13 -1
package/docs/cli.md
CHANGED
|
@@ -105,8 +105,8 @@ its exit status or signal and exit `127` when it cannot be spawned
|
|
|
105
105
|
|
|
106
106
|
The Boss pane starts at the Playbook Captain shell, where the session
|
|
107
107
|
Captain runs for the whole session and sees every turn. Use `/code`,
|
|
108
|
-
`/review`, or `/
|
|
109
|
-
playbooks explicitly. A registered command resolves deterministically,
|
|
108
|
+
`/review`, `/decide`, or `/dev` followed by a task to select one of the
|
|
109
|
+
bundled playbooks explicitly. A registered command resolves deterministically,
|
|
110
110
|
with no model call parsing it: at idle it starts that playbook, at its
|
|
111
111
|
own leaf it delivers the rest of the line, an enabled command absent
|
|
112
112
|
from the active path switches to it, and a bare command answers with
|
|
@@ -121,9 +121,11 @@ untouched
|
|
|
121
121
|
([[playbook-captain-1](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-captain.md#playbook-captain-1)],
|
|
122
122
|
[[playbook-captain-2](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-captain.md#playbook-captain-2)]).
|
|
123
123
|
|
|
124
|
-
The current CODE, REVIEW, and
|
|
124
|
+
The current CODE, REVIEW, DECIDE, and DEV workflows take their deterministic
|
|
125
125
|
initial event from the selecting Boss turn. CODE and DECIDE then call REVIEW
|
|
126
|
-
as a nested playbook
|
|
126
|
+
as a nested playbook, while DEV — the repository-aware planner behind
|
|
127
|
+
`/dev` — analyzes a development request and itself calls CODE, or DECIDE and
|
|
128
|
+
then CODE, as nested playbooks. Local role names do not imply continuity: each frame
|
|
127
129
|
uses the exact stable player IDs configured under its `roles` map. Equal IDs
|
|
128
130
|
share one pane and provider conversation across nested and later root
|
|
129
131
|
engagements; distinct IDs remain isolated even when their agent settings are
|
|
@@ -321,8 +323,9 @@ to present the `incomplete` status it receives
|
|
|
321
323
|
For each governed player call, Playbook records a Git baseline before the call
|
|
322
324
|
and a durable receipt afterward. CODE and DECIDE commit arms, and REVIEW's
|
|
323
325
|
Coder commit arm, accept a commit only when the receipt proves exactly one
|
|
324
|
-
descendant commit with no residual repository change; REVIEW's Reviewer calls
|
|
325
|
-
|
|
326
|
+
descendant commit with no residual repository change; REVIEW's Reviewer calls,
|
|
327
|
+
DECIDE's proposal calls, and every DEV Analyst planning call require the
|
|
328
|
+
repository to remain exact. The
|
|
326
329
|
player's prose, including any `Commit:` line, is presentation rather than
|
|
327
330
|
proof.
|
|
328
331
|
|
package/docs/configuration.md
CHANGED
|
@@ -84,6 +84,13 @@ players:
|
|
|
84
84
|
permissions:
|
|
85
85
|
mode: auto # protected auto mode for the Claude Reviewer
|
|
86
86
|
|
|
87
|
+
dev.analyst:
|
|
88
|
+
adapter: claude
|
|
89
|
+
model: claude-opus-5
|
|
90
|
+
effort: xhigh
|
|
91
|
+
permissions:
|
|
92
|
+
mode: auto # protected auto mode for the Claude Analyst
|
|
93
|
+
|
|
87
94
|
playbooks:
|
|
88
95
|
code:
|
|
89
96
|
from: '@sublang/playbook/code/registry'
|
|
@@ -101,6 +108,11 @@ playbooks:
|
|
|
101
108
|
roles:
|
|
102
109
|
coder: dev.coder
|
|
103
110
|
reviewer: dev.reviewer
|
|
111
|
+
|
|
112
|
+
dev:
|
|
113
|
+
from: '@sublang/playbook/dev/registry'
|
|
114
|
+
roles:
|
|
115
|
+
analyst: dev.analyst
|
|
104
116
|
```
|
|
105
117
|
|
|
106
118
|
The current bundled workflows accept no workflow-specific options.
|
|
@@ -146,7 +158,9 @@ Two bindings that name the same player ID deliberately share one sequential
|
|
|
146
158
|
provider conversation throughout the logical Captain session — across nested
|
|
147
159
|
calls, returns, and later root engagements. CODE's and REVIEW's `coder` roles
|
|
148
160
|
therefore share `dev.coder` in the starter, and DECIDE and its nested REVIEW
|
|
149
|
-
share both starter players.
|
|
161
|
+
share both starter players. DEV's `analyst` instead binds the distinct
|
|
162
|
+
`dev.analyst` player, so planning context does not bleed into the shared
|
|
163
|
+
review conversation. Disposal of one playbook frame does not clear that
|
|
150
164
|
session ledger.
|
|
151
165
|
|
|
152
166
|
Two distinct player IDs stay isolated even when their agent blocks are
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sublang/playbook",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Composable XState v5 playbook runtime with compiled Captain, CODE, REVIEW, and
|
|
5
|
+
"description": "Composable XState v5 playbook runtime with compiled Captain, CODE, REVIEW, DECIDE, and DEV workflows driven by GEARS specs.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"homepage": "https://github.com/sublang-ai/playbook#readme",
|
|
8
8
|
"repository": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"reference/sdlc/code.md",
|
|
46
46
|
"reference/sdlc/review.md",
|
|
47
47
|
"reference/sdlc/decide.md",
|
|
48
|
+
"reference/sdlc/dev.md",
|
|
48
49
|
"reference/sdlc/captain.playbook/captain.gears.md",
|
|
49
50
|
"reference/sdlc/captain.playbook/captain.fsm.ts",
|
|
50
51
|
"reference/sdlc/captain.playbook/captain.fsm.js",
|
|
@@ -100,6 +101,16 @@
|
|
|
100
101
|
"reference/sdlc/decide.playbook/decide.registry.ts",
|
|
101
102
|
"reference/sdlc/decide.playbook/decide.registry.js",
|
|
102
103
|
"reference/sdlc/decide.playbook/decide.registry.d.ts",
|
|
104
|
+
"reference/sdlc/dev.playbook/dev.gears.md",
|
|
105
|
+
"reference/sdlc/dev.playbook/dev.fsm.ts",
|
|
106
|
+
"reference/sdlc/dev.playbook/dev.fsm.js",
|
|
107
|
+
"reference/sdlc/dev.playbook/dev.fsm.d.ts",
|
|
108
|
+
"reference/sdlc/dev.playbook/dev.playbook.ts",
|
|
109
|
+
"reference/sdlc/dev.playbook/dev.playbook.js",
|
|
110
|
+
"reference/sdlc/dev.playbook/dev.playbook.d.ts",
|
|
111
|
+
"reference/sdlc/dev.playbook/dev.registry.ts",
|
|
112
|
+
"reference/sdlc/dev.playbook/dev.registry.js",
|
|
113
|
+
"reference/sdlc/dev.playbook/dev.registry.d.ts",
|
|
103
114
|
"docs"
|
|
104
115
|
],
|
|
105
116
|
"bin": {
|
|
@@ -150,6 +161,14 @@
|
|
|
150
161
|
"./decide/registry": {
|
|
151
162
|
"types": "./reference/sdlc/decide.playbook/decide.registry.d.ts",
|
|
152
163
|
"default": "./reference/sdlc/decide.playbook/decide.registry.js"
|
|
164
|
+
},
|
|
165
|
+
"./dev/playbook": {
|
|
166
|
+
"types": "./reference/sdlc/dev.playbook/dev.playbook.d.ts",
|
|
167
|
+
"default": "./reference/sdlc/dev.playbook/dev.playbook.js"
|
|
168
|
+
},
|
|
169
|
+
"./dev/registry": {
|
|
170
|
+
"types": "./reference/sdlc/dev.playbook/dev.registry.d.ts",
|
|
171
|
+
"default": "./reference/sdlc/dev.playbook/dev.registry.js"
|
|
153
172
|
}
|
|
154
173
|
},
|
|
155
174
|
"scripts": {
|
|
@@ -172,7 +191,7 @@
|
|
|
172
191
|
"yaml": "^2.9.0"
|
|
173
192
|
},
|
|
174
193
|
"devDependencies": {
|
|
175
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
194
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.223",
|
|
176
195
|
"@openai/codex-sdk": "^0.146.0",
|
|
177
196
|
"@types/node": "^22.0.0",
|
|
178
197
|
"typescript": "^5.8.0",
|
|
@@ -23,7 +23,6 @@ export type PlayerInput = {
|
|
|
23
23
|
readonly callerInput: string;
|
|
24
24
|
readonly runResults: string;
|
|
25
25
|
readonly irNumber?: string;
|
|
26
|
-
readonly irTask?: string;
|
|
27
26
|
readonly pendingBossQuestion?: PendingBossQuestion;
|
|
28
27
|
readonly bossReply?: string;
|
|
29
28
|
};
|
|
@@ -36,16 +35,18 @@ export type PlayerOutput = {
|
|
|
36
35
|
readonly coderOutput: string;
|
|
37
36
|
readonly latestCommit: string;
|
|
38
37
|
readonly irNumber: string;
|
|
39
|
-
readonly irTask: string;
|
|
40
38
|
} | {
|
|
41
39
|
readonly guard: 'moreTasks';
|
|
42
40
|
readonly coderOutput: string;
|
|
43
41
|
readonly latestCommit: string;
|
|
42
|
+
readonly irNumber: string;
|
|
44
43
|
readonly irTask: string;
|
|
45
44
|
} | {
|
|
46
45
|
readonly guard: 'finalTask';
|
|
47
46
|
readonly coderOutput: string;
|
|
48
47
|
readonly latestCommit: string;
|
|
48
|
+
readonly irNumber: string;
|
|
49
|
+
readonly irTask: string;
|
|
49
50
|
} | {
|
|
50
51
|
readonly guard: 'needsBossReply';
|
|
51
52
|
readonly question: string;
|
|
@@ -57,8 +58,13 @@ export type PlaybookInput = {
|
|
|
57
58
|
readonly text: string;
|
|
58
59
|
};
|
|
59
60
|
export type ReviewOutput = {
|
|
60
|
-
readonly approvedCommit: 'latest';
|
|
61
61
|
readonly noUnsettledFindings: true;
|
|
62
|
+
/**
|
|
63
|
+
* Exact repository revision at which the review scope was evaluated:
|
|
64
|
+
* the last review-fix commit when one landed, otherwise the clean
|
|
65
|
+
* round's receipt-observed HEAD (DR-045). Always present.
|
|
66
|
+
*/
|
|
67
|
+
readonly evaluatedRevision: string;
|
|
62
68
|
};
|
|
63
69
|
export type CompactError = {
|
|
64
70
|
readonly name: string;
|
|
@@ -66,16 +72,16 @@ export type CompactError = {
|
|
|
66
72
|
};
|
|
67
73
|
export type CodePlaybookOutput = {
|
|
68
74
|
readonly status: 'complete';
|
|
69
|
-
/** Exact identity of the
|
|
75
|
+
/** Exact identity of the last CODE-owned commit. */
|
|
70
76
|
readonly lastCodeCommit: string;
|
|
71
|
-
/** Exact
|
|
72
|
-
readonly
|
|
77
|
+
/** Exact repository revision the final passing review evaluated. */
|
|
78
|
+
readonly finalEvaluatedRevision: string;
|
|
79
|
+
/** Every phase's review passed with no unsettled findings. */
|
|
80
|
+
readonly allReviewsPassed: true;
|
|
73
81
|
} | {
|
|
74
82
|
readonly status: 'review-failed';
|
|
75
|
-
/** Exact identity of the
|
|
83
|
+
/** Exact identity of the last CODE-owned commit. */
|
|
76
84
|
readonly lastCodeCommit: string;
|
|
77
|
-
/** Exact Coder final text produced after that commit. */
|
|
78
|
-
readonly lastCodeOutput: string;
|
|
79
85
|
readonly error: CompactError;
|
|
80
86
|
};
|
|
81
87
|
export type CodingInput = {
|
|
@@ -88,7 +94,7 @@ export type CodingContext = {
|
|
|
88
94
|
readonly latestCommit?: string;
|
|
89
95
|
readonly irNumber?: string;
|
|
90
96
|
readonly irTask?: string;
|
|
91
|
-
readonly
|
|
97
|
+
readonly evaluatedRevision?: string;
|
|
92
98
|
readonly phase?: CodePhase;
|
|
93
99
|
readonly completion?: 'complete' | 'review-failed';
|
|
94
100
|
readonly reviewError?: CompactError;
|
|
@@ -146,9 +152,6 @@ export declare const codingMachine: import("xstate").StateMachine<CodingContext,
|
|
|
146
152
|
} | {
|
|
147
153
|
type: "rememberFinalTask";
|
|
148
154
|
params: import("xstate").NonReducibleUnknown;
|
|
149
|
-
} | {
|
|
150
|
-
type: "advanceToNextIrTask";
|
|
151
|
-
params: import("xstate").NonReducibleUnknown;
|
|
152
155
|
} | {
|
|
153
156
|
type: "rememberPendingQuestion";
|
|
154
157
|
params: import("xstate").NonReducibleUnknown;
|
|
@@ -211,16 +214,16 @@ export declare const codingMachine: import("xstate").StateMachine<CodingContext,
|
|
|
211
214
|
params: unknown;
|
|
212
215
|
}, never, "done" | "failed" | "awaitBossReply" | "ready" | "runFirstPhase" | "reviewFirstCommit" | "runIrTask" | "reviewIrTask" | "reportedReviewFailure", string, CodingInput, {
|
|
213
216
|
readonly status: "complete";
|
|
214
|
-
/** Exact identity of the
|
|
217
|
+
/** Exact identity of the last CODE-owned commit. */
|
|
215
218
|
readonly lastCodeCommit: string;
|
|
216
|
-
/** Exact
|
|
217
|
-
readonly
|
|
219
|
+
/** Exact repository revision the final passing review evaluated. */
|
|
220
|
+
readonly finalEvaluatedRevision: string;
|
|
221
|
+
/** Every phase's review passed with no unsettled findings. */
|
|
222
|
+
readonly allReviewsPassed: true;
|
|
218
223
|
} | {
|
|
219
224
|
readonly status: "review-failed";
|
|
220
|
-
/** Exact identity of the
|
|
225
|
+
/** Exact identity of the last CODE-owned commit. */
|
|
221
226
|
readonly lastCodeCommit: string;
|
|
222
|
-
/** Exact Coder final text produced after that commit. */
|
|
223
|
-
readonly lastCodeOutput: string;
|
|
224
227
|
readonly error: CompactError;
|
|
225
228
|
}, import("xstate").EventObject, import("xstate").MetaObject, {
|
|
226
229
|
id: "code";
|
|
@@ -3,56 +3,70 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Generated by slc/gears2fsm.md from ./code.gears.md.
|
|
5
5
|
import { assign, fromPromise, setup } from 'xstate';
|
|
6
|
+
const APPENDED_PHASE_PROMPT = [
|
|
7
|
+
'Keep to the original intent and follow what it asks.',
|
|
8
|
+
'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
|
|
9
|
+
"Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
|
|
10
|
+
'Make the commit message explain concisely what changed and why, including relevant verification.',
|
|
11
|
+
'Identify every new commit you make.',
|
|
12
|
+
'Coder is <coder-llm>.',
|
|
13
|
+
];
|
|
6
14
|
const FIRST_PHASE_PROMPT = [
|
|
7
15
|
'> <caller-input>',
|
|
8
16
|
'> <run-results>',
|
|
9
17
|
'',
|
|
10
|
-
'
|
|
11
|
-
'If
|
|
12
|
-
'
|
|
18
|
+
'First determine whether the coding request starts a new coding intent or continues an existing IR with unfinished work.',
|
|
19
|
+
'If the request may continue an existing IR but does not identify it unambiguously, ask Boss before changing files.',
|
|
20
|
+
'',
|
|
21
|
+
'For a new coding intent, assess whether it can be completed well in one commit.',
|
|
22
|
+
'If it can, implement and test it, update the affected specs, and ensure @specs/map.md remains accurate.',
|
|
23
|
+
'If it cannot, decompose it into tasks sized to exactly one commit each, add a new IR under @specs/intents, and do not implement any IR task in this phase.',
|
|
13
24
|
'Plan affected spec updates before, with, or after their corresponding code changes, either as standalone IR tasks or as explicit work within related tasks.',
|
|
14
25
|
'',
|
|
26
|
+
'For an existing IR, read the identified IR and implement exactly its next unfinished task, including corresponding tests or specs if any.',
|
|
27
|
+
'Do not implement a later task in this phase.',
|
|
28
|
+
"Mark the IR's progress and deliverables when relevant.",
|
|
29
|
+
'If the IR will be finished after this phase, double-check that all acceptance criteria are met.',
|
|
30
|
+
'',
|
|
15
31
|
'Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.',
|
|
16
32
|
'',
|
|
17
|
-
|
|
18
|
-
"Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
|
|
19
|
-
'Make the commit message explain concisely what changed and why, including relevant verification.',
|
|
20
|
-
'Coder is <coder-llm>; format the model token in conventional human form.',
|
|
33
|
+
...APPENDED_PHASE_PROMPT,
|
|
21
34
|
].join('\n');
|
|
22
35
|
const IR_TASK_PROMPT = [
|
|
23
|
-
'> <
|
|
36
|
+
'> <caller-input>',
|
|
37
|
+
'> <ir-number>',
|
|
24
38
|
'> <run-results>',
|
|
25
39
|
'',
|
|
26
|
-
'Read IR
|
|
40
|
+
'Read the identified IR and implement exactly its next unfinished task, including corresponding tests or specs if any.',
|
|
27
41
|
'Do not implement a later task in this phase.',
|
|
28
42
|
"Mark the IR's progress and deliverables when relevant.",
|
|
29
43
|
'If the IR will be finished after this phase, double-check that all acceptance criteria are met.',
|
|
30
44
|
'',
|
|
31
|
-
|
|
32
|
-
"Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
|
|
33
|
-
'Make the commit message explain concisely what changed and why, including relevant verification.',
|
|
34
|
-
'Coder is <coder-llm>; format the model token in conventional human form.',
|
|
45
|
+
...APPENDED_PHASE_PROMPT,
|
|
35
46
|
].join('\n');
|
|
47
|
+
const NEEDS_BOSS_REPLY_RESULT = "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>`.";
|
|
36
48
|
const FIRST_PHASE_RESULTS = {
|
|
37
49
|
directCommit: 'Coder completed and committed the direct implementation phase. Output shall include `coderOutput: <verbatim final text>` and `latestCommit: <commit identity>`.',
|
|
38
|
-
irCommit: 'Coder created and committed a new IR without implementing an IR task. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`,
|
|
39
|
-
|
|
50
|
+
irCommit: 'Coder created and committed a new IR without implementing an IR task. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, and `irNumber` identifying the created IR.',
|
|
51
|
+
moreTasks: 'Coder continued an existing IR, completed and committed exactly its next unfinished task, and at least one task remains. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber` identifying the continued IR, and `irTask` naming the implemented task.',
|
|
52
|
+
finalTask: 'Coder continued an existing IR and completed and committed its final task. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber` identifying the continued IR, and `irTask` naming the implemented task.',
|
|
53
|
+
needsBossReply: NEEDS_BOSS_REPLY_RESULT,
|
|
40
54
|
};
|
|
41
55
|
const IR_TASK_RESULTS = {
|
|
42
|
-
moreTasks:
|
|
43
|
-
finalTask:
|
|
44
|
-
needsBossReply:
|
|
56
|
+
moreTasks: "Coder completed and committed exactly the IR's next unfinished task and at least one task remains. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber` identifying the continued IR, and `irTask` naming the implemented task.",
|
|
57
|
+
finalTask: "Coder completed and committed the IR's final task. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber` identifying the continued IR, and `irTask` naming the implemented task.",
|
|
58
|
+
needsBossReply: NEEDS_BOSS_REPLY_RESULT,
|
|
45
59
|
};
|
|
46
60
|
const STATE_DESCRIPTIONS = {
|
|
47
61
|
ready: 'Waiting for a coding intent.',
|
|
48
|
-
runFirstPhase: 'Coder is
|
|
49
|
-
reviewFirstCommit: 'The REVIEW playbook is checking the
|
|
62
|
+
runFirstPhase: 'Coder is running the first coding phase: a direct implementation, a new intent record, or an existing intent-record task.',
|
|
63
|
+
reviewFirstCommit: 'The REVIEW playbook is checking the direct or new-IR phase commit.',
|
|
50
64
|
runIrTask: 'Coder is implementing exactly one unfinished intent-record task.',
|
|
51
65
|
reviewIrTask: 'The REVIEW playbook is checking the latest task commit.',
|
|
52
66
|
awaitBossReply: 'Waiting for Boss to answer Coder.',
|
|
53
67
|
failed: 'The coding workflow failed and is waiting for a new coding intent.',
|
|
54
68
|
reportedReviewFailure: 'The coding workflow reported a REVIEW failure and the last code-owned commit.',
|
|
55
|
-
done:
|
|
69
|
+
done: "The coding workflow completed after every phase's REVIEW passed with no unsettled findings.",
|
|
56
70
|
};
|
|
57
71
|
function playbookMeta(stateId, role) {
|
|
58
72
|
return {
|
|
@@ -89,6 +103,11 @@ function hasCommittedOutput(event) {
|
|
|
89
103
|
return (outputString(event, 'coderOutput') !== undefined &&
|
|
90
104
|
outputString(event, 'latestCommit') !== undefined);
|
|
91
105
|
}
|
|
106
|
+
function hasIrTaskOutput(event) {
|
|
107
|
+
return (hasCommittedOutput(event) &&
|
|
108
|
+
outputString(event, 'irNumber') !== undefined &&
|
|
109
|
+
outputString(event, 'irTask') !== undefined);
|
|
110
|
+
}
|
|
92
111
|
function isDirectCommit({ event }) {
|
|
93
112
|
return (outputGuard(event, 'directCommit') &&
|
|
94
113
|
hasCommittedOutput(event));
|
|
@@ -96,17 +115,15 @@ function isDirectCommit({ event }) {
|
|
|
96
115
|
function isIrCommit({ event }) {
|
|
97
116
|
return (outputGuard(event, 'irCommit') &&
|
|
98
117
|
hasCommittedOutput(event) &&
|
|
99
|
-
outputString(event, 'irNumber') !== undefined
|
|
100
|
-
outputString(event, 'irTask') !== undefined);
|
|
118
|
+
outputString(event, 'irNumber') !== undefined);
|
|
101
119
|
}
|
|
102
120
|
function isMoreTasks({ event }) {
|
|
103
121
|
return (outputGuard(event, 'moreTasks') &&
|
|
104
|
-
|
|
105
|
-
outputString(event, 'irTask') !== undefined);
|
|
122
|
+
hasIrTaskOutput(event));
|
|
106
123
|
}
|
|
107
124
|
function isFinalTask({ event }) {
|
|
108
125
|
return (outputGuard(event, 'finalTask') &&
|
|
109
|
-
|
|
126
|
+
hasIrTaskOutput(event));
|
|
110
127
|
}
|
|
111
128
|
function needsBossReply({ event }) {
|
|
112
129
|
return (outputGuard(event, 'needsBossReply') &&
|
|
@@ -116,11 +133,14 @@ function reviewOutput(event) {
|
|
|
116
133
|
return isRecord(event) ? event.output : undefined;
|
|
117
134
|
}
|
|
118
135
|
function isReviewApprovedValue(value) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
if (!isRecord(value))
|
|
137
|
+
return false;
|
|
138
|
+
if (Reflect.ownKeys(value).some((key) => typeof key !== 'string' ||
|
|
139
|
+
(key !== 'noUnsettledFindings' && key !== 'evaluatedRevision'))) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return (value.noUnsettledFindings === true &&
|
|
143
|
+
isNonEmptyString(value.evaluatedRevision));
|
|
124
144
|
}
|
|
125
145
|
function reviewApprovedFor(phase) {
|
|
126
146
|
return ({ context, event }) => context.phase === phase && isReviewApprovedValue(reviewOutput(event));
|
|
@@ -265,16 +285,23 @@ function quoteRelay(label, value) {
|
|
|
265
285
|
const lines = value.split('\n');
|
|
266
286
|
return [`> ${label}: ${lines[0] ?? ''}`, ...lines.slice(1).map((line) => `> ${line}`)].join('\n');
|
|
267
287
|
}
|
|
268
|
-
function
|
|
288
|
+
function reviewScopeLine(context) {
|
|
289
|
+
return (`> Review scope: the commit ${context.latestCommit ?? ''} ` +
|
|
290
|
+
'from this coding phase and its resulting repository state.');
|
|
291
|
+
}
|
|
292
|
+
function newIntentReviewText(context) {
|
|
269
293
|
return [
|
|
270
|
-
quoteRelay('
|
|
294
|
+
quoteRelay('Original intent', context.callerInput ?? ''),
|
|
295
|
+
reviewScopeLine(context),
|
|
271
296
|
quoteRelay('Coder output', context.coderOutput ?? ''),
|
|
272
297
|
].join('\n');
|
|
273
298
|
}
|
|
274
299
|
function irTaskReviewText(context) {
|
|
275
300
|
return [
|
|
276
|
-
quoteRelay('
|
|
301
|
+
quoteRelay('Original intent', context.callerInput ?? ''),
|
|
302
|
+
reviewScopeLine(context),
|
|
277
303
|
quoteRelay('Coder output', context.coderOutput ?? ''),
|
|
304
|
+
quoteRelay('Current IR task', context.irTask ?? ''),
|
|
278
305
|
].join('\n');
|
|
279
306
|
}
|
|
280
307
|
const machineSetup = setup({
|
|
@@ -320,7 +347,7 @@ const machineSetup = setup({
|
|
|
320
347
|
latestCommit: undefined,
|
|
321
348
|
irNumber: undefined,
|
|
322
349
|
irTask: undefined,
|
|
323
|
-
|
|
350
|
+
evaluatedRevision: undefined,
|
|
324
351
|
phase: undefined,
|
|
325
352
|
completion: undefined,
|
|
326
353
|
reviewError: undefined,
|
|
@@ -340,8 +367,7 @@ const machineSetup = setup({
|
|
|
340
367
|
coderOutput: outputString(event, 'coderOutput'),
|
|
341
368
|
latestCommit: outputString(event, 'latestCommit'),
|
|
342
369
|
irNumber: outputString(event, 'irNumber'),
|
|
343
|
-
irTask:
|
|
344
|
-
nextIrTask: undefined,
|
|
370
|
+
irTask: undefined,
|
|
345
371
|
phase: 'ir-created',
|
|
346
372
|
pendingBossQuestion: undefined,
|
|
347
373
|
bossReply: undefined,
|
|
@@ -349,7 +375,8 @@ const machineSetup = setup({
|
|
|
349
375
|
rememberMoreTasks: assign(({ event }) => ({
|
|
350
376
|
coderOutput: outputString(event, 'coderOutput'),
|
|
351
377
|
latestCommit: outputString(event, 'latestCommit'),
|
|
352
|
-
|
|
378
|
+
irNumber: outputString(event, 'irNumber'),
|
|
379
|
+
irTask: outputString(event, 'irTask'),
|
|
353
380
|
phase: 'ir-task-more',
|
|
354
381
|
pendingBossQuestion: undefined,
|
|
355
382
|
bossReply: undefined,
|
|
@@ -357,15 +384,12 @@ const machineSetup = setup({
|
|
|
357
384
|
rememberFinalTask: assign(({ event }) => ({
|
|
358
385
|
coderOutput: outputString(event, 'coderOutput'),
|
|
359
386
|
latestCommit: outputString(event, 'latestCommit'),
|
|
360
|
-
|
|
387
|
+
irNumber: outputString(event, 'irNumber'),
|
|
388
|
+
irTask: outputString(event, 'irTask'),
|
|
361
389
|
phase: 'ir-task-final',
|
|
362
390
|
pendingBossQuestion: undefined,
|
|
363
391
|
bossReply: undefined,
|
|
364
392
|
})),
|
|
365
|
-
advanceToNextIrTask: assign(({ context }) => ({
|
|
366
|
-
irTask: context.nextIrTask,
|
|
367
|
-
nextIrTask: undefined,
|
|
368
|
-
})),
|
|
369
393
|
rememberPendingQuestion: assign(({ context, event }) => {
|
|
370
394
|
const question = outputString(event, 'question');
|
|
371
395
|
const output = playerOutput(event);
|
|
@@ -390,9 +414,14 @@ const machineSetup = setup({
|
|
|
390
414
|
pendingBossQuestion: undefined,
|
|
391
415
|
bossReply: undefined,
|
|
392
416
|
}),
|
|
393
|
-
completeSuccessfully: assign({
|
|
394
|
-
|
|
395
|
-
|
|
417
|
+
completeSuccessfully: assign(({ event }) => {
|
|
418
|
+
const output = reviewOutput(event);
|
|
419
|
+
const approved = isReviewApprovedValue(output) ? output : undefined;
|
|
420
|
+
return {
|
|
421
|
+
completion: 'complete',
|
|
422
|
+
evaluatedRevision: approved?.evaluatedRevision,
|
|
423
|
+
reviewError: undefined,
|
|
424
|
+
};
|
|
396
425
|
}),
|
|
397
426
|
completeWithReviewFailure: assign(({ event }) => ({
|
|
398
427
|
completion: 'review-failed',
|
|
@@ -426,19 +455,26 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
426
455
|
if (!isNonEmptyString(lastCodeCommit)) {
|
|
427
456
|
throw new Error('CODE reached a final state without a commit identity');
|
|
428
457
|
}
|
|
429
|
-
const lastCodeOutput = context.coderOutput ?? '';
|
|
430
458
|
if (context.completion === 'review-failed') {
|
|
431
459
|
return {
|
|
432
460
|
status: 'review-failed',
|
|
433
461
|
lastCodeCommit,
|
|
434
|
-
lastCodeOutput,
|
|
435
462
|
error: context.reviewError ?? {
|
|
436
463
|
name: 'Error',
|
|
437
464
|
message: 'REVIEW failed without error detail.',
|
|
438
465
|
},
|
|
439
466
|
};
|
|
440
467
|
}
|
|
441
|
-
|
|
468
|
+
const finalEvaluatedRevision = context.evaluatedRevision;
|
|
469
|
+
if (!isNonEmptyString(finalEvaluatedRevision)) {
|
|
470
|
+
throw new Error('CODE completed without an evaluated repository revision');
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
status: 'complete',
|
|
474
|
+
lastCodeCommit,
|
|
475
|
+
finalEvaluatedRevision,
|
|
476
|
+
allReviewsPassed: true,
|
|
477
|
+
};
|
|
442
478
|
},
|
|
443
479
|
states: {
|
|
444
480
|
ready: {
|
|
@@ -503,6 +539,36 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
503
539
|
'rememberIrCommit',
|
|
504
540
|
],
|
|
505
541
|
},
|
|
542
|
+
{
|
|
543
|
+
guard: 'isMoreTasks',
|
|
544
|
+
target: 'reviewIrTask',
|
|
545
|
+
actions: [
|
|
546
|
+
{
|
|
547
|
+
type: 'playbook.acceptedOutcome',
|
|
548
|
+
params: {
|
|
549
|
+
source: 'runFirstPhase',
|
|
550
|
+
target: 'reviewIrTask',
|
|
551
|
+
acceptedOutcome: 'moreTasks',
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
'rememberMoreTasks',
|
|
555
|
+
],
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
guard: 'isFinalTask',
|
|
559
|
+
target: 'reviewIrTask',
|
|
560
|
+
actions: [
|
|
561
|
+
{
|
|
562
|
+
type: 'playbook.acceptedOutcome',
|
|
563
|
+
params: {
|
|
564
|
+
source: 'runFirstPhase',
|
|
565
|
+
target: 'reviewIrTask',
|
|
566
|
+
acceptedOutcome: 'finalTask',
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
'rememberFinalTask',
|
|
570
|
+
],
|
|
571
|
+
},
|
|
506
572
|
{
|
|
507
573
|
guard: 'needsBossReply',
|
|
508
574
|
target: 'awaitBossReply',
|
|
@@ -537,7 +603,7 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
537
603
|
stateId: 'reviewFirstCommit',
|
|
538
604
|
sourceItem: 'CODE-2',
|
|
539
605
|
playbookId: 'review',
|
|
540
|
-
text:
|
|
606
|
+
text: newIntentReviewText(context),
|
|
541
607
|
}),
|
|
542
608
|
onDone: [
|
|
543
609
|
{
|
|
@@ -582,7 +648,6 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
582
648
|
...(context.irNumber === undefined
|
|
583
649
|
? {}
|
|
584
650
|
: { irNumber: context.irNumber }),
|
|
585
|
-
...(context.irTask === undefined ? {} : { irTask: context.irTask }),
|
|
586
651
|
...(context.pendingBossQuestion === undefined
|
|
587
652
|
? {}
|
|
588
653
|
: { pendingBossQuestion: context.pendingBossQuestion }),
|
|
@@ -661,7 +726,6 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
661
726
|
{
|
|
662
727
|
guard: 'reviewApprovedMoreTasks',
|
|
663
728
|
target: 'runIrTask',
|
|
664
|
-
actions: 'advanceToNextIrTask',
|
|
665
729
|
},
|
|
666
730
|
{
|
|
667
731
|
guard: 'reviewApprovedFinalTask',
|