@sublang/playbook 13.1.0 → 13.3.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/README.md +7 -5
  2. package/docs/cli.md +28 -9
  3. package/docs/configuration.md +27 -14
  4. package/docs/embedding.md +1 -1
  5. package/package.json +40 -2
  6. package/reference/sdlc/branch.md +37 -0
  7. package/reference/sdlc/branch.playbook/branch.fsm.d.ts +169 -0
  8. package/reference/sdlc/branch.playbook/branch.fsm.js +325 -0
  9. package/reference/sdlc/branch.playbook/branch.fsm.ts +458 -0
  10. package/reference/sdlc/branch.playbook/branch.gears.md +44 -0
  11. package/reference/sdlc/branch.playbook/branch.playbook.d.ts +20 -0
  12. package/reference/sdlc/branch.playbook/branch.playbook.js +121 -0
  13. package/reference/sdlc/branch.playbook/branch.playbook.ts +217 -0
  14. package/reference/sdlc/branch.playbook/branch.registry.d.ts +40 -0
  15. package/reference/sdlc/branch.playbook/branch.registry.js +63 -0
  16. package/reference/sdlc/branch.playbook/branch.registry.ts +121 -0
  17. package/reference/sdlc/code.playbook/bin/session-host.js +13 -3
  18. package/reference/sdlc/code.playbook/playbook-captain.d.ts +26 -1
  19. package/reference/sdlc/code.playbook/playbook-captain.js +218 -6
  20. package/reference/sdlc/code.playbook/playbook-captain.ts +281 -6
  21. package/reference/sdlc/code.playbook/playbook.config.template.yaml +21 -8
  22. package/reference/sdlc/code.playbook/session-host.d.ts +26 -0
  23. package/reference/sdlc/dev.md +24 -3
  24. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +100 -9
  25. package/reference/sdlc/dev.playbook/dev.fsm.js +235 -18
  26. package/reference/sdlc/dev.playbook/dev.fsm.ts +368 -24
  27. package/reference/sdlc/dev.playbook/dev.gears.md +45 -7
  28. package/reference/sdlc/dev.playbook/dev.playbook.js +13 -1
  29. package/reference/sdlc/dev.playbook/dev.playbook.ts +13 -1
  30. package/reference/sdlc/dev.playbook/dev.registry.d.ts +1 -1
  31. package/reference/sdlc/dev.playbook/dev.registry.js +7 -2
  32. package/reference/sdlc/dev.playbook/dev.registry.ts +7 -2
  33. package/reference/sdlc/pr.md +108 -0
  34. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +352 -0
  35. package/reference/sdlc/pr.playbook/pr.fsm.js +902 -0
  36. package/reference/sdlc/pr.playbook/pr.fsm.ts +1251 -0
  37. package/reference/sdlc/pr.playbook/pr.gears.md +155 -0
  38. package/reference/sdlc/pr.playbook/pr.playbook.d.ts +27 -0
  39. package/reference/sdlc/pr.playbook/pr.playbook.js +133 -0
  40. package/reference/sdlc/pr.playbook/pr.playbook.ts +235 -0
  41. package/reference/sdlc/pr.playbook/pr.registry.d.ts +40 -0
  42. package/reference/sdlc/pr.playbook/pr.registry.js +68 -0
  43. package/reference/sdlc/pr.playbook/pr.registry.ts +124 -0
@@ -1,9 +1,15 @@
1
1
  export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
2
2
  readonly [key: string]: JsonValue;
3
3
  };
4
- export type DevStateId = 'planAnalysis' | 'callCode' | 'callDecide' | 'callCodeAfterDecide';
5
- export type DevSourceItem = 'DEV-1' | 'DEV-2' | 'DEV-3' | 'DEV-4';
6
- export type DevChildPlaybookId = 'code' | 'decide';
4
+ export type DevStateId = 'planAnalysis' | 'createBranch' | 'callCode' | 'callDecide' | 'callCodeAfterDecide' | 'openPullRequest';
5
+ export type DevSourceItem = 'DEV-1' | 'DEV-2' | 'DEV-3' | 'DEV-4' | 'DEV-5' | 'DEV-6';
6
+ export type DevChildPlaybookId = 'code' | 'decide' | 'branch' | 'pr';
7
+ /**
8
+ * DR-050: the development path a pull-request planning outcome selected. It
9
+ * routes the `branch` success into `callCode` or `callDecide`, so the plain
10
+ * `code` and `decide then code` paths keep their items, prompts, and edges.
11
+ */
12
+ export type DevPullRequestPath = 'code' | 'decide-then-code';
7
13
  export type PendingBossQuestion = {
8
14
  readonly questionId: 'planAnalysis';
9
15
  readonly resumeStateId: 'planAnalysis';
@@ -38,13 +44,19 @@ export type PlayerOutput = {
38
44
  } | {
39
45
  readonly guard: 'decideThenCode';
40
46
  readonly planningResult: string;
47
+ } | {
48
+ readonly guard: 'codeViaPullRequest';
49
+ readonly planningResult: string;
50
+ } | {
51
+ readonly guard: 'decideThenCodeViaPullRequest';
52
+ readonly planningResult: string;
41
53
  } | {
42
54
  readonly guard: 'needsBossReply';
43
55
  readonly question: string;
44
56
  };
45
57
  export type PlaybookInput = {
46
- readonly stateId: 'callCode' | 'callDecide' | 'callCodeAfterDecide';
47
- readonly sourceItem: 'DEV-2' | 'DEV-3' | 'DEV-4';
58
+ readonly stateId: 'createBranch' | 'callCode' | 'callDecide' | 'callCodeAfterDecide' | 'openPullRequest';
59
+ readonly sourceItem: 'DEV-2' | 'DEV-3' | 'DEV-4' | 'DEV-5' | 'DEV-6';
48
60
  readonly playbookId: DevChildPlaybookId;
49
61
  readonly text: string;
50
62
  };
@@ -60,6 +72,27 @@ export type DecideSuccessOutput = {
60
72
  readonly evaluatedRevision: string;
61
73
  readonly noUnsettledFindings: true;
62
74
  };
75
+ /**
76
+ * The fields DEV consumes from `branch`'s canonical structured terminal
77
+ * output before continuing a pull-request path: the exact branch name, the
78
+ * exact base revision, and the issue summary. Additional members belong to
79
+ * BRANCH's own contract and do not disprove success; a missing member is an
80
+ * insufficient result, never an empty default.
81
+ */
82
+ export type BranchSuccessOutput = {
83
+ readonly branch: string;
84
+ readonly baseRevision: string;
85
+ readonly issueSummary: string;
86
+ };
87
+ /**
88
+ * The fields DEV consumes from `code`'s canonical structured terminal output
89
+ * before starting the dependent `pr` call: the exact last `code`-owned commit
90
+ * and the exact final evaluated repository revision.
91
+ */
92
+ export type CodeSuccessOutput = {
93
+ readonly lastCodeCommit: string;
94
+ readonly finalEvaluatedRevision: string;
95
+ };
63
96
  export type CompactError = {
64
97
  readonly name: string;
65
98
  readonly message: string;
@@ -78,7 +111,8 @@ export type DevPlaybookOutput = {
78
111
  readonly status: 'discussion-complete';
79
112
  } | {
80
113
  readonly status: 'complete';
81
- readonly childPlaybookId: 'code';
114
+ /** The final child of the selected path: `pr` on a pull-request path. */
115
+ readonly childPlaybookId: 'code' | 'pr';
82
116
  /** The successful result of DEV's final child call, when it has one. */
83
117
  readonly childOutput?: JsonValue;
84
118
  } | {
@@ -96,6 +130,14 @@ export type DevContext = {
96
130
  readonly planningResult?: string;
97
131
  readonly decideCommit?: string;
98
132
  readonly evaluatedRevision?: string;
133
+ /** DR-050: whether the accepted planning outcome selected a pull-request path. */
134
+ readonly deliveryViaPullRequest: boolean;
135
+ readonly pullRequestPath?: DevPullRequestPath;
136
+ readonly branch?: string;
137
+ readonly baseRevision?: string;
138
+ readonly issueSummary?: string;
139
+ readonly lastCodeCommit?: string;
140
+ readonly finalEvaluatedRevision?: string;
99
141
  readonly completion?: 'discussion-complete' | 'complete' | 'child-failed';
100
142
  readonly childOutput?: JsonValue;
101
143
  readonly childFailure?: CompletedChildResult;
@@ -164,24 +206,45 @@ export declare const devMachine: import("xstate").StateMachine<DevContext, {
164
206
  } | {
165
207
  type: "rememberDecidePath";
166
208
  params: import("xstate").NonReducibleUnknown;
209
+ } | {
210
+ type: "rememberCodeViaPullRequestPath";
211
+ params: import("xstate").NonReducibleUnknown;
212
+ } | {
213
+ type: "rememberDecideThenCodeViaPullRequestPath";
214
+ params: import("xstate").NonReducibleUnknown;
215
+ } | {
216
+ type: "rememberBranchResult";
217
+ params: import("xstate").NonReducibleUnknown;
167
218
  } | {
168
219
  type: "rememberDecideResult";
169
220
  params: import("xstate").NonReducibleUnknown;
221
+ } | {
222
+ type: "rememberCodeResult";
223
+ params: import("xstate").NonReducibleUnknown;
170
224
  } | {
171
225
  type: "completeWithChildSuccess";
172
226
  params: import("xstate").NonReducibleUnknown;
227
+ } | {
228
+ type: "completeWithInsufficientBranchResult";
229
+ params: import("xstate").NonReducibleUnknown;
173
230
  } | {
174
231
  type: "completeWithInsufficientCodeResult";
175
232
  params: import("xstate").NonReducibleUnknown;
176
233
  } | {
177
234
  type: "completeWithInsufficientDecideResult";
178
235
  params: import("xstate").NonReducibleUnknown;
236
+ } | {
237
+ type: "completeWithBranchFailure";
238
+ params: import("xstate").NonReducibleUnknown;
179
239
  } | {
180
240
  type: "completeWithCodeFailure";
181
241
  params: import("xstate").NonReducibleUnknown;
182
242
  } | {
183
243
  type: "completeWithDecideFailure";
184
244
  params: import("xstate").NonReducibleUnknown;
245
+ } | {
246
+ type: "completeWithPrFailure";
247
+ params: import("xstate").NonReducibleUnknown;
185
248
  }, {
186
249
  type: "needsBossReply";
187
250
  params: unknown;
@@ -198,25 +261,47 @@ export declare const devMachine: import("xstate").StateMachine<DevContext, {
198
261
  type: "isDecideThenCode";
199
262
  params: unknown;
200
263
  } | {
201
- type: "isCodeSuccess";
264
+ type: "isCodeViaPullRequest";
265
+ params: unknown;
266
+ } | {
267
+ type: "isDecideThenCodeViaPullRequest";
268
+ params: unknown;
269
+ } | {
270
+ type: "isBranchSuccessForCode";
271
+ params: unknown;
272
+ } | {
273
+ type: "isBranchSuccessForDecide";
274
+ params: unknown;
275
+ } | {
276
+ type: "isCodeSuccessViaPullRequest";
277
+ params: unknown;
278
+ } | {
279
+ type: "isPlainCodeSuccess";
202
280
  params: unknown;
203
281
  } | {
204
282
  type: "isDecideSuccess";
205
283
  params: unknown;
284
+ } | {
285
+ type: "authoredBranchFailure";
286
+ params: unknown;
206
287
  } | {
207
288
  type: "authoredCodeFailure";
208
289
  params: unknown;
209
290
  } | {
210
291
  type: "authoredDecideFailure";
211
292
  params: unknown;
293
+ } | {
294
+ type: "authoredPrFailure";
295
+ params: unknown;
212
296
  } | {
213
297
  type: "resumesPlanAnalysis";
214
298
  params: unknown;
215
- }, never, "done" | "failed" | "awaitBossReply" | "ready" | "planAnalysis" | "callCode" | "callDecide" | "callCodeAfterDecide" | "discussionComplete" | "reportedChildFailure", string, DevInput, {
299
+ }, never, "done" | "failed" | "awaitBossReply" | "ready" | "planAnalysis" | "createBranch" | "callCode" | "callDecide" | "callCodeAfterDecide" | "openPullRequest" | "discussionComplete" | "reportedChildFailure", string, DevInput, {
216
300
  readonly status: "discussion-complete";
217
301
  } | {
218
302
  readonly status: "complete";
219
- readonly childPlaybookId: "code";
303
+ /** The final child of the selected path: `pr` on a pull-request path. */
304
+ readonly childPlaybookId: "code" | "pr";
220
305
  /** The successful result of DEV's final child call, when it has one. */
221
306
  readonly childOutput?: JsonValue;
222
307
  } | {
@@ -232,6 +317,9 @@ export declare const devMachine: import("xstate").StateMachine<DevContext, {
232
317
  readonly planAnalysis: {
233
318
  id: "planAnalysis";
234
319
  };
320
+ readonly createBranch: {
321
+ id: "createBranch";
322
+ };
235
323
  readonly callCode: {
236
324
  id: "callCode";
237
325
  };
@@ -241,6 +329,9 @@ export declare const devMachine: import("xstate").StateMachine<DevContext, {
241
329
  readonly callCodeAfterDecide: {
242
330
  id: "callCodeAfterDecide";
243
331
  };
332
+ readonly openPullRequest: {
333
+ id: "openPullRequest";
334
+ };
244
335
  readonly awaitBossReply: {
245
336
  id: "awaitBossReply";
246
337
  };
@@ -15,6 +15,7 @@ const PLAN_ANALYSIS_PROMPT = [
15
15
  '- If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.',
16
16
  '- If implementation can proceed under the existing decisions, choose `code`.',
17
17
  '- If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.',
18
+ '- If the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery, read the issue and its comments as part of the analysis (`gh issue view --comments` with the issue number) and choose `code via pull request` or `decide then code via pull request` in place of `code` or `decide then code`.',
18
19
  '',
19
20
  'A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.',
20
21
  'Do not choose `decide then code` merely because the work is large.',
@@ -24,14 +25,18 @@ const PLAN_ANALYSIS_RESULTS = {
24
25
  discussionComplete: 'Analyst concluded the discussion after a Boss reply, with no repository work to follow.',
25
26
  code: 'Analyst determined implementation can proceed under the existing decisions. Output shall include `planningResult: <verbatim final text>`.',
26
27
  decideThenCode: 'Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle. Output shall include `planningResult: <verbatim final text>`.',
28
+ codeViaPullRequest: 'Analyst determined implementation can proceed under the existing decisions and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
29
+ decideThenCodeViaPullRequest: 'Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
27
30
  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>`.",
28
31
  };
29
32
  const STATE_DESCRIPTIONS = {
30
33
  ready: 'Waiting for a development request.',
31
34
  planAnalysis: 'Analyst is analyzing the development request to choose the smallest sound next step.',
35
+ createBranch: 'The BRANCH playbook is preparing the pull-request branch for the planned development path.',
32
36
  callCode: 'The CODE playbook is implementing the planned development path.',
33
37
  callDecide: 'The DECIDE playbook is settling the durable decision the planned path requires.',
34
38
  callCodeAfterDecide: 'The CODE playbook is implementing the decided development path.',
39
+ openPullRequest: 'The PR playbook is delivering the implemented branch through a pull request.',
35
40
  awaitBossReply: 'Waiting for Boss to answer Analyst.',
36
41
  failed: 'The development planning workflow failed and is waiting for a new development request.',
37
42
  discussionComplete: 'The planning discussion concluded after a Boss reply with no repository work to follow.',
@@ -89,6 +94,16 @@ function isDecideThenCode({ event }) {
89
94
  return (outputGuard(event, 'decideThenCode') &&
90
95
  outputString(event, 'planningResult') !== undefined);
91
96
  }
97
+ // DR-050: the two pull-request outcomes select the same development paths
98
+ // as `code` and `decide then code`, entered through `branch` first.
99
+ function isCodeViaPullRequest({ event }) {
100
+ return (outputGuard(event, 'codeViaPullRequest') &&
101
+ outputString(event, 'planningResult') !== undefined);
102
+ }
103
+ function isDecideThenCodeViaPullRequest({ event, }) {
104
+ return (outputGuard(event, 'decideThenCodeViaPullRequest') &&
105
+ outputString(event, 'planningResult') !== undefined);
106
+ }
92
107
  // Discussion complete is available only after a Boss reply: the round that
93
108
  // produced it must itself have been resumed with Boss's answer.
94
109
  function isDiscussionComplete({ context, event, }) {
@@ -111,6 +126,24 @@ function isDecideSuccessValue(value) {
111
126
  function isDecideSuccess({ event }) {
112
127
  return isDecideSuccessValue(childOutputOf(event));
113
128
  }
129
+ // BRANCH's canonical successful terminal output carries the exact name of
130
+ // the new checked-out branch, the exact receipt-owned base revision, and the
131
+ // issue summary; DEV consumes exactly those three by name and never defaults
132
+ // a missing one.
133
+ function isBranchSuccessValue(value) {
134
+ return (isRecord(value) &&
135
+ isNonEmptyString(value.branch) &&
136
+ isNonEmptyString(value.baseRevision) &&
137
+ isNonEmptyString(value.issueSummary));
138
+ }
139
+ function isBranchSuccessForCode({ context, event, }) {
140
+ return (context.pullRequestPath === 'code' &&
141
+ isBranchSuccessValue(childOutputOf(event)));
142
+ }
143
+ function isBranchSuccessForDecide({ context, event, }) {
144
+ return (context.pullRequestPath === 'decide-then-code' &&
145
+ isBranchSuccessValue(childOutputOf(event)));
146
+ }
114
147
  // CODE's canonical successful terminal output discriminates completion with
115
148
  // `status: 'complete'`, carries the exact last CODE-owned commit, and
116
149
  // affirms that every phase's review passed.
@@ -120,8 +153,22 @@ function isCodeSuccessValue(value) {
120
153
  isNonEmptyString(value.lastCodeCommit) &&
121
154
  value.allReviewsPassed === true);
122
155
  }
123
- function isCodeSuccess({ event }) {
124
- return isCodeSuccessValue(childOutputOf(event));
156
+ // A plain path completes DEV on that proof alone, exactly as before DR-050.
157
+ function isPlainCodeSuccess({ context, event, }) {
158
+ return (context.deliveryViaPullRequest !== true &&
159
+ isCodeSuccessValue(childOutputOf(event)));
160
+ }
161
+ // A pull-request path additionally consumes the exact final evaluated
162
+ // repository revision for the `pr` call, so a CODE result lacking it does not
163
+ // prove the success that path requires.
164
+ function isCodeSuccessValueForPullRequest(value) {
165
+ return (isCodeSuccessValue(value) &&
166
+ isRecord(value) &&
167
+ isNonEmptyString(value.finalEvaluatedRevision));
168
+ }
169
+ function isCodeSuccessViaPullRequest({ context, event, }) {
170
+ return (context.deliveryViaPullRequest === true &&
171
+ isCodeSuccessValueForPullRequest(childOutputOf(event)));
125
172
  }
126
173
  function isStateValue(value, ancestors = new Set()) {
127
174
  if (typeof value === 'string')
@@ -361,6 +408,31 @@ function codeAfterDecideCallText(context) {
361
408
  ['Evaluated revision', context.evaluatedRevision ?? ''],
362
409
  ]);
363
410
  }
411
+ function branchCallText(context) {
412
+ return quotedRelay(planningRelayValues(context));
413
+ }
414
+ // DEV-6: every placeholder is substituted from typed context that only a
415
+ // child's canonical structured result populated — never from player prose.
416
+ function pullRequestCallText(context) {
417
+ return quotedRelay([
418
+ ['Original request', context.developmentRequest ?? ''],
419
+ ['Issue summary', context.issueSummary ?? ''],
420
+ ['Branch', context.branch ?? ''],
421
+ ['Base revision', context.baseRevision ?? ''],
422
+ ['CODE commit', context.lastCodeCommit ?? ''],
423
+ ['Evaluated revision', context.finalEvaluatedRevision ?? ''],
424
+ ]);
425
+ }
426
+ function acceptedPlanningPath(context, event, pullRequestPath) {
427
+ return {
428
+ planningResult: outputString(event, 'planningResult'),
429
+ discussionExchanges: archivedExchanges(context),
430
+ deliveryViaPullRequest: pullRequestPath !== undefined,
431
+ pullRequestPath,
432
+ pendingBossQuestion: undefined,
433
+ bossReply: undefined,
434
+ };
435
+ }
364
436
  const machineSetup = setup({
365
437
  types: {},
366
438
  actors: {
@@ -375,11 +447,18 @@ const machineSetup = setup({
375
447
  isDiscussionComplete,
376
448
  isCodePath,
377
449
  isDecideThenCode,
450
+ isCodeViaPullRequest,
451
+ isDecideThenCodeViaPullRequest,
378
452
  needsBossReply,
379
- isCodeSuccess,
453
+ isBranchSuccessForCode,
454
+ isBranchSuccessForDecide,
455
+ isCodeSuccessViaPullRequest,
456
+ isPlainCodeSuccess,
380
457
  isDecideSuccess,
458
+ authoredBranchFailure: authoredChildFailureGuard('branch'),
381
459
  authoredCodeFailure: authoredChildFailureGuard('code'),
382
460
  authoredDecideFailure: authoredChildFailureGuard('decide'),
461
+ authoredPrFailure: authoredChildFailureGuard('pr'),
383
462
  emptyBossReply: ({ event }) => event.type === 'BOSS_REPLY' && event.answer.trim().length === 0,
384
463
  resumesPlanAnalysis: ({ context, event }) => event.type === 'BOSS_REPLY' &&
385
464
  event.answer.trim().length > 0 &&
@@ -398,6 +477,13 @@ const machineSetup = setup({
398
477
  planningResult: undefined,
399
478
  decideCommit: undefined,
400
479
  evaluatedRevision: undefined,
480
+ deliveryViaPullRequest: false,
481
+ pullRequestPath: undefined,
482
+ branch: undefined,
483
+ baseRevision: undefined,
484
+ issueSummary: undefined,
485
+ lastCodeCommit: undefined,
486
+ finalEvaluatedRevision: undefined,
401
487
  completion: undefined,
402
488
  childOutput: undefined,
403
489
  childFailure: undefined,
@@ -412,18 +498,10 @@ const machineSetup = setup({
412
498
  pendingBossQuestion: undefined,
413
499
  bossReply: undefined,
414
500
  })),
415
- rememberCodePath: assign(({ context, event }) => ({
416
- planningResult: outputString(event, 'planningResult'),
417
- discussionExchanges: archivedExchanges(context),
418
- pendingBossQuestion: undefined,
419
- bossReply: undefined,
420
- })),
421
- rememberDecidePath: assign(({ context, event }) => ({
422
- planningResult: outputString(event, 'planningResult'),
423
- discussionExchanges: archivedExchanges(context),
424
- pendingBossQuestion: undefined,
425
- bossReply: undefined,
426
- })),
501
+ rememberCodePath: assign(({ context, event }) => acceptedPlanningPath(context, event)),
502
+ rememberDecidePath: assign(({ context, event }) => acceptedPlanningPath(context, event)),
503
+ rememberCodeViaPullRequestPath: assign(({ context, event }) => acceptedPlanningPath(context, event, 'code')),
504
+ rememberDecideThenCodeViaPullRequestPath: assign(({ context, event }) => acceptedPlanningPath(context, event, 'decide-then-code')),
427
505
  rememberPendingQuestion: assign(({ context, event }) => {
428
506
  const question = outputString(event, 'question');
429
507
  if (question === undefined || !outputGuard(event, 'needsBossReply')) {
@@ -447,6 +525,16 @@ const machineSetup = setup({
447
525
  pendingBossQuestion: undefined,
448
526
  bossReply: undefined,
449
527
  }),
528
+ rememberBranchResult: assign(({ event }) => {
529
+ const output = childOutputOf(event);
530
+ if (!isBranchSuccessValue(output))
531
+ return {};
532
+ return {
533
+ branch: output.branch,
534
+ baseRevision: output.baseRevision,
535
+ issueSummary: output.issueSummary,
536
+ };
537
+ }),
450
538
  rememberDecideResult: assign(({ event }) => {
451
539
  const output = childOutputOf(event);
452
540
  if (!isDecideSuccessValue(output))
@@ -456,6 +544,15 @@ const machineSetup = setup({
456
544
  evaluatedRevision: output.evaluatedRevision,
457
545
  };
458
546
  }),
547
+ rememberCodeResult: assign(({ event }) => {
548
+ const output = childOutputOf(event);
549
+ if (!isCodeSuccessValueForPullRequest(output))
550
+ return {};
551
+ return {
552
+ lastCodeCommit: output.lastCodeCommit,
553
+ finalEvaluatedRevision: output.finalEvaluatedRevision,
554
+ };
555
+ }),
459
556
  completeWithChildSuccess: assign(({ event }) => {
460
557
  const output = childOutputOf(event);
461
558
  return {
@@ -463,6 +560,10 @@ const machineSetup = setup({
463
560
  ...(output === undefined ? {} : { childOutput: output }),
464
561
  };
465
562
  }),
563
+ completeWithInsufficientBranchResult: assign(({ event }) => ({
564
+ completion: 'child-failed',
565
+ childFailure: insufficientChildResult(event, 'branch'),
566
+ })),
466
567
  completeWithInsufficientCodeResult: assign(({ event }) => ({
467
568
  completion: 'child-failed',
468
569
  childFailure: insufficientChildResult(event, 'code'),
@@ -471,6 +572,10 @@ const machineSetup = setup({
471
572
  completion: 'child-failed',
472
573
  childFailure: insufficientChildResult(event, 'decide'),
473
574
  })),
575
+ completeWithBranchFailure: assign(({ event }) => ({
576
+ completion: 'child-failed',
577
+ childFailure: relayedChildFailure(event, 'branch'),
578
+ })),
474
579
  completeWithCodeFailure: assign(({ event }) => ({
475
580
  completion: 'child-failed',
476
581
  childFailure: relayedChildFailure(event, 'code'),
@@ -479,6 +584,10 @@ const machineSetup = setup({
479
584
  completion: 'child-failed',
480
585
  childFailure: relayedChildFailure(event, 'decide'),
481
586
  })),
587
+ completeWithPrFailure: assign(({ event }) => ({
588
+ completion: 'child-failed',
589
+ childFailure: relayedChildFailure(event, 'pr'),
590
+ })),
482
591
  rememberActorError: assign(({ event }) => ({
483
592
  lastError: event.error,
484
593
  })),
@@ -493,6 +602,7 @@ export const devMachine = machineSetup.createMachine({
493
602
  context: ({ input }) => ({
494
603
  runResults: typeof input.runResults === 'string' ? input.runResults : '',
495
604
  discussionExchanges: [],
605
+ deliveryViaPullRequest: false,
496
606
  }),
497
607
  output: ({ context }) => {
498
608
  if (context.completion === 'discussion-complete') {
@@ -501,7 +611,9 @@ export const devMachine = machineSetup.createMachine({
501
611
  if (context.completion === 'complete') {
502
612
  return {
503
613
  status: 'complete',
504
- childPlaybookId: 'code',
614
+ // `done` is entered by `pr` on a pull-request path and by `code` on a
615
+ // plain path; no other arm completes the workflow.
616
+ childPlaybookId: context.deliveryViaPullRequest ? 'pr' : 'code',
505
617
  ...(context.childOutput === undefined
506
618
  ? {}
507
619
  : { childOutput: context.childOutput }),
@@ -594,6 +706,36 @@ export const devMachine = machineSetup.createMachine({
594
706
  'rememberDecidePath',
595
707
  ],
596
708
  },
709
+ {
710
+ guard: 'isCodeViaPullRequest',
711
+ target: 'createBranch',
712
+ actions: [
713
+ {
714
+ type: 'playbook.acceptedOutcome',
715
+ params: {
716
+ source: 'planAnalysis',
717
+ target: 'createBranch',
718
+ acceptedOutcome: 'codeViaPullRequest',
719
+ },
720
+ },
721
+ 'rememberCodeViaPullRequestPath',
722
+ ],
723
+ },
724
+ {
725
+ guard: 'isDecideThenCodeViaPullRequest',
726
+ target: 'createBranch',
727
+ actions: [
728
+ {
729
+ type: 'playbook.acceptedOutcome',
730
+ params: {
731
+ source: 'planAnalysis',
732
+ target: 'createBranch',
733
+ acceptedOutcome: 'decideThenCodeViaPullRequest',
734
+ },
735
+ },
736
+ 'rememberDecideThenCodeViaPullRequestPath',
737
+ ],
738
+ },
597
739
  {
598
740
  guard: 'needsBossReply',
599
741
  target: 'awaitBossReply',
@@ -617,6 +759,45 @@ export const devMachine = machineSetup.createMachine({
617
759
  onError: { target: 'failed', actions: 'rememberActorError' },
618
760
  },
619
761
  },
762
+ createBranch: {
763
+ id: 'createBranch',
764
+ description: STATE_DESCRIPTIONS.createBranch,
765
+ meta: playbookMeta('createBranch'),
766
+ tags: ['playbook.suspended'],
767
+ invoke: {
768
+ src: 'playbook',
769
+ input: ({ context }) => ({
770
+ stateId: 'createBranch',
771
+ sourceItem: 'DEV-5',
772
+ playbookId: 'branch',
773
+ text: branchCallText(context),
774
+ }),
775
+ onDone: [
776
+ {
777
+ guard: 'isBranchSuccessForCode',
778
+ target: 'callCode',
779
+ actions: 'rememberBranchResult',
780
+ },
781
+ {
782
+ guard: 'isBranchSuccessForDecide',
783
+ target: 'callDecide',
784
+ actions: 'rememberBranchResult',
785
+ },
786
+ {
787
+ target: 'reportedChildFailure',
788
+ actions: 'completeWithInsufficientBranchResult',
789
+ },
790
+ ],
791
+ onError: [
792
+ {
793
+ guard: 'authoredBranchFailure',
794
+ target: 'reportedChildFailure',
795
+ actions: 'completeWithBranchFailure',
796
+ },
797
+ { target: 'failed', actions: 'rememberActorError' },
798
+ ],
799
+ },
800
+ },
620
801
  callCode: {
621
802
  id: 'callCode',
622
803
  description: STATE_DESCRIPTIONS.callCode,
@@ -632,7 +813,12 @@ export const devMachine = machineSetup.createMachine({
632
813
  }),
633
814
  onDone: [
634
815
  {
635
- guard: 'isCodeSuccess',
816
+ guard: 'isCodeSuccessViaPullRequest',
817
+ target: 'openPullRequest',
818
+ actions: 'rememberCodeResult',
819
+ },
820
+ {
821
+ guard: 'isPlainCodeSuccess',
636
822
  target: 'done',
637
823
  actions: 'completeWithChildSuccess',
638
824
  },
@@ -700,7 +886,12 @@ export const devMachine = machineSetup.createMachine({
700
886
  }),
701
887
  onDone: [
702
888
  {
703
- guard: 'isCodeSuccess',
889
+ guard: 'isCodeSuccessViaPullRequest',
890
+ target: 'openPullRequest',
891
+ actions: 'rememberCodeResult',
892
+ },
893
+ {
894
+ guard: 'isPlainCodeSuccess',
704
895
  target: 'done',
705
896
  actions: 'completeWithChildSuccess',
706
897
  },
@@ -719,6 +910,32 @@ export const devMachine = machineSetup.createMachine({
719
910
  ],
720
911
  },
721
912
  },
913
+ openPullRequest: {
914
+ id: 'openPullRequest',
915
+ description: STATE_DESCRIPTIONS.openPullRequest,
916
+ meta: playbookMeta('openPullRequest'),
917
+ tags: ['playbook.suspended'],
918
+ invoke: {
919
+ src: 'playbook',
920
+ input: ({ context }) => ({
921
+ stateId: 'openPullRequest',
922
+ sourceItem: 'DEV-6',
923
+ playbookId: 'pr',
924
+ text: pullRequestCallText(context),
925
+ }),
926
+ // DR-048: `pr` declares its terminal kinds, so `onDone` alone proves
927
+ // its success; DEV consumes none of its fields by name.
928
+ onDone: { target: 'done', actions: 'completeWithChildSuccess' },
929
+ onError: [
930
+ {
931
+ guard: 'authoredPrFailure',
932
+ target: 'reportedChildFailure',
933
+ actions: 'completeWithPrFailure',
934
+ },
935
+ { target: 'failed', actions: 'rememberActorError' },
936
+ ],
937
+ },
938
+ },
722
939
  awaitBossReply: {
723
940
  id: 'awaitBossReply',
724
941
  description: STATE_DESCRIPTIONS.awaitBossReply,