@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.
Files changed (43) hide show
  1. package/docs/cli.md +9 -6
  2. package/docs/configuration.md +15 -1
  3. package/package.json +22 -3
  4. package/reference/sdlc/code.playbook/code.fsm.d.ts +22 -19
  5. package/reference/sdlc/code.playbook/code.fsm.js +116 -52
  6. package/reference/sdlc/code.playbook/code.fsm.ts +149 -64
  7. package/reference/sdlc/code.playbook/code.gears.md +40 -20
  8. package/reference/sdlc/code.playbook/code.playbook.js +23 -2
  9. package/reference/sdlc/code.playbook/code.playbook.ts +23 -2
  10. package/reference/sdlc/code.playbook/playbook.config.template.yaml +18 -2
  11. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +13 -6
  12. package/reference/sdlc/decide.playbook/decide.fsm.js +54 -27
  13. package/reference/sdlc/decide.playbook/decide.fsm.ts +68 -29
  14. package/reference/sdlc/decide.playbook/decide.gears.md +25 -19
  15. package/reference/sdlc/decide.playbook/decide.playbook.js +11 -3
  16. package/reference/sdlc/decide.playbook/decide.playbook.ts +11 -3
  17. package/reference/sdlc/decide.playbook/decide.registry.js +1 -1
  18. package/reference/sdlc/decide.playbook/decide.registry.ts +1 -1
  19. package/reference/sdlc/dev.md +52 -0
  20. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +261 -0
  21. package/reference/sdlc/dev.playbook/dev.fsm.js +723 -0
  22. package/reference/sdlc/dev.playbook/dev.fsm.ts +988 -0
  23. package/reference/sdlc/dev.playbook/dev.gears.md +91 -0
  24. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +21 -0
  25. package/reference/sdlc/dev.playbook/dev.playbook.js +143 -0
  26. package/reference/sdlc/dev.playbook/dev.playbook.ts +246 -0
  27. package/reference/sdlc/dev.playbook/dev.registry.d.ts +40 -0
  28. package/reference/sdlc/dev.playbook/dev.registry.js +64 -0
  29. package/reference/sdlc/dev.playbook/dev.registry.ts +120 -0
  30. package/reference/sdlc/review.playbook/review.fsm.d.ts +15 -2
  31. package/reference/sdlc/review.playbook/review.fsm.js +77 -27
  32. package/reference/sdlc/review.playbook/review.fsm.ts +96 -30
  33. package/reference/sdlc/review.playbook/review.gears.md +52 -26
  34. package/reference/sdlc/review.playbook/review.playbook.js +17 -7
  35. package/reference/sdlc/review.playbook/review.playbook.ts +17 -7
  36. package/reference/sdlc/review.playbook/review.registry.js +1 -1
  37. package/reference/sdlc/review.playbook/review.registry.ts +1 -1
  38. package/slc/link.md +12 -5
  39. package/slc/text2gears.md +3 -0
  40. package/src/xstate-playbook-runtime.js +5 -2
  41. package/src/xstate-playbook-runtime.ts +5 -2
  42. package/src/xstate-runtime.js +13 -1
  43. 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 `/decide` followed by a task to select one of the bundled
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 DECIDE workflows take their deterministic
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. Local role names do not imply continuity: each frame
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
- and DECIDE's proposal calls require the repository to remain exact. The
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
 
@@ -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. Disposal of one playbook frame does not clear that
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": "11.0.0",
3
+ "version": "12.0.0",
4
4
  "type": "module",
5
- "description": "Composable XState v5 playbook runtime with compiled Captain, CODE, REVIEW, and DECIDE workflows driven by GEARS specs.",
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.221",
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 latest CODE-owned commit. */
75
+ /** Exact identity of the last CODE-owned commit. */
70
76
  readonly lastCodeCommit: string;
71
- /** Exact Coder final text produced after that commit. */
72
- readonly lastCodeOutput: string;
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 latest CODE-owned commit. */
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 nextIrTask?: string;
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 latest CODE-owned commit. */
217
+ /** Exact identity of the last CODE-owned commit. */
215
218
  readonly lastCodeCommit: string;
216
- /** Exact Coder final text produced after that commit. */
217
- readonly lastCodeOutput: string;
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 latest CODE-owned commit. */
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
- 'Assess whether the coding intent can be completed well in one commit.',
11
- 'If yes, implement and test it, update the affected specs, and ensure @specs/map.md remains accurate.',
12
- 'Otherwise, 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.',
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
- 'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
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
- '> <ir-task>',
36
+ '> <caller-input>',
37
+ '> <ir-number>',
24
38
  '> <run-results>',
25
39
  '',
26
- 'Read IR-<#> and implement exactly the next unfinished task, including corresponding tests or specs if any.',
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
- 'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
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>`, `irNumber`, and `irTask` naming the exact next unfinished task.',
39
- needsBossReply: "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>`.",
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: 'Coder completed and committed the current IR task and at least one task remains. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, and `irTask` naming the exact next unfinished task.',
43
- finalTask: 'Coder completed and committed the final IR task. Output shall include `coderOutput: <verbatim final text>` and `latestCommit: <commit identity>`.',
44
- needsBossReply: "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>`.",
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 implementing one direct phase or committing a new intent record.',
49
- reviewFirstCommit: 'The REVIEW playbook is checking the first phase commit.',
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: 'The coding workflow completed after REVIEW found no unsettled findings.',
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
- hasCommittedOutput(event) &&
105
- outputString(event, 'irTask') !== undefined);
122
+ hasIrTaskOutput(event));
106
123
  }
107
124
  function isFinalTask({ event }) {
108
125
  return (outputGuard(event, 'finalTask') &&
109
- hasCommittedOutput(event));
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
- return (isRecord(value) &&
120
- Object.keys(value).sort().join('\0') ===
121
- ['approvedCommit', 'noUnsettledFindings'].sort().join('\0') &&
122
- value.approvedCommit === 'latest' &&
123
- value.noUnsettledFindings === true);
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 initialReviewText(context) {
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('Initial intent', context.callerInput ?? ''),
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('IR task', context.irTask ?? ''),
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
- nextIrTask: undefined,
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: outputString(event, '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
- nextIrTask: outputString(event, 'irTask'),
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
- nextIrTask: undefined,
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
- completion: 'complete',
395
- reviewError: undefined,
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
- return { status: 'complete', lastCodeCommit, lastCodeOutput };
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: initialReviewText(context),
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',