@sublang/playbook 13.1.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 (38) 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 +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/playbook.config.template.yaml +16 -2
  18. package/reference/sdlc/dev.md +24 -3
  19. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +100 -9
  20. package/reference/sdlc/dev.playbook/dev.fsm.js +235 -18
  21. package/reference/sdlc/dev.playbook/dev.fsm.ts +368 -24
  22. package/reference/sdlc/dev.playbook/dev.gears.md +45 -7
  23. package/reference/sdlc/dev.playbook/dev.playbook.js +13 -1
  24. package/reference/sdlc/dev.playbook/dev.playbook.ts +13 -1
  25. package/reference/sdlc/dev.playbook/dev.registry.d.ts +1 -1
  26. package/reference/sdlc/dev.playbook/dev.registry.js +7 -2
  27. package/reference/sdlc/dev.playbook/dev.registry.ts +7 -2
  28. package/reference/sdlc/pr.md +108 -0
  29. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +352 -0
  30. package/reference/sdlc/pr.playbook/pr.fsm.js +902 -0
  31. package/reference/sdlc/pr.playbook/pr.fsm.ts +1251 -0
  32. package/reference/sdlc/pr.playbook/pr.gears.md +155 -0
  33. package/reference/sdlc/pr.playbook/pr.playbook.d.ts +27 -0
  34. package/reference/sdlc/pr.playbook/pr.playbook.js +133 -0
  35. package/reference/sdlc/pr.playbook/pr.playbook.ts +235 -0
  36. package/reference/sdlc/pr.playbook/pr.registry.d.ts +40 -0
  37. package/reference/sdlc/pr.playbook/pr.registry.js +68 -0
  38. package/reference/sdlc/pr.playbook/pr.registry.ts +124 -0
@@ -15,13 +15,28 @@ export type JsonValue =
15
15
 
16
16
  export type DevStateId =
17
17
  | 'planAnalysis'
18
+ | 'createBranch'
18
19
  | 'callCode'
19
20
  | 'callDecide'
20
- | 'callCodeAfterDecide';
21
+ | 'callCodeAfterDecide'
22
+ | 'openPullRequest';
21
23
 
22
- export type DevSourceItem = 'DEV-1' | 'DEV-2' | 'DEV-3' | 'DEV-4';
24
+ export type DevSourceItem =
25
+ | 'DEV-1'
26
+ | 'DEV-2'
27
+ | 'DEV-3'
28
+ | 'DEV-4'
29
+ | 'DEV-5'
30
+ | 'DEV-6';
23
31
 
24
- export type DevChildPlaybookId = 'code' | 'decide';
32
+ export type DevChildPlaybookId = 'code' | 'decide' | 'branch' | 'pr';
33
+
34
+ /**
35
+ * DR-050: the development path a pull-request planning outcome selected. It
36
+ * routes the `branch` success into `callCode` or `callDecide`, so the plain
37
+ * `code` and `decide then code` paths keep their items, prompts, and edges.
38
+ */
39
+ export type DevPullRequestPath = 'code' | 'decide-then-code';
25
40
 
26
41
  export type PendingBossQuestion = {
27
42
  readonly questionId: 'planAnalysis';
@@ -53,11 +68,21 @@ export type PlayerOutput =
53
68
  | { readonly guard: 'discussionComplete' }
54
69
  | { readonly guard: 'code'; readonly planningResult: string }
55
70
  | { readonly guard: 'decideThenCode'; readonly planningResult: string }
71
+ | { readonly guard: 'codeViaPullRequest'; readonly planningResult: string }
72
+ | {
73
+ readonly guard: 'decideThenCodeViaPullRequest';
74
+ readonly planningResult: string;
75
+ }
56
76
  | { readonly guard: 'needsBossReply'; readonly question: string };
57
77
 
58
78
  export type PlaybookInput = {
59
- readonly stateId: 'callCode' | 'callDecide' | 'callCodeAfterDecide';
60
- readonly sourceItem: 'DEV-2' | 'DEV-3' | 'DEV-4';
79
+ readonly stateId:
80
+ | 'createBranch'
81
+ | 'callCode'
82
+ | 'callDecide'
83
+ | 'callCodeAfterDecide'
84
+ | 'openPullRequest';
85
+ readonly sourceItem: 'DEV-2' | 'DEV-3' | 'DEV-4' | 'DEV-5' | 'DEV-6';
61
86
  readonly playbookId: DevChildPlaybookId;
62
87
  readonly text: string;
63
88
  };
@@ -75,6 +100,29 @@ export type DecideSuccessOutput = {
75
100
  readonly noUnsettledFindings: true;
76
101
  };
77
102
 
103
+ /**
104
+ * The fields DEV consumes from `branch`'s canonical structured terminal
105
+ * output before continuing a pull-request path: the exact branch name, the
106
+ * exact base revision, and the issue summary. Additional members belong to
107
+ * BRANCH's own contract and do not disprove success; a missing member is an
108
+ * insufficient result, never an empty default.
109
+ */
110
+ export type BranchSuccessOutput = {
111
+ readonly branch: string;
112
+ readonly baseRevision: string;
113
+ readonly issueSummary: string;
114
+ };
115
+
116
+ /**
117
+ * The fields DEV consumes from `code`'s canonical structured terminal output
118
+ * before starting the dependent `pr` call: the exact last `code`-owned commit
119
+ * and the exact final evaluated repository revision.
120
+ */
121
+ export type CodeSuccessOutput = {
122
+ readonly lastCodeCommit: string;
123
+ readonly finalEvaluatedRevision: string;
124
+ };
125
+
78
126
  export type CompactError = {
79
127
  readonly name: string;
80
128
  readonly message: string;
@@ -99,7 +147,8 @@ export type DevPlaybookOutput =
99
147
  }
100
148
  | {
101
149
  readonly status: 'complete';
102
- readonly childPlaybookId: 'code';
150
+ /** The final child of the selected path: `pr` on a pull-request path. */
151
+ readonly childPlaybookId: 'code' | 'pr';
103
152
  /** The successful result of DEV's final child call, when it has one. */
104
153
  readonly childOutput?: JsonValue;
105
154
  }
@@ -120,6 +169,14 @@ export type DevContext = {
120
169
  readonly planningResult?: string;
121
170
  readonly decideCommit?: string;
122
171
  readonly evaluatedRevision?: string;
172
+ /** DR-050: whether the accepted planning outcome selected a pull-request path. */
173
+ readonly deliveryViaPullRequest: boolean;
174
+ readonly pullRequestPath?: DevPullRequestPath;
175
+ readonly branch?: string;
176
+ readonly baseRevision?: string;
177
+ readonly issueSummary?: string;
178
+ readonly lastCodeCommit?: string;
179
+ readonly finalEvaluatedRevision?: string;
123
180
  readonly completion?: 'discussion-complete' | 'complete' | 'child-failed';
124
181
  readonly childOutput?: JsonValue;
125
182
  readonly childFailure?: CompletedChildResult;
@@ -148,6 +205,7 @@ const PLAN_ANALYSIS_PROMPT = [
148
205
  '- If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.',
149
206
  '- If implementation can proceed under the existing decisions, choose `code`.',
150
207
  '- If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.',
208
+ '- 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`.',
151
209
  '',
152
210
  'A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.',
153
211
  'Do not choose `decide then code` merely because the work is large.',
@@ -161,6 +219,10 @@ const PLAN_ANALYSIS_RESULTS = {
161
219
  'Analyst determined implementation can proceed under the existing decisions. Output shall include `planningResult: <verbatim final text>`.',
162
220
  decideThenCode:
163
221
  '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>`.',
222
+ codeViaPullRequest:
223
+ '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>`.',
224
+ decideThenCodeViaPullRequest:
225
+ '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>`.',
164
226
  needsBossReply:
165
227
  "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>`.",
166
228
  } as const;
@@ -169,11 +231,15 @@ const STATE_DESCRIPTIONS = {
169
231
  ready: 'Waiting for a development request.',
170
232
  planAnalysis:
171
233
  'Analyst is analyzing the development request to choose the smallest sound next step.',
234
+ createBranch:
235
+ 'The BRANCH playbook is preparing the pull-request branch for the planned development path.',
172
236
  callCode: 'The CODE playbook is implementing the planned development path.',
173
237
  callDecide:
174
238
  'The DECIDE playbook is settling the durable decision the planned path requires.',
175
239
  callCodeAfterDecide:
176
240
  'The CODE playbook is implementing the decided development path.',
241
+ openPullRequest:
242
+ 'The PR playbook is delivering the implemented branch through a pull request.',
177
243
  awaitBossReply: 'Waiting for Boss to answer Analyst.',
178
244
  failed:
179
245
  'The development planning workflow failed and is waiting for a new development request.',
@@ -253,6 +319,26 @@ function isDecideThenCode({ event }: { event: unknown }): boolean {
253
319
  );
254
320
  }
255
321
 
322
+ // DR-050: the two pull-request outcomes select the same development paths
323
+ // as `code` and `decide then code`, entered through `branch` first.
324
+ function isCodeViaPullRequest({ event }: { event: unknown }): boolean {
325
+ return (
326
+ outputGuard(event, 'codeViaPullRequest') &&
327
+ outputString(event, 'planningResult') !== undefined
328
+ );
329
+ }
330
+
331
+ function isDecideThenCodeViaPullRequest({
332
+ event,
333
+ }: {
334
+ event: unknown;
335
+ }): boolean {
336
+ return (
337
+ outputGuard(event, 'decideThenCodeViaPullRequest') &&
338
+ outputString(event, 'planningResult') !== undefined
339
+ );
340
+ }
341
+
256
342
  // Discussion complete is available only after a Boss reply: the round that
257
343
  // produced it must itself have been resumed with Boss's answer.
258
344
  function isDiscussionComplete({
@@ -292,6 +378,45 @@ function isDecideSuccess({ event }: { event: unknown }): boolean {
292
378
  return isDecideSuccessValue(childOutputOf(event));
293
379
  }
294
380
 
381
+ // BRANCH's canonical successful terminal output carries the exact name of
382
+ // the new checked-out branch, the exact receipt-owned base revision, and the
383
+ // issue summary; DEV consumes exactly those three by name and never defaults
384
+ // a missing one.
385
+ function isBranchSuccessValue(value: unknown): value is BranchSuccessOutput {
386
+ return (
387
+ isRecord(value) &&
388
+ isNonEmptyString(value.branch) &&
389
+ isNonEmptyString(value.baseRevision) &&
390
+ isNonEmptyString(value.issueSummary)
391
+ );
392
+ }
393
+
394
+ function isBranchSuccessForCode({
395
+ context,
396
+ event,
397
+ }: {
398
+ context: DevContext;
399
+ event: unknown;
400
+ }): boolean {
401
+ return (
402
+ context.pullRequestPath === 'code' &&
403
+ isBranchSuccessValue(childOutputOf(event))
404
+ );
405
+ }
406
+
407
+ function isBranchSuccessForDecide({
408
+ context,
409
+ event,
410
+ }: {
411
+ context: DevContext;
412
+ event: unknown;
413
+ }): boolean {
414
+ return (
415
+ context.pullRequestPath === 'decide-then-code' &&
416
+ isBranchSuccessValue(childOutputOf(event))
417
+ );
418
+ }
419
+
295
420
  // CODE's canonical successful terminal output discriminates completion with
296
421
  // `status: 'complete'`, carries the exact last CODE-owned commit, and
297
422
  // affirms that every phase's review passed.
@@ -304,8 +429,44 @@ function isCodeSuccessValue(value: unknown): boolean {
304
429
  );
305
430
  }
306
431
 
307
- function isCodeSuccess({ event }: { event: unknown }): boolean {
308
- return isCodeSuccessValue(childOutputOf(event));
432
+ // A plain path completes DEV on that proof alone, exactly as before DR-050.
433
+ function isPlainCodeSuccess({
434
+ context,
435
+ event,
436
+ }: {
437
+ context: DevContext;
438
+ event: unknown;
439
+ }): boolean {
440
+ return (
441
+ context.deliveryViaPullRequest !== true &&
442
+ isCodeSuccessValue(childOutputOf(event))
443
+ );
444
+ }
445
+
446
+ // A pull-request path additionally consumes the exact final evaluated
447
+ // repository revision for the `pr` call, so a CODE result lacking it does not
448
+ // prove the success that path requires.
449
+ function isCodeSuccessValueForPullRequest(
450
+ value: unknown,
451
+ ): value is CodeSuccessOutput & Record<string, unknown> {
452
+ return (
453
+ isCodeSuccessValue(value) &&
454
+ isRecord(value) &&
455
+ isNonEmptyString(value.finalEvaluatedRevision)
456
+ );
457
+ }
458
+
459
+ function isCodeSuccessViaPullRequest({
460
+ context,
461
+ event,
462
+ }: {
463
+ context: DevContext;
464
+ event: unknown;
465
+ }): boolean {
466
+ return (
467
+ context.deliveryViaPullRequest === true &&
468
+ isCodeSuccessValueForPullRequest(childOutputOf(event))
469
+ );
309
470
  }
310
471
 
311
472
  function isStateValue(value: unknown, ancestors = new Set<object>()): boolean {
@@ -626,6 +787,38 @@ function codeAfterDecideCallText(context: DevContext): string {
626
787
  ]);
627
788
  }
628
789
 
790
+ function branchCallText(context: DevContext): string {
791
+ return quotedRelay(planningRelayValues(context));
792
+ }
793
+
794
+ // DEV-6: every placeholder is substituted from typed context that only a
795
+ // child's canonical structured result populated — never from player prose.
796
+ function pullRequestCallText(context: DevContext): string {
797
+ return quotedRelay([
798
+ ['Original request', context.developmentRequest ?? ''],
799
+ ['Issue summary', context.issueSummary ?? ''],
800
+ ['Branch', context.branch ?? ''],
801
+ ['Base revision', context.baseRevision ?? ''],
802
+ ['CODE commit', context.lastCodeCommit ?? ''],
803
+ ['Evaluated revision', context.finalEvaluatedRevision ?? ''],
804
+ ]);
805
+ }
806
+
807
+ function acceptedPlanningPath(
808
+ context: DevContext,
809
+ event: unknown,
810
+ pullRequestPath?: DevPullRequestPath,
811
+ ) {
812
+ return {
813
+ planningResult: outputString(event, 'planningResult'),
814
+ discussionExchanges: archivedExchanges(context),
815
+ deliveryViaPullRequest: pullRequestPath !== undefined,
816
+ pullRequestPath,
817
+ pendingBossQuestion: undefined,
818
+ bossReply: undefined,
819
+ };
820
+ }
821
+
629
822
  const machineSetup = setup({
630
823
  types: {} as {
631
824
  context: DevContext;
@@ -645,11 +838,18 @@ const machineSetup = setup({
645
838
  isDiscussionComplete,
646
839
  isCodePath,
647
840
  isDecideThenCode,
841
+ isCodeViaPullRequest,
842
+ isDecideThenCodeViaPullRequest,
648
843
  needsBossReply,
649
- isCodeSuccess,
844
+ isBranchSuccessForCode,
845
+ isBranchSuccessForDecide,
846
+ isCodeSuccessViaPullRequest,
847
+ isPlainCodeSuccess,
650
848
  isDecideSuccess,
849
+ authoredBranchFailure: authoredChildFailureGuard('branch'),
651
850
  authoredCodeFailure: authoredChildFailureGuard('code'),
652
851
  authoredDecideFailure: authoredChildFailureGuard('decide'),
852
+ authoredPrFailure: authoredChildFailureGuard('pr'),
653
853
  emptyBossReply: ({ event }) =>
654
854
  event.type === 'BOSS_REPLY' && event.answer.trim().length === 0,
655
855
  resumesPlanAnalysis: ({ context, event }) =>
@@ -676,6 +876,13 @@ const machineSetup = setup({
676
876
  planningResult: undefined,
677
877
  decideCommit: undefined,
678
878
  evaluatedRevision: undefined,
879
+ deliveryViaPullRequest: false,
880
+ pullRequestPath: undefined,
881
+ branch: undefined,
882
+ baseRevision: undefined,
883
+ issueSummary: undefined,
884
+ lastCodeCommit: undefined,
885
+ finalEvaluatedRevision: undefined,
679
886
  completion: undefined,
680
887
  childOutput: undefined,
681
888
  childFailure: undefined,
@@ -690,18 +897,18 @@ const machineSetup = setup({
690
897
  pendingBossQuestion: undefined,
691
898
  bossReply: undefined,
692
899
  })),
693
- rememberCodePath: assign(({ context, event }) => ({
694
- planningResult: outputString(event, 'planningResult'),
695
- discussionExchanges: archivedExchanges(context),
696
- pendingBossQuestion: undefined,
697
- bossReply: undefined,
698
- })),
699
- rememberDecidePath: assign(({ context, event }) => ({
700
- planningResult: outputString(event, 'planningResult'),
701
- discussionExchanges: archivedExchanges(context),
702
- pendingBossQuestion: undefined,
703
- bossReply: undefined,
704
- })),
900
+ rememberCodePath: assign(({ context, event }) =>
901
+ acceptedPlanningPath(context, event),
902
+ ),
903
+ rememberDecidePath: assign(({ context, event }) =>
904
+ acceptedPlanningPath(context, event),
905
+ ),
906
+ rememberCodeViaPullRequestPath: assign(({ context, event }) =>
907
+ acceptedPlanningPath(context, event, 'code'),
908
+ ),
909
+ rememberDecideThenCodeViaPullRequestPath: assign(({ context, event }) =>
910
+ acceptedPlanningPath(context, event, 'decide-then-code'),
911
+ ),
705
912
  rememberPendingQuestion: assign(({ context, event }) => {
706
913
  const question = outputString(event, 'question');
707
914
  if (question === undefined || !outputGuard(event, 'needsBossReply')) {
@@ -727,6 +934,15 @@ const machineSetup = setup({
727
934
  pendingBossQuestion: undefined,
728
935
  bossReply: undefined,
729
936
  }),
937
+ rememberBranchResult: assign(({ event }) => {
938
+ const output = childOutputOf(event);
939
+ if (!isBranchSuccessValue(output)) return {};
940
+ return {
941
+ branch: output.branch,
942
+ baseRevision: output.baseRevision,
943
+ issueSummary: output.issueSummary,
944
+ };
945
+ }),
730
946
  rememberDecideResult: assign(({ event }) => {
731
947
  const output = childOutputOf(event);
732
948
  if (!isDecideSuccessValue(output)) return {};
@@ -735,6 +951,14 @@ const machineSetup = setup({
735
951
  evaluatedRevision: output.evaluatedRevision,
736
952
  };
737
953
  }),
954
+ rememberCodeResult: assign(({ event }) => {
955
+ const output = childOutputOf(event);
956
+ if (!isCodeSuccessValueForPullRequest(output)) return {};
957
+ return {
958
+ lastCodeCommit: output.lastCodeCommit,
959
+ finalEvaluatedRevision: output.finalEvaluatedRevision,
960
+ };
961
+ }),
738
962
  completeWithChildSuccess: assign(({ event }) => {
739
963
  const output = childOutputOf(event);
740
964
  return {
@@ -742,6 +966,10 @@ const machineSetup = setup({
742
966
  ...(output === undefined ? {} : { childOutput: output as JsonValue }),
743
967
  };
744
968
  }),
969
+ completeWithInsufficientBranchResult: assign(({ event }) => ({
970
+ completion: 'child-failed' as const,
971
+ childFailure: insufficientChildResult(event, 'branch'),
972
+ })),
745
973
  completeWithInsufficientCodeResult: assign(({ event }) => ({
746
974
  completion: 'child-failed' as const,
747
975
  childFailure: insufficientChildResult(event, 'code'),
@@ -750,6 +978,10 @@ const machineSetup = setup({
750
978
  completion: 'child-failed' as const,
751
979
  childFailure: insufficientChildResult(event, 'decide'),
752
980
  })),
981
+ completeWithBranchFailure: assign(({ event }) => ({
982
+ completion: 'child-failed' as const,
983
+ childFailure: relayedChildFailure(event, 'branch'),
984
+ })),
753
985
  completeWithCodeFailure: assign(({ event }) => ({
754
986
  completion: 'child-failed' as const,
755
987
  childFailure: relayedChildFailure(event, 'code'),
@@ -758,6 +990,10 @@ const machineSetup = setup({
758
990
  completion: 'child-failed' as const,
759
991
  childFailure: relayedChildFailure(event, 'decide'),
760
992
  })),
993
+ completeWithPrFailure: assign(({ event }) => ({
994
+ completion: 'child-failed' as const,
995
+ childFailure: relayedChildFailure(event, 'pr'),
996
+ })),
761
997
  rememberActorError: assign(({ event }) => ({
762
998
  lastError: (event as unknown as { readonly error?: unknown }).error,
763
999
  })),
@@ -774,6 +1010,7 @@ export const devMachine = machineSetup.createMachine({
774
1010
  context: ({ input }) => ({
775
1011
  runResults: typeof input.runResults === 'string' ? input.runResults : '',
776
1012
  discussionExchanges: [],
1013
+ deliveryViaPullRequest: false,
777
1014
  }),
778
1015
  output: ({ context }): DevPlaybookOutput => {
779
1016
  if (context.completion === 'discussion-complete') {
@@ -782,7 +1019,9 @@ export const devMachine = machineSetup.createMachine({
782
1019
  if (context.completion === 'complete') {
783
1020
  return {
784
1021
  status: 'complete',
785
- childPlaybookId: 'code',
1022
+ // `done` is entered by `pr` on a pull-request path and by `code` on a
1023
+ // plain path; no other arm completes the workflow.
1024
+ childPlaybookId: context.deliveryViaPullRequest ? 'pr' : 'code',
786
1025
  ...(context.childOutput === undefined
787
1026
  ? {}
788
1027
  : { childOutput: context.childOutput }),
@@ -877,6 +1116,36 @@ export const devMachine = machineSetup.createMachine({
877
1116
  'rememberDecidePath',
878
1117
  ],
879
1118
  },
1119
+ {
1120
+ guard: 'isCodeViaPullRequest',
1121
+ target: 'createBranch',
1122
+ actions: [
1123
+ {
1124
+ type: 'playbook.acceptedOutcome',
1125
+ params: {
1126
+ source: 'planAnalysis',
1127
+ target: 'createBranch',
1128
+ acceptedOutcome: 'codeViaPullRequest',
1129
+ },
1130
+ },
1131
+ 'rememberCodeViaPullRequestPath',
1132
+ ],
1133
+ },
1134
+ {
1135
+ guard: 'isDecideThenCodeViaPullRequest',
1136
+ target: 'createBranch',
1137
+ actions: [
1138
+ {
1139
+ type: 'playbook.acceptedOutcome',
1140
+ params: {
1141
+ source: 'planAnalysis',
1142
+ target: 'createBranch',
1143
+ acceptedOutcome: 'decideThenCodeViaPullRequest',
1144
+ },
1145
+ },
1146
+ 'rememberDecideThenCodeViaPullRequestPath',
1147
+ ],
1148
+ },
880
1149
  {
881
1150
  guard: 'needsBossReply',
882
1151
  target: 'awaitBossReply',
@@ -900,6 +1169,45 @@ export const devMachine = machineSetup.createMachine({
900
1169
  onError: { target: 'failed', actions: 'rememberActorError' },
901
1170
  },
902
1171
  },
1172
+ createBranch: {
1173
+ id: 'createBranch',
1174
+ description: STATE_DESCRIPTIONS.createBranch,
1175
+ meta: playbookMeta('createBranch'),
1176
+ tags: ['playbook.suspended'],
1177
+ invoke: {
1178
+ src: 'playbook',
1179
+ input: ({ context }): PlaybookInput => ({
1180
+ stateId: 'createBranch',
1181
+ sourceItem: 'DEV-5',
1182
+ playbookId: 'branch',
1183
+ text: branchCallText(context),
1184
+ }),
1185
+ onDone: [
1186
+ {
1187
+ guard: 'isBranchSuccessForCode',
1188
+ target: 'callCode',
1189
+ actions: 'rememberBranchResult',
1190
+ },
1191
+ {
1192
+ guard: 'isBranchSuccessForDecide',
1193
+ target: 'callDecide',
1194
+ actions: 'rememberBranchResult',
1195
+ },
1196
+ {
1197
+ target: 'reportedChildFailure',
1198
+ actions: 'completeWithInsufficientBranchResult',
1199
+ },
1200
+ ],
1201
+ onError: [
1202
+ {
1203
+ guard: 'authoredBranchFailure',
1204
+ target: 'reportedChildFailure',
1205
+ actions: 'completeWithBranchFailure',
1206
+ },
1207
+ { target: 'failed', actions: 'rememberActorError' },
1208
+ ],
1209
+ },
1210
+ },
903
1211
  callCode: {
904
1212
  id: 'callCode',
905
1213
  description: STATE_DESCRIPTIONS.callCode,
@@ -915,7 +1223,12 @@ export const devMachine = machineSetup.createMachine({
915
1223
  }),
916
1224
  onDone: [
917
1225
  {
918
- guard: 'isCodeSuccess',
1226
+ guard: 'isCodeSuccessViaPullRequest',
1227
+ target: 'openPullRequest',
1228
+ actions: 'rememberCodeResult',
1229
+ },
1230
+ {
1231
+ guard: 'isPlainCodeSuccess',
919
1232
  target: 'done',
920
1233
  actions: 'completeWithChildSuccess',
921
1234
  },
@@ -983,7 +1296,12 @@ export const devMachine = machineSetup.createMachine({
983
1296
  }),
984
1297
  onDone: [
985
1298
  {
986
- guard: 'isCodeSuccess',
1299
+ guard: 'isCodeSuccessViaPullRequest',
1300
+ target: 'openPullRequest',
1301
+ actions: 'rememberCodeResult',
1302
+ },
1303
+ {
1304
+ guard: 'isPlainCodeSuccess',
987
1305
  target: 'done',
988
1306
  actions: 'completeWithChildSuccess',
989
1307
  },
@@ -1002,6 +1320,32 @@ export const devMachine = machineSetup.createMachine({
1002
1320
  ],
1003
1321
  },
1004
1322
  },
1323
+ openPullRequest: {
1324
+ id: 'openPullRequest',
1325
+ description: STATE_DESCRIPTIONS.openPullRequest,
1326
+ meta: playbookMeta('openPullRequest'),
1327
+ tags: ['playbook.suspended'],
1328
+ invoke: {
1329
+ src: 'playbook',
1330
+ input: ({ context }): PlaybookInput => ({
1331
+ stateId: 'openPullRequest',
1332
+ sourceItem: 'DEV-6',
1333
+ playbookId: 'pr',
1334
+ text: pullRequestCallText(context),
1335
+ }),
1336
+ // DR-048: `pr` declares its terminal kinds, so `onDone` alone proves
1337
+ // its success; DEV consumes none of its fields by name.
1338
+ onDone: { target: 'done', actions: 'completeWithChildSuccess' },
1339
+ onError: [
1340
+ {
1341
+ guard: 'authoredPrFailure',
1342
+ target: 'reportedChildFailure',
1343
+ actions: 'completeWithPrFailure',
1344
+ },
1345
+ { target: 'failed', actions: 'rememberActorError' },
1346
+ ],
1347
+ },
1348
+ },
1005
1349
  awaitBossReply: {
1006
1350
  id: 'awaitBossReply',
1007
1351
  description: STATE_DESCRIPTIONS.awaitBossReply,
@@ -27,6 +27,7 @@ At the start of `dev` and after each Boss reply, Captain shall relay the develop
27
27
  > - If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.
28
28
  > - If implementation can proceed under the existing decisions, choose `code`.
29
29
  > - If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.
30
+ > - 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`.
30
31
  >
31
32
  > A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.
32
33
  > Do not choose `decide then code` merely because the work is large.
@@ -36,9 +37,11 @@ Results:
36
37
  - `discussionComplete`: Analyst concluded the discussion after a Boss reply, with no repository work to follow.
37
38
  - `code`: Analyst determined implementation can proceed under the existing decisions. Output shall include `planningResult: <verbatim final text>`.
38
39
  - `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>`.
40
+ - `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>`.
41
+ - `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>`.
39
42
 
40
43
  Workflow outcomes:
41
- - The planning result has four semantic outcomes: needs Boss reply, discussion complete, code, and decide then code.
44
+ - The planning result has six semantic outcomes: needs Boss reply, discussion complete, code, decide then code, code via pull request, and decide then code via pull request.
42
45
  - Each outcome requires affirmative support in Analyst's result; absence of a reason to choose another outcome is not support, and no outcome depends on a fixed presentation format of Analyst's reply.
43
46
  - Needs Boss reply uses the standard Boss-question suspension with Analyst's complete response; after Boss replies, `dev` resumes with the answer in the same Analyst conversation; the previous question is included only when that conversation must start fresh.
44
47
  - Discussion complete is available only after a Boss reply, when any useful analysis has already been presented through needs Boss reply; it completes `dev` without a child call or repository change.
@@ -48,21 +51,22 @@ Workflow outcomes:
48
51
 
49
52
  ### DEV-2
50
53
 
51
- When the accepted planning result selects `code`, Captain shall call playbook `code`:
54
+ When the accepted planning result selects `code`, or `branch` succeeds for `code via pull request`, Captain shall call playbook `code`:
52
55
 
53
56
  > > Original request: <development-request>
54
57
  > > Prior discussion: <discussion-context>
55
58
  > > Planning result: <planning-result>
56
59
 
57
60
  Workflow outcomes:
58
- - `code` success completes `dev` with the successful `code` result.
61
+ - On a plain path, `code` success completes `dev` with the successful `code` result.
62
+ - On a pull-request path, `code` success provides the exact last `code`-owned commit and the exact final evaluated repository revision from `code`'s canonical structured result and continues with the `pr` call.
59
63
  - An authored `code` abort or failure, or a terminal `code` result that does not prove the success required for the selected path, terminates `dev` with that canonical result relayed and no later child call.
60
64
  - Any other nested-call error parks `dev` as failed and retains the control-plane error.
61
- - `dev` consumes commit identities only from each child's canonical structured result, never from player prose.
65
+ - `dev` consumes commit, revision, branch, and pull-request identities only from each child's canonical structured result, never from player prose.
62
66
 
63
67
  ### DEV-3
64
68
 
65
- When the accepted planning result selects `decide then code`, Captain shall call playbook `decide`:
69
+ When the accepted planning result selects `decide then code`, or `branch` succeeds for `decide then code via pull request`, Captain shall call playbook `decide`:
66
70
 
67
71
  > > Original request: <development-request>
68
72
  > > Prior discussion: <discussion-context>
@@ -85,7 +89,41 @@ When `decide` succeeds, Captain shall call playbook `code`:
85
89
  > > Evaluated revision: <evaluated-revision>
86
90
 
87
91
  Workflow outcomes:
88
- - `code` success completes `dev` with the successful `code` result.
92
+ - On a plain path, `code` success completes `dev` with the successful `code` result.
93
+ - On a pull-request path, `code` success provides the exact last `code`-owned commit and the exact final evaluated repository revision from `code`'s canonical structured result and continues with the `pr` call.
89
94
  - An authored `code` abort or failure, or a terminal `code` result that does not prove the success required for the selected path, terminates `dev` with that canonical result relayed and no later child call.
90
95
  - Any other nested-call error parks `dev` as failed and retains the control-plane error.
91
- - `dev` consumes commit identities only from each child's canonical structured result, never from player prose.
96
+ - `dev` consumes commit, revision, branch, and pull-request identities only from each child's canonical structured result, never from player prose.
97
+
98
+ ## Pull-request delivery
99
+
100
+ ### DEV-5
101
+
102
+ When the accepted planning result selects `code via pull request` or `decide then code via pull request`, Captain shall call playbook `branch`:
103
+
104
+ > > Original request: <development-request>
105
+ > > Prior discussion: <discussion-context>
106
+ > > Planning result: <planning-result>
107
+
108
+ Workflow outcomes:
109
+ - `branch` success provides the exact branch name, the exact base revision, and the issue summary from `branch`'s canonical structured result and continues with the `code` call for code via pull request, or the `decide` call and then the `code` call for decide then code via pull request, each with the same input as its plain path.
110
+ - An authored `branch` abort or failure, or a terminal `branch` result that does not prove that success, terminates `dev` with that canonical result relayed and no later child call.
111
+ - Any other nested-call error parks `dev` as failed and retains the control-plane error.
112
+ - A plain request calls neither `branch` nor `pr`.
113
+
114
+ ### DEV-6
115
+
116
+ When `code` succeeds on a pull-request path, Captain shall call playbook `pr`:
117
+
118
+ > > Original request: <development-request>
119
+ > > Issue summary: <issue-summary>
120
+ > > Branch: <branch>
121
+ > > Base revision: <base-revision>
122
+ > > CODE commit: <last-code-commit>
123
+ > > Evaluated revision: <final-evaluated-revision>
124
+
125
+ Workflow outcomes:
126
+ - `pr` success completes `dev` with the successful `pr` result.
127
+ - An authored `pr` abort or failure, or a terminal `pr` result that does not prove the success required for the selected path, terminates `dev` with that canonical result relayed and no later child call.
128
+ - Any other nested-call error parks `dev` as failed and retains the control-plane error.
129
+ - `dev` consumes commit, revision, branch, and pull-request identities only from each child's canonical structured result, never from player prose.