@sublang/playbook 13.0.0 → 13.2.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 (68) hide show
  1. package/README.md +7 -5
  2. package/docs/cli.md +28 -9
  3. package/docs/configuration.md +21 -5
  4. package/docs/embedding.md +1 -1
  5. package/package.json +41 -3
  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.md +7 -0
  18. package/reference/sdlc/code.playbook/code.fsm.js +5 -5
  19. package/reference/sdlc/code.playbook/code.fsm.ts +5 -5
  20. package/reference/sdlc/code.playbook/code.gears.md +5 -5
  21. package/reference/sdlc/code.playbook/code.playbook.d.ts +1 -1
  22. package/reference/sdlc/code.playbook/code.playbook.js +6 -16
  23. package/reference/sdlc/code.playbook/code.playbook.ts +7 -17
  24. package/reference/sdlc/code.playbook/playbook-captain.js +4 -1
  25. package/reference/sdlc/code.playbook/playbook-captain.ts +4 -1
  26. package/reference/sdlc/code.playbook/playbook.config.template.yaml +16 -2
  27. package/reference/sdlc/decide.md +2 -0
  28. package/reference/sdlc/decide.playbook/decide.fsm.js +1 -1
  29. package/reference/sdlc/decide.playbook/decide.fsm.ts +1 -1
  30. package/reference/sdlc/decide.playbook/decide.gears.md +2 -2
  31. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +1 -2
  32. package/reference/sdlc/decide.playbook/decide.playbook.js +8 -22
  33. package/reference/sdlc/decide.playbook/decide.playbook.ts +8 -25
  34. package/reference/sdlc/dev.md +45 -4
  35. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +100 -9
  36. package/reference/sdlc/dev.playbook/dev.fsm.js +245 -28
  37. package/reference/sdlc/dev.playbook/dev.fsm.ts +381 -37
  38. package/reference/sdlc/dev.playbook/dev.gears.md +60 -22
  39. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +1 -1
  40. package/reference/sdlc/dev.playbook/dev.playbook.js +20 -18
  41. package/reference/sdlc/dev.playbook/dev.playbook.ts +21 -21
  42. package/reference/sdlc/dev.playbook/dev.registry.d.ts +1 -1
  43. package/reference/sdlc/dev.playbook/dev.registry.js +7 -2
  44. package/reference/sdlc/dev.playbook/dev.registry.ts +7 -2
  45. package/reference/sdlc/pr.md +108 -0
  46. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +352 -0
  47. package/reference/sdlc/pr.playbook/pr.fsm.js +902 -0
  48. package/reference/sdlc/pr.playbook/pr.fsm.ts +1251 -0
  49. package/reference/sdlc/pr.playbook/pr.gears.md +155 -0
  50. package/reference/sdlc/pr.playbook/pr.playbook.d.ts +27 -0
  51. package/reference/sdlc/pr.playbook/pr.playbook.js +133 -0
  52. package/reference/sdlc/pr.playbook/pr.playbook.ts +235 -0
  53. package/reference/sdlc/pr.playbook/pr.registry.d.ts +40 -0
  54. package/reference/sdlc/pr.playbook/pr.registry.js +68 -0
  55. package/reference/sdlc/pr.playbook/pr.registry.ts +124 -0
  56. package/reference/sdlc/review.md +18 -0
  57. package/reference/sdlc/review.playbook/review.fsm.js +8 -8
  58. package/reference/sdlc/review.playbook/review.fsm.ts +8 -8
  59. package/reference/sdlc/review.playbook/review.gears.md +8 -8
  60. package/reference/sdlc/review.playbook/review.playbook.d.ts +1 -1
  61. package/reference/sdlc/review.playbook/review.playbook.js +5 -15
  62. package/reference/sdlc/review.playbook/review.playbook.ts +6 -16
  63. package/slc/link.md +12 -24
  64. package/src/runtime.d.ts +2 -0
  65. package/src/runtime.ts +2 -0
  66. package/src/xstate-playbook-runtime.d.ts +4 -2
  67. package/src/xstate-playbook-runtime.js +18 -13
  68. package/src/xstate-playbook-runtime.ts +26 -14
@@ -4,9 +4,9 @@
4
4
  // Generated by slc/gears2fsm.md from ./dev.gears.md.
5
5
  import { assign, fromPromise, setup } from 'xstate';
6
6
  const PLAN_ANALYSIS_PROMPT = [
7
- '> <development-request>',
8
- '> <discussion-context>',
9
- '> <run-results>',
7
+ '> Original request: <development-request>',
8
+ '> Prior discussion: <discussion-context>',
9
+ '> Run results: <run-results>',
10
10
  '',
11
11
  'Inspect the request and the relevant repository and specs only as needed to determine the smallest sound next step.',
12
12
  'Do not change files or commit while planning or discussing the request.',
@@ -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')
@@ -334,8 +381,8 @@ function archivedExchanges(context) {
334
381
  }
335
382
  function quotedRelay(values) {
336
383
  return values
337
- .filter((value) => value.length > 0)
338
- .map((value) => value
384
+ .filter(([, value]) => value.length > 0)
385
+ .map(([label, value]) => `${label}: ${value}`
339
386
  .split('\n')
340
387
  .map((line) => `> ${line}`)
341
388
  .join('\n'))
@@ -343,9 +390,9 @@ function quotedRelay(values) {
343
390
  }
344
391
  function planningRelayValues(context) {
345
392
  return [
346
- context.developmentRequest ?? '',
347
- renderDiscussionContext(context.discussionExchanges),
348
- context.planningResult ?? '',
393
+ ['Original request', context.developmentRequest ?? ''],
394
+ ['Prior discussion', renderDiscussionContext(context.discussionExchanges)],
395
+ ['Planning result', context.planningResult ?? ''],
349
396
  ];
350
397
  }
351
398
  function codeCallText(context) {
@@ -357,10 +404,35 @@ function decideCallText(context) {
357
404
  function codeAfterDecideCallText(context) {
358
405
  return quotedRelay([
359
406
  ...planningRelayValues(context),
360
- context.decideCommit ?? '',
361
- context.evaluatedRevision ?? '',
407
+ ['DECIDE commit', context.decideCommit ?? ''],
408
+ ['Evaluated revision', context.evaluatedRevision ?? ''],
409
+ ]);
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 ?? ''],
362
424
  ]);
363
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,