@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.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/docs/embedding.md +9 -7
  3. package/package.json +3 -1
  4. package/reference/sdlc/branch.playbook/branch.fsm.d.ts +63 -33
  5. package/reference/sdlc/branch.playbook/branch.fsm.js +190 -108
  6. package/reference/sdlc/branch.playbook/branch.fsm.ts +269 -147
  7. package/reference/sdlc/branch.playbook/branch.gears.md +7 -17
  8. package/reference/sdlc/branch.playbook/branch.playbook.d.ts +12 -16
  9. package/reference/sdlc/branch.playbook/branch.playbook.js +112 -101
  10. package/reference/sdlc/branch.playbook/branch.playbook.ts +128 -171
  11. package/reference/sdlc/branch.playbook/branch.registry.d.ts +3 -2
  12. package/reference/sdlc/branch.playbook/branch.registry.ts +6 -2
  13. package/reference/sdlc/code.playbook/bin/repository-effects.js +54 -13
  14. package/reference/sdlc/code.playbook/code.fsm.d.ts +143 -90
  15. package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +8 -8
  16. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +13 -13
  17. package/reference/sdlc/code.playbook/code.fsm.js +609 -649
  18. package/reference/sdlc/code.playbook/code.fsm.ts +890 -821
  19. package/reference/sdlc/code.playbook/code.gears.md +39 -29
  20. package/reference/sdlc/code.playbook/code.playbook.d.ts +13 -16
  21. package/reference/sdlc/code.playbook/code.playbook.js +166 -147
  22. package/reference/sdlc/code.playbook/code.playbook.ts +180 -229
  23. package/reference/sdlc/code.playbook/code.registry.d.ts +3 -2
  24. package/reference/sdlc/code.playbook/code.registry.ts +6 -2
  25. package/reference/sdlc/code.playbook/playbook-captain.d.ts +42 -0
  26. package/reference/sdlc/code.playbook/playbook-captain.js +829 -194
  27. package/reference/sdlc/code.playbook/playbook-captain.ts +1227 -220
  28. package/reference/sdlc/decide.md +1 -0
  29. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +270 -154
  30. package/reference/sdlc/decide.playbook/decide.fsm.js +765 -732
  31. package/reference/sdlc/decide.playbook/decide.fsm.ts +1167 -945
  32. package/reference/sdlc/decide.playbook/decide.gears.md +28 -21
  33. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +11 -67
  34. package/reference/sdlc/decide.playbook/decide.playbook.js +155 -3570
  35. package/reference/sdlc/decide.playbook/decide.playbook.ts +186 -5061
  36. package/reference/sdlc/decide.playbook/decide.registry.d.ts +8 -4
  37. package/reference/sdlc/decide.playbook/decide.registry.js +2 -2
  38. package/reference/sdlc/decide.playbook/decide.registry.ts +13 -6
  39. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +113 -131
  40. package/reference/sdlc/dev.playbook/dev.fsm.js +517 -575
  41. package/reference/sdlc/dev.playbook/dev.fsm.ts +667 -787
  42. package/reference/sdlc/dev.playbook/dev.gears.md +52 -54
  43. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +13 -10
  44. package/reference/sdlc/dev.playbook/dev.playbook.js +128 -120
  45. package/reference/sdlc/dev.playbook/dev.playbook.ts +143 -193
  46. package/reference/sdlc/dev.playbook/dev.registry.d.ts +3 -2
  47. package/reference/sdlc/dev.playbook/dev.registry.ts +6 -2
  48. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +92 -85
  49. package/reference/sdlc/pr.playbook/pr.fsm.js +461 -518
  50. package/reference/sdlc/pr.playbook/pr.fsm.ts +533 -663
  51. package/reference/sdlc/pr.playbook/pr.gears.md +47 -63
  52. package/reference/sdlc/pr.playbook/pr.playbook.d.ts +13 -23
  53. package/reference/sdlc/pr.playbook/pr.playbook.js +118 -112
  54. package/reference/sdlc/pr.playbook/pr.playbook.ts +136 -190
  55. package/reference/sdlc/pr.playbook/pr.registry.d.ts +3 -2
  56. package/reference/sdlc/pr.playbook/pr.registry.ts +4 -2
  57. package/reference/sdlc/review.md +3 -3
  58. package/reference/sdlc/review.playbook/review.fsm.d.ts +150 -116
  59. package/reference/sdlc/review.playbook/review.fsm.js +479 -550
  60. package/reference/sdlc/review.playbook/review.fsm.ts +717 -662
  61. package/reference/sdlc/review.playbook/review.gears.md +32 -29
  62. package/reference/sdlc/review.playbook/review.playbook.d.ts +12 -12
  63. package/reference/sdlc/review.playbook/review.playbook.js +180 -155
  64. package/reference/sdlc/review.playbook/review.playbook.ts +190 -230
  65. package/reference/sdlc/review.playbook/review.registry.d.ts +6 -5
  66. package/reference/sdlc/review.playbook/review.registry.js +3 -3
  67. package/reference/sdlc/review.playbook/review.registry.ts +9 -5
  68. package/slc/gears2fsm.md +72 -3
  69. package/slc/link.md +50 -27
  70. package/slc/materialize-link.mjs +51 -13
  71. package/slc/optimize.md +4 -1
  72. package/slc/prefix-prompts.mjs +191 -0
  73. package/slc/prefix.md +203 -0
  74. package/slc/slc.pin-inputs.json +11 -1
  75. package/slc/text2gears.md +71 -11
  76. package/src/xstate-playbook-runtime.d.ts +82 -9
  77. package/src/xstate-playbook-runtime.js +1224 -283
  78. package/src/xstate-playbook-runtime.ts +1671 -338
  79. package/src/xstate-runtime.d.ts +3 -1
  80. package/src/xstate-runtime.js +9 -2
  81. package/src/xstate-runtime.ts +12 -2
@@ -1,11 +1,15 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
- //
4
- // Generated by slc/gears2fsm.md from ./branch.gears.md.
5
3
  import { assign, fromPromise, setup } from 'xstate';
4
+ /**
5
+ * Stable working-leaf ids that may suspend on a Boss question and be resumed
6
+ * by `BOSS_REPLY`. The machine has at most one active player task, so the
7
+ * scalar Boss-reply form applies.
8
+ */
9
+ const RESUMABLE_STATE_IDS = ['createBranch'];
10
+ /** No parallel group in this playbook. */
11
+ export const concurrentRoleSets = [];
6
12
  const CREATE_BRANCH_PROMPT = [
7
- '> Original request: <caller-input>',
8
- '',
9
13
  'Prepare a new branch for this work without changing any file or making any commit.',
10
14
  'Identify the GitHub issue the request names, if any, and read it with its comments (`gh issue view --comments` with the issue number).',
11
15
  'If the request could refer to more than one issue, or does not say which work to branch for, ask Boss before creating anything.',
@@ -14,19 +18,22 @@ const CREATE_BRANCH_PROMPT = [
14
18
  'Create the branch from the current commit and check it out; do not pull, reset, stash, or move HEAD to another commit.',
15
19
  'Report the exact branch name, the commit it was created from, and a concise summary of the issue and its comments, or of the request when no issue is named.',
16
20
  'If the working tree is not clean, `gh` is not authenticated, the named issue does not exist or cannot be read, or a branch with that name already exists locally or on the remote, create nothing and report the failure with its reason.',
21
+ '',
22
+ '> Original request: <caller-input>',
17
23
  ].join('\n');
24
+ 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>`.";
18
25
  const CREATE_BRANCH_RESULTS = {
19
- branched: 'Coder created the new branch from the current commit and checked it out, reporting its exact name and a concise summary of the issue and its comments, or of the request when no issue is named; the absence of a reported obstacle is not support. Output shall include `branch: <exact branch name>`, `baseRevision: <repository revision>`, and `issueSummary: <concise summary>`.',
20
- refused: 'Coder created nothing and reported the failure with its reason: the working tree is not clean, `gh` is not authenticated, the named issue does not exist or cannot be read, or a branch with that name already exists locally or on the remote. Output shall include `coderOutput: <verbatim final text>`.',
21
- 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>`.",
26
+ branched: "Coder affirmatively reported that it created the new branch from the current commit and checked it out, with the exact branch name and a concise summary of the issue and its comments, or of the request when no issue is named; the absence of a reported obstacle is not support. The branch workflow is then complete and returns the exact branch name, the exact base revision taken from repository authority, and the issue summary to its caller. Output shall include `branch: <exact branch name>`, `baseRevision: <exact base revision from repository authority, not from Coder's prose>`, and `issueSummary: <concise summary of the issue and its comments, or of the request when no issue is named>`.",
27
+ refused: "Coder affirmatively reported that it created no branch and gave the failure with its reason, such as a working tree that is not clean, gh not being authenticated, a named issue that does not exist or cannot be read, or a branch with that name already existing locally or on the remote. The branch workflow then fails and reports Coder's complete result with its reason to its caller; no branch was created. Output shall include `coderOutput: <verbatim final text>`.",
28
+ needsBossReply: NEEDS_BOSS_REPLY_RESULT,
22
29
  };
23
30
  const STATE_DESCRIPTIONS = {
24
- ready: 'Waiting for a development request to branch for.',
25
- createBranch: 'Coder is reading the named issue and creating and checking out a new branch at the current commit.',
26
- awaitBossReply: 'Waiting for Boss to answer Coder.',
27
- failed: 'The branch workflow failed and is waiting for a new development request.',
28
- branched: "A new branch for the request is checked out at the caller's current commit; no file changed and no commit was made.",
29
- refused: 'No branch was created: Coder reported the obstacle with its reason — a dirty working tree, an unauthenticated `gh`, an unreadable issue, or a branch-name collision.',
31
+ ready: 'Waiting for the caller to give a development request to branch for.',
32
+ createBranch: 'Coder is identifying the request\'s issue and creating and checking out a new branch at the current commit.',
33
+ awaitBossReply: "Waiting for Boss to answer the acting agent's question.",
34
+ failed: 'The branch workflow failed and is waiting for the caller to give the request again.',
35
+ branched: 'A new branch for the request was created at the current commit and checked out; the workflow returns its exact name, the exact base revision taken from repository authority, and the issue summary.',
36
+ refused: "No branch was created: Coder reported the failure with its reason, and the workflow fails and reports Coder's complete result to its caller.",
30
37
  };
31
38
  function playbookMeta(stateId, role) {
32
39
  return {
@@ -37,9 +44,7 @@ function playbookMeta(stateId, role) {
37
44
  },
38
45
  };
39
46
  }
40
- // DR-048: a final state additionally declares whether its outcome means the
41
- // workflow succeeded or failed, so BRANCH's own caller learns that from the
42
- // machine rather than from BRANCH's output fields.
47
+ /** A final state also publishes whether its outcome is success or failure. */
43
48
  function terminalMeta(stateId, terminal) {
44
49
  return {
45
50
  playbook: {
@@ -59,33 +64,69 @@ function isRecord(value) {
59
64
  function isNonEmptyString(value) {
60
65
  return typeof value === 'string' && value.trim().length > 0;
61
66
  }
62
- function playerOutput(event) {
63
- if (!isRecord(event))
67
+ /**
68
+ * Narrows an unknown event's `output` structurally to the declared `player`
69
+ * contract. Output missing a field its guard requires narrows to `undefined`.
70
+ */
71
+ function playerOutputOf(event) {
72
+ if (!isRecord(event) || !isRecord(event.output))
64
73
  return undefined;
65
- return isRecord(event.output) ? event.output : undefined;
66
- }
67
- function outputGuard(event, guard) {
68
- return playerOutput(event)?.guard === guard;
69
- }
70
- function outputString(event, field) {
71
- const value = playerOutput(event)?.[field];
72
- return isNonEmptyString(value) ? value : undefined;
74
+ const output = event.output;
75
+ if (output.guard === 'branched' &&
76
+ isNonEmptyString(output.branch) &&
77
+ isNonEmptyString(output.baseRevision) &&
78
+ isNonEmptyString(output.issueSummary)) {
79
+ return {
80
+ guard: 'branched',
81
+ branch: output.branch,
82
+ baseRevision: output.baseRevision,
83
+ issueSummary: output.issueSummary,
84
+ };
85
+ }
86
+ if (output.guard === 'refused' && isNonEmptyString(output.coderOutput)) {
87
+ return { guard: 'refused', coderOutput: output.coderOutput };
88
+ }
89
+ if (output.guard === 'needsBossReply' && isNonEmptyString(output.question)) {
90
+ return { guard: 'needsBossReply', question: output.question };
91
+ }
92
+ return undefined;
73
93
  }
74
- // DR-045: the branched outcome is accepted only with the receipt-observed
75
- // base revision the linked runtime injects; Coder's prose never supplies it.
76
- function isBranched({ event }) {
77
- return (outputGuard(event, 'branched') &&
78
- outputString(event, 'branch') !== undefined &&
79
- outputString(event, 'baseRevision') !== undefined &&
80
- outputString(event, 'issueSummary') !== undefined);
94
+ /** Narrows an unknown event's `error` structurally to a JSON-safe record. */
95
+ function normalizedErrorOf(event) {
96
+ const error = isRecord(event) ? event.error : undefined;
97
+ if (error instanceof Error) {
98
+ const name = error.name || 'Error';
99
+ return typeof error.stack === 'string'
100
+ ? { name, message: error.message, stack: error.stack }
101
+ : { name, message: error.message };
102
+ }
103
+ if (isRecord(error)) {
104
+ const name = typeof error.name === 'string' ? error.name : 'Error';
105
+ const message = typeof error.message === 'string'
106
+ ? error.message
107
+ : 'player actor rejected without a normalized error';
108
+ return typeof error.stack === 'string'
109
+ ? { name, message, stack: error.stack }
110
+ : { name, message };
111
+ }
112
+ return {
113
+ name: 'Error',
114
+ message: typeof error === 'string'
115
+ ? error
116
+ : 'player actor rejected without a normalized error',
117
+ };
81
118
  }
82
- function isRefused({ event }) {
83
- return (outputGuard(event, 'refused') &&
84
- outputString(event, 'coderOutput') !== undefined);
119
+ function isResumableStateId(value) {
120
+ return RESUMABLE_STATE_IDS.includes(value);
85
121
  }
86
- function needsBossReply({ event }) {
87
- return (outputGuard(event, 'needsBossReply') &&
88
- outputString(event, 'question') !== undefined);
122
+ /** Builds one guarded `BOSS_REPLY` resume arm per registered working-leaf id. */
123
+ function resumableStates(ids) {
124
+ return ids.map((id) => ({
125
+ guard: { type: 'canResume', params: { stateId: id } },
126
+ target: `#${id}`,
127
+ reenter: true,
128
+ actions: 'acceptBossReply',
129
+ }));
89
130
  }
90
131
  const machineSetup = setup({
91
132
  types: {},
@@ -95,87 +136,114 @@ const machineSetup = setup({
95
136
  }),
96
137
  },
97
138
  guards: {
98
- isBranched,
99
- isRefused,
100
- needsBossReply,
101
- emptyBossReply: ({ event }) => event.type === 'BOSS_REPLY' && event.answer.trim().length === 0,
102
- resumesCreateBranch: ({ context, event }) => event.type === 'BOSS_REPLY' &&
103
- event.answer.trim().length > 0 &&
104
- context.pendingBossQuestion?.resumeStateId === 'createBranch' &&
105
- (event.questionId === undefined || event.questionId === 'createBranch'),
139
+ // The entry guard validates the text the current event supplies, before
140
+ // the entry action copies it into context.
141
+ givesRequest: ({ event }) => event.type === 'START_BRANCH' && isNonEmptyString(event.callerInput),
142
+ isBranched: ({ event }) => playerOutputOf(event)?.guard === 'branched',
143
+ isRefused: ({ event }) => playerOutputOf(event)?.guard === 'refused',
144
+ needsBossReply: ({ event }, params) => playerOutputOf(event)?.guard === 'needsBossReply' &&
145
+ isResumableStateId(params.stateId),
146
+ canResume: ({ context, event }, params) => {
147
+ if (event.type !== 'BOSS_REPLY' || !isNonEmptyString(event.answer)) {
148
+ return false;
149
+ }
150
+ const pending = context.pendingBossQuestion;
151
+ if (pending === undefined || pending.resumeStateId !== params.stateId) {
152
+ return false;
153
+ }
154
+ return (event.questionId === undefined ||
155
+ event.questionId === pending.questionId);
156
+ },
106
157
  },
107
158
  actions: {
108
159
  'playbook.acceptedOutcome': (_args, _params) => undefined,
109
- startBranch: assign(({ event }) => {
160
+ startBranch: assign(({ context, event }) => {
110
161
  if (event.type !== 'START_BRANCH')
111
162
  return {};
112
163
  return {
113
164
  callerInput: event.callerInput,
114
- branch: undefined,
115
- baseRevision: undefined,
116
- issueSummary: undefined,
117
- coderOutput: undefined,
118
- completion: undefined,
119
- lastError: undefined,
120
- pendingBossQuestion: undefined,
121
- bossReply: undefined,
165
+ ...(context.lastError === undefined ? {} : { lastError: undefined }),
166
+ ...(context.pendingBossQuestion === undefined
167
+ ? {}
168
+ : { pendingBossQuestion: undefined }),
169
+ ...(context.bossReply === undefined ? {} : { bossReply: undefined }),
122
170
  };
123
171
  }),
124
- rememberBranched: assign(({ event }) => ({
125
- branch: outputString(event, 'branch'),
126
- // DR-045: the receipt-observed HEAD the branch was created from.
127
- baseRevision: outputString(event, 'baseRevision'),
128
- issueSummary: outputString(event, 'issueSummary'),
129
- completion: 'branched',
130
- pendingBossQuestion: undefined,
131
- bossReply: undefined,
132
- })),
133
- rememberRefused: assign(({ event }) => ({
134
- coderOutput: outputString(event, 'coderOutput'),
135
- completion: 'refused',
136
- pendingBossQuestion: undefined,
137
- bossReply: undefined,
138
- })),
139
- rememberPendingQuestion: assign(({ event }) => {
140
- const question = outputString(event, 'question');
141
- if (question === undefined || !outputGuard(event, 'needsBossReply')) {
142
- return { lastError: new Error('Coder question output was malformed') };
143
- }
172
+ rememberBranched: assign(({ context, event }) => {
173
+ const output = playerOutputOf(event);
174
+ if (output?.guard !== 'branched')
175
+ return {};
176
+ return {
177
+ branch: output.branch,
178
+ baseRevision: output.baseRevision,
179
+ issueSummary: output.issueSummary,
180
+ completion: 'branched',
181
+ ...(context.pendingBossQuestion === undefined
182
+ ? {}
183
+ : { pendingBossQuestion: undefined }),
184
+ ...(context.bossReply === undefined ? {} : { bossReply: undefined }),
185
+ };
186
+ }),
187
+ rememberRefused: assign(({ context, event }) => {
188
+ const output = playerOutputOf(event);
189
+ if (output?.guard !== 'refused')
190
+ return {};
191
+ return {
192
+ coderOutput: output.coderOutput,
193
+ completion: 'refused',
194
+ ...(context.pendingBossQuestion === undefined
195
+ ? {}
196
+ : { pendingBossQuestion: undefined }),
197
+ ...(context.bossReply === undefined ? {} : { bossReply: undefined }),
198
+ };
199
+ }),
200
+ setPendingBossQuestion: assign(({ context, event }, params) => {
201
+ const output = playerOutputOf(event);
202
+ if (output?.guard !== 'needsBossReply')
203
+ return {};
144
204
  return {
145
205
  pendingBossQuestion: {
146
- questionId: 'createBranch',
147
- resumeStateId: 'createBranch',
148
- sourceItem: 'BRANCH-1',
206
+ questionId: params.stateId,
207
+ resumeStateId: params.stateId,
208
+ sourceItem: params.sourceItem,
149
209
  asker: { kind: 'role', roleId: 'coder' },
150
- question,
210
+ question: output.question,
151
211
  },
152
- bossReply: undefined,
212
+ ...(context.bossReply === undefined ? {} : { bossReply: undefined }),
153
213
  };
154
214
  }),
155
- rememberBossReply: assign(({ event }) => event.type === 'BOSS_REPLY' ? { bossReply: event.answer } : {}),
156
- rememberEmptyBossReplyError: assign({
157
- lastError: () => new Error('BOSS_REPLY received an empty answer'),
158
- pendingBossQuestion: undefined,
159
- bossReply: undefined,
160
- }),
215
+ acceptBossReply: assign(({ event }) => event.type === 'BOSS_REPLY' ? { bossReply: event.answer } : {}),
216
+ clearBossReplyContext: assign(({ context }) => context.pendingBossQuestion === undefined &&
217
+ context.bossReply === undefined
218
+ ? {}
219
+ : { pendingBossQuestion: undefined, bossReply: undefined }),
161
220
  rememberActorError: assign(({ event }) => ({
162
- lastError: event.error,
221
+ lastError: normalizedErrorOf(event),
222
+ })),
223
+ rememberMalformedPlayerOutput: assign(() => ({
224
+ lastError: {
225
+ name: 'MalformedPlayerOutput',
226
+ message: 'Coder result for BRANCH-1 did not satisfy a declared outcome of its result contract.',
227
+ },
228
+ })),
229
+ rememberMalformedBossReply: assign(() => ({
230
+ lastError: {
231
+ name: 'MalformedBossReply',
232
+ message: 'BOSS_REPLY carried an empty answer or named no pending question.',
233
+ },
163
234
  })),
164
- rememberMalformedPlayerOutput: assign({
165
- lastError: () => new Error('Coder result for BRANCH-1 did not match an available outcome.'),
166
- }),
167
235
  },
168
236
  });
169
237
  export const branchMachine = machineSetup.createMachine({
170
238
  id: 'branch',
171
239
  initial: 'ready',
172
- context: {},
240
+ context: () => ({}),
173
241
  output: ({ context }) => {
174
242
  if (context.completion === 'branched') {
175
243
  if (!isNonEmptyString(context.branch) ||
176
244
  !isNonEmptyString(context.baseRevision) ||
177
245
  !isNonEmptyString(context.issueSummary)) {
178
- throw new Error('BRANCH reached branched without the exact branch name, a receipt-observed base revision, and an issue summary');
246
+ throw new Error('BRANCH reached branched without the exact branch name, base revision, and issue summary');
179
247
  }
180
248
  return {
181
249
  status: 'branched',
@@ -186,7 +254,7 @@ export const branchMachine = machineSetup.createMachine({
186
254
  }
187
255
  if (context.completion === 'refused') {
188
256
  if (!isNonEmptyString(context.coderOutput)) {
189
- throw new Error("BRANCH reached refused without Coder's report");
257
+ throw new Error("BRANCH reached refused without Coder's complete result");
190
258
  }
191
259
  return { status: 'refused', coderOutput: context.coderOutput };
192
260
  }
@@ -199,7 +267,11 @@ export const branchMachine = machineSetup.createMachine({
199
267
  meta: playbookMeta('ready'),
200
268
  tags: ['playbook.parked'],
201
269
  on: {
202
- START_BRANCH: { target: 'createBranch', actions: 'startBranch' },
270
+ START_BRANCH: {
271
+ guard: 'givesRequest',
272
+ target: 'createBranch',
273
+ actions: 'startBranch',
274
+ },
203
275
  },
204
276
  },
205
277
  createBranch: {
@@ -215,6 +287,8 @@ export const branchMachine = machineSetup.createMachine({
215
287
  sourceItem: 'BRANCH-1',
216
288
  prompt: CREATE_BRANCH_PROMPT,
217
289
  result: CREATE_BRANCH_RESULTS,
290
+ // Entry is guarded on a non-empty caller input, so this state is
291
+ // never entered without it.
218
292
  callerInput: context.callerInput ?? '',
219
293
  ...(context.pendingBossQuestion === undefined
220
294
  ? {}
@@ -255,7 +329,10 @@ export const branchMachine = machineSetup.createMachine({
255
329
  ],
256
330
  },
257
331
  {
258
- guard: 'needsBossReply',
332
+ guard: {
333
+ type: 'needsBossReply',
334
+ params: { stateId: 'createBranch' },
335
+ },
259
336
  target: 'awaitBossReply',
260
337
  actions: [
261
338
  {
@@ -266,7 +343,10 @@ export const branchMachine = machineSetup.createMachine({
266
343
  acceptedOutcome: 'needsBossReply',
267
344
  },
268
345
  },
269
- 'rememberPendingQuestion',
346
+ {
347
+ type: 'setPendingBossQuestion',
348
+ params: { stateId: 'createBranch', sourceItem: 'BRANCH-1' },
349
+ },
270
350
  ],
271
351
  },
272
352
  {
@@ -284,19 +364,17 @@ export const branchMachine = machineSetup.createMachine({
284
364
  tags: ['playbook.parked'],
285
365
  on: {
286
366
  BOSS_REPLY: [
367
+ ...resumableStates(RESUMABLE_STATE_IDS),
287
368
  {
288
- guard: 'emptyBossReply',
289
- target: '#failed',
290
- actions: 'rememberEmptyBossReplyError',
291
- },
292
- {
293
- guard: 'resumesCreateBranch',
294
- target: '#createBranch',
295
- reenter: true,
296
- actions: 'rememberBossReply',
369
+ target: 'failed',
370
+ actions: ['rememberMalformedBossReply', 'clearBossReplyContext'],
297
371
  },
298
372
  ],
299
- START_BRANCH: { target: 'createBranch', actions: 'startBranch' },
373
+ START_BRANCH: {
374
+ guard: 'givesRequest',
375
+ target: 'createBranch',
376
+ actions: 'startBranch',
377
+ },
300
378
  },
301
379
  },
302
380
  failed: {
@@ -305,7 +383,11 @@ export const branchMachine = machineSetup.createMachine({
305
383
  meta: playbookMeta('failed'),
306
384
  tags: ['playbook.parked'],
307
385
  on: {
308
- START_BRANCH: { target: 'createBranch', actions: 'startBranch' },
386
+ START_BRANCH: {
387
+ guard: 'givesRequest',
388
+ target: 'createBranch',
389
+ actions: 'startBranch',
390
+ },
309
391
  },
310
392
  },
311
393
  branched: {