@sublang/playbook 0.1.2 → 0.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.
@@ -31,6 +31,27 @@ const jumpableStateIds = [
31
31
  'commitJoint',
32
32
  'failed',
33
33
  ];
34
+ const resumableStateIds = [
35
+ 'planAndImplement',
36
+ 'respondToReview',
37
+ 'continueIr',
38
+ 'summarizeSpecs',
39
+ 'reviewBossCommitSpecs',
40
+ 'reviewBossCommitCode',
41
+ 'reviewBossCommitMixed',
42
+ 'reviewIrTaskCommitSpecs',
43
+ 'reviewIrTaskCommitCode',
44
+ 'reviewIrTaskCommitMixed',
45
+ 'reviewChangesSpecs',
46
+ 'reviewChangesCode',
47
+ 'reviewChangesMixed',
48
+ 'reviewChangesAndChallengesSpecs',
49
+ 'reviewChangesAndChallengesCode',
50
+ 'reviewChangesAndChallengesMixed',
51
+ 'adjudicateChallenges',
52
+ 'commitCoderInitial',
53
+ 'commitJoint',
54
+ ];
34
55
  const outputOf = (event) => event.output;
35
56
  const guardIs = (guard) => ({ event }) => outputOf(event)?.guard === guard;
36
57
  const guardAndOrigin = (guard, origin) => ({ context, event }) => outputOf(event)?.guard === guard && context.changeOrigin === origin;
@@ -47,16 +68,145 @@ const rememberCaptainOutput = assign({
47
68
  const rememberCaptainError = assign({
48
69
  lastError: ({ event }) => event.error,
49
70
  });
71
+ const rememberEmptyBossReplyError = assign({
72
+ lastError: () => new Error('BOSS_REPLY received empty answer'),
73
+ });
50
74
  const rememberBossInput = assign({
51
75
  intent: ({ context, event }) => event.intent ?? context.intent,
52
76
  irNumber: ({ context, event }) => event.irNumber ?? context.irNumber,
53
77
  });
54
- const bossInterrupts = (ids) => ids.map((id) => ({
78
+ const needsBossReplyDescription = "The player's prose surfaces a clarifying question for Boss that the player cannot answer alone. Output shall include `question: <verbatim question text from the player's prose>`.";
79
+ const bossReplyInputFields = (context) => ({
80
+ pendingBossQuestion: context.pendingBossQuestion,
81
+ bossReply: context.bossReply,
82
+ });
83
+ const withNeedsBossReply = (result) => ({
84
+ ...result,
85
+ needsBossReply: needsBossReplyDescription,
86
+ });
87
+ const captainStateMetadata = {
88
+ planAndImplement: { sourceItem: 'CODE-1', player: 'Coder' },
89
+ respondToReview: { sourceItem: 'CODE-2', player: 'Coder' },
90
+ continueIr: { sourceItem: 'CODE-3', player: 'Coder' },
91
+ summarizeSpecs: { sourceItem: 'CODE-4', player: 'Coder' },
92
+ reviewBossCommitSpecs: { sourceItem: 'CODE-5', player: 'Reviewer' },
93
+ reviewBossCommitCode: { sourceItem: 'CODE-6', player: 'Reviewer' },
94
+ reviewBossCommitMixed: { sourceItem: 'CODE-7', player: 'Reviewer' },
95
+ reviewIrTaskCommitSpecs: { sourceItem: 'CODE-8', player: 'Reviewer' },
96
+ reviewIrTaskCommitCode: { sourceItem: 'CODE-9', player: 'Reviewer' },
97
+ reviewIrTaskCommitMixed: { sourceItem: 'CODE-10', player: 'Reviewer' },
98
+ reviewChangesSpecs: { sourceItem: 'CODE-11', player: 'Reviewer' },
99
+ reviewChangesCode: { sourceItem: 'CODE-12', player: 'Reviewer' },
100
+ reviewChangesMixed: { sourceItem: 'CODE-13', player: 'Reviewer' },
101
+ reviewChangesAndChallengesSpecs: { sourceItem: 'CODE-15', player: 'Reviewer' },
102
+ reviewChangesAndChallengesCode: { sourceItem: 'CODE-16', player: 'Reviewer' },
103
+ reviewChangesAndChallengesMixed: { sourceItem: 'CODE-17', player: 'Reviewer' },
104
+ adjudicateChallenges: { sourceItem: 'CODE-14', player: 'Reviewer' },
105
+ commitCoderInitial: { sourceItem: 'CODE-18', player: 'Committer' },
106
+ commitJoint: { sourceItem: 'CODE-19', player: 'Committer' },
107
+ };
108
+ const planAndImplementInput = (context) => ({
109
+ player: 'Coder',
110
+ sourceItem: 'CODE-1',
111
+ intent: context.intent,
112
+ ...bossReplyInputFields(context),
113
+ prompt: [
114
+ 'Assess whether this can be completed in a single commit, following best practices.',
115
+ 'If yes, implement and test, updating both code and specs; otherwise, decompose into tasks as a new IR under @specs/iterations.',
116
+ 'Consult @specs/map.md for relevant context if needed; ensure it reflects the changes.',
117
+ 'Do not commit.',
118
+ ].join('\n'),
119
+ result: withNeedsBossReply({
120
+ singleCommitReady: 'Coder produced uncommitted single-commit changes (Initial Changes).',
121
+ irDrafted: 'Coder decomposed the intent into a new IR and drafted it uncommitted (Initial Changes).',
122
+ }),
123
+ });
124
+ const continueIrInput = (context) => ({
125
+ player: 'Coder',
126
+ sourceItem: 'CODE-3',
127
+ irNumber: context.irNumber,
128
+ ...bossReplyInputFields(context),
129
+ prompt: [
130
+ 'Continue to implement IR-<#> if not all deliverables and tasks are done.',
131
+ 'Implement one task at a time (including corresponding tests if any).',
132
+ 'Stop after each task for review — do not commit yet.',
133
+ 'If relevant, mark progress in the IR.',
134
+ ].join('\n'),
135
+ result: withNeedsBossReply({
136
+ taskReady: 'Coder produced uncommitted changes for the next IR task (Initial Changes). Output shall include `taskDescription: <one-line description of the task just implemented>`.',
137
+ iterationDone: 'All IR deliverables and tasks are done.',
138
+ }),
139
+ });
140
+ const summarizeSpecsInput = (context) => ({
141
+ player: 'Coder',
142
+ sourceItem: 'CODE-4',
143
+ irNumber: context.irNumber,
144
+ ...bossReplyInputFields(context),
145
+ prompt: [
146
+ 'Read IR-<#> and corresponding commits.',
147
+ 'According to @specs/meta.md, add or update spec items to fully capture:',
148
+ '',
149
+ '- the user requirements in @specs/user,',
150
+ '- the system behavior in @specs/dev, and',
151
+ '- the integration/system test cases in @specs/test.',
152
+ '',
153
+ 'The spec items should be the *minimal* set needed to reimplement code without the IR.',
154
+ 'The set should be complete and coherent.',
155
+ 'Avoid implementation specifics.',
156
+ 'Avoid redundant spec items.',
157
+ 'Consult @specs/map.md for relevant context and update it to reflect your changes.',
158
+ ].join('\n'),
159
+ result: withNeedsBossReply({
160
+ specsReady: 'Coder produced uncommitted spec updates (Initial Changes).',
161
+ noSpecChanges: 'Existing specs already capture the iteration.',
162
+ }),
163
+ });
164
+ const setPendingBossQuestion = (resumeStateId) => assign({
165
+ pendingBossQuestion: ({ context, event, }) => {
166
+ const input = captainStateMetadata[resumeStateId];
167
+ return {
168
+ resumeStateId,
169
+ sourceItem: input.sourceItem,
170
+ player: input.player,
171
+ question: outputOf(event)?.question ?? '',
172
+ };
173
+ },
174
+ bossReply: () => undefined,
175
+ });
176
+ const rememberBossReply = assign({
177
+ bossReply: ({ event }) => event.answer ?? '',
178
+ });
179
+ const clearBossReplyContext = assign({
180
+ pendingBossQuestion: () => undefined,
181
+ bossReply: () => undefined,
182
+ });
183
+ const actionsArray = (actions) => actions === undefined ? [] : Array.isArray(actions) ? actions : [actions];
184
+ const withClearBossReplyContext = (transition) => ({
185
+ ...transition,
186
+ actions: [clearBossReplyContext, ...actionsArray(transition.actions)],
187
+ });
188
+ const bossInterrupts = (ids, extraActions = []) => ids.map((id) => ({
55
189
  target: `#${id}`,
56
190
  guard: ({ event }) => event.type === 'BOSS_INTERRUPT' && event.targetId === id,
57
191
  reenter: true,
58
- actions: rememberBossInput,
192
+ actions: [...extraActions, rememberBossInput],
59
193
  }));
194
+ const resumableStates = (ids) => ids.map((id) => ({
195
+ target: `#${id}`,
196
+ guard: ({ context, event }) => event.type === 'BOSS_REPLY' &&
197
+ context.pendingBossQuestion?.resumeStateId === id,
198
+ reenter: true,
199
+ actions: rememberBossReply,
200
+ }));
201
+ const bossReplyIsEmpty = ({ event }) => event.type === 'BOSS_REPLY' && event.answer.trim() === '';
202
+ const withNeedsBossReplyTransition = (resumeStateId, transitions) => [
203
+ ...transitions.map((transition) => withClearBossReplyContext(transition)),
204
+ {
205
+ guard: guardIs('needsBossReply'),
206
+ target: '#awaitBossReply',
207
+ actions: [rememberCaptainOutput, setPendingBossQuestion(resumeStateId)],
208
+ },
209
+ ];
60
210
  const captainError = {
61
211
  target: '#failed',
62
212
  actions: rememberCaptainError,
@@ -96,6 +246,20 @@ const readyEvents = {
96
246
  ],
97
247
  },
98
248
  };
249
+ const awaitBossReplyEvents = {
250
+ START_CODING: withClearBossReplyContext(readyEvents.START_CODING),
251
+ CONTINUE_IR: withClearBossReplyContext(readyEvents.CONTINUE_IR),
252
+ SUMMARIZE_IR: withClearBossReplyContext(readyEvents.SUMMARIZE_IR),
253
+ BOSS_INTERRUPT: bossInterrupts(jumpableStateIds, [clearBossReplyContext]),
254
+ BOSS_REPLY: [
255
+ {
256
+ guard: bossReplyIsEmpty,
257
+ target: '#failed',
258
+ actions: [clearBossReplyContext, rememberEmptyBossReplyError],
259
+ },
260
+ ...resumableStates(resumableStateIds),
261
+ ],
262
+ };
99
263
  const captainPlaceholder = fromPromise(async () => {
100
264
  throw new Error('captain actor must be provided by the runner');
101
265
  });
@@ -123,28 +287,18 @@ export const codingMachine = setup({
123
287
  description: 'Idle hub: waits for Boss to start or resume a coding sub-procedure.',
124
288
  on: readyEvents,
125
289
  },
290
+ awaitBossReply: {
291
+ id: 'awaitBossReply',
292
+ description: 'Waiting for Boss to answer a player question.',
293
+ on: awaitBossReplyEvents,
294
+ },
126
295
  planAndImplement: {
127
296
  id: 'planAndImplement',
128
297
  description: 'CODE-1: Coder assesses a Boss intent and either implements a single-commit change or drafts an IR.',
129
298
  invoke: {
130
299
  src: 'captain',
131
- input: ({ context }) => ({
132
- player: 'Coder',
133
- sourceItem: 'CODE-1',
134
- intent: context.intent,
135
- prompt: [
136
- 'Assess whether this can be completed in a single commit, following best practices.',
137
- 'If yes, implement and test, updating both code and specs; otherwise, decompose into tasks as a new IR under @specs/iterations.',
138
- 'Consult @specs/map.md for relevant context if needed; ensure it reflects the changes.',
139
- 'Do not commit.',
140
- ].join('\n'),
141
- result: {
142
- singleCommitReady: 'Coder produced uncommitted single-commit changes (Initial Changes).',
143
- irDrafted: 'Coder decomposed the intent into a new IR and drafted it uncommitted (Initial Changes).',
144
- needsBossInput: 'Progress requires additional Boss input.',
145
- },
146
- }),
147
- onDone: [
300
+ input: ({ context }) => planAndImplementInput(context),
301
+ onDone: withNeedsBossReplyTransition('planAndImplement', [
148
302
  {
149
303
  guard: guardIs('singleCommitReady'),
150
304
  target: '#commitCoderInitial',
@@ -169,8 +323,7 @@ export const codingMachine = setup({
169
323
  }),
170
324
  ],
171
325
  },
172
- { guard: guardIs('needsBossInput'), target: '#ready', actions: rememberCaptainOutput },
173
- ],
326
+ ]),
174
327
  onError: captainError,
175
328
  },
176
329
  },
@@ -182,6 +335,7 @@ export const codingMachine = setup({
182
335
  input: ({ context }) => ({
183
336
  player: 'Coder',
184
337
  sourceItem: 'CODE-2',
338
+ ...bossReplyInputFields(context),
185
339
  reviews: context.reviews,
186
340
  prompt: [
187
341
  'For each review item below for the above changes, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
@@ -196,9 +350,10 @@ export const codingMachine = setup({
196
350
  changesMadeMixedAndChallenged: 'Coder produced unstaged/untracked edits spanning both @specs/{user,dev,test}/ and other files AND challenged one or more review items. Output shall include `challenges: <numbered rebuttals, one per challenged item>`.',
197
351
  challengesRaised: 'Coder challenged one or more review items without producing any code edits. Output shall include `challenges: <numbered rebuttals, one per challenged item>`.',
198
352
  accepted: 'Coder accepted the review outcome without further edits.',
353
+ needsBossReply: needsBossReplyDescription,
199
354
  },
200
355
  }),
201
- onDone: [
356
+ onDone: withNeedsBossReplyTransition('respondToReview', [
202
357
  {
203
358
  guard: guardIs('changesMadeSpecs'),
204
359
  target: '#reviewChangesSpecs',
@@ -260,7 +415,7 @@ export const codingMachine = setup({
260
415
  target: '#done',
261
416
  actions: rememberCaptainOutput,
262
417
  },
263
- ],
418
+ ]),
264
419
  onError: captainError,
265
420
  },
266
421
  },
@@ -269,23 +424,8 @@ export const codingMachine = setup({
269
424
  description: 'CODE-3: Coder continues an IR after the previous task or IR draft passed review.',
270
425
  invoke: {
271
426
  src: 'captain',
272
- input: ({ context }) => ({
273
- player: 'Coder',
274
- sourceItem: 'CODE-3',
275
- irNumber: context.irNumber,
276
- prompt: [
277
- 'Continue to implement IR-<#> if not all deliverables and tasks are done.',
278
- 'Implement one task at a time (including corresponding tests if any).',
279
- 'Stop after each task for review — do not commit yet.',
280
- 'If relevant, mark progress in the IR.',
281
- ].join('\n'),
282
- result: {
283
- taskReady: 'Coder produced uncommitted changes for the next IR task (Initial Changes). Output shall include `taskDescription: <one-line description of the task just implemented>`.',
284
- iterationDone: 'All IR deliverables and tasks are done.',
285
- needsBossInput: 'Progress requires additional Boss input.',
286
- },
287
- }),
288
- onDone: [
427
+ input: ({ context }) => continueIrInput(context),
428
+ onDone: withNeedsBossReplyTransition('continueIr', [
289
429
  {
290
430
  guard: guardIs('taskReady'),
291
431
  target: '#commitCoderInitial',
@@ -309,8 +449,7 @@ export const codingMachine = setup({
309
449
  }),
310
450
  ],
311
451
  },
312
- { guard: guardIs('needsBossInput'), target: '#ready', actions: rememberCaptainOutput },
313
- ],
452
+ ]),
314
453
  onError: captainError,
315
454
  },
316
455
  },
@@ -319,31 +458,8 @@ export const codingMachine = setup({
319
458
  description: 'CODE-4: Coder summarizes a completed IR into minimal spec items.',
320
459
  invoke: {
321
460
  src: 'captain',
322
- input: ({ context }) => ({
323
- player: 'Coder',
324
- sourceItem: 'CODE-4',
325
- irNumber: context.irNumber,
326
- prompt: [
327
- 'Read IR-<#> and corresponding commits.',
328
- 'According to @specs/meta.md, add or update spec items to fully capture:',
329
- '',
330
- '- the user requirements in @specs/user,',
331
- '- the system behavior in @specs/dev, and',
332
- '- the integration/system test cases in @specs/test.',
333
- '',
334
- 'The spec items should be the *minimal* set needed to reimplement code without the IR.',
335
- 'The set should be complete and coherent.',
336
- 'Avoid implementation specifics.',
337
- 'Avoid redundant spec items.',
338
- 'Consult @specs/map.md for relevant context and update it to reflect your changes.',
339
- ].join('\n'),
340
- result: {
341
- specsReady: 'Coder produced uncommitted spec updates (Initial Changes).',
342
- noSpecChanges: 'Existing specs already capture the iteration.',
343
- needsBossInput: 'Spec summarization requires additional Boss input.',
344
- },
345
- }),
346
- onDone: [
461
+ input: ({ context }) => summarizeSpecsInput(context),
462
+ onDone: withNeedsBossReplyTransition('summarizeSpecs', [
347
463
  {
348
464
  guard: guardIs('specsReady'),
349
465
  target: '#commitCoderInitial',
@@ -356,9 +472,12 @@ export const codingMachine = setup({
356
472
  }),
357
473
  ],
358
474
  },
359
- { guard: guardIs('noSpecChanges'), target: '#done', actions: rememberCaptainOutput },
360
- { guard: guardIs('needsBossInput'), target: '#ready', actions: rememberCaptainOutput },
361
- ],
475
+ {
476
+ guard: guardIs('noSpecChanges'),
477
+ target: '#done',
478
+ actions: rememberCaptainOutput,
479
+ },
480
+ ]),
362
481
  onError: captainError,
363
482
  },
364
483
  },
@@ -370,6 +489,7 @@ export const codingMachine = setup({
370
489
  input: ({ context }) => ({
371
490
  player: 'Reviewer',
372
491
  sourceItem: 'CODE-5',
492
+ ...bossReplyInputFields(context),
373
493
  intent: context.intent,
374
494
  prompt: [
375
495
  'Review the latest commit.',
@@ -387,9 +507,10 @@ export const codingMachine = setup({
387
507
  result: {
388
508
  noFindings: 'The spec-only commit has no review findings.',
389
509
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
510
+ needsBossReply: needsBossReplyDescription,
390
511
  },
391
512
  }),
392
- onDone: [
513
+ onDone: withNeedsBossReplyTransition('reviewBossCommitSpecs', [
393
514
  { guard: noFindingsAfter('continueIr'), target: '#continueIr', actions: rememberCaptainOutput },
394
515
  { guard: noFindingsAfter('summarizeSpecs'), target: '#summarizeSpecs', actions: rememberCaptainOutput },
395
516
  { guard: noFindingsAfter('done'), target: '#done', actions: rememberCaptainOutput },
@@ -398,7 +519,7 @@ export const codingMachine = setup({
398
519
  target: '#respondToReview',
399
520
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'commit' })],
400
521
  },
401
- ],
522
+ ]),
402
523
  onError: captainError,
403
524
  },
404
525
  },
@@ -410,6 +531,7 @@ export const codingMachine = setup({
410
531
  input: ({ context }) => ({
411
532
  player: 'Reviewer',
412
533
  sourceItem: 'CODE-6',
534
+ ...bossReplyInputFields(context),
413
535
  intent: context.intent,
414
536
  prompt: [
415
537
  'Review the latest commit.',
@@ -422,9 +544,10 @@ export const codingMachine = setup({
422
544
  result: {
423
545
  noFindings: 'The code-only commit has no review findings.',
424
546
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
547
+ needsBossReply: needsBossReplyDescription,
425
548
  },
426
549
  }),
427
- onDone: [
550
+ onDone: withNeedsBossReplyTransition('reviewBossCommitCode', [
428
551
  { guard: noFindingsAfter('continueIr'), target: '#continueIr', actions: rememberCaptainOutput },
429
552
  { guard: noFindingsAfter('summarizeSpecs'), target: '#summarizeSpecs', actions: rememberCaptainOutput },
430
553
  { guard: noFindingsAfter('done'), target: '#done', actions: rememberCaptainOutput },
@@ -433,7 +556,7 @@ export const codingMachine = setup({
433
556
  target: '#respondToReview',
434
557
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'commit' })],
435
558
  },
436
- ],
559
+ ]),
437
560
  onError: captainError,
438
561
  },
439
562
  },
@@ -445,6 +568,7 @@ export const codingMachine = setup({
445
568
  input: ({ context }) => ({
446
569
  player: 'Reviewer',
447
570
  sourceItem: 'CODE-7',
571
+ ...bossReplyInputFields(context),
448
572
  intent: context.intent,
449
573
  prompt: [
450
574
  'Review the latest commit.',
@@ -464,9 +588,10 @@ export const codingMachine = setup({
464
588
  result: {
465
589
  noFindings: 'The mixed commit has no review findings.',
466
590
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
591
+ needsBossReply: needsBossReplyDescription,
467
592
  },
468
593
  }),
469
- onDone: [
594
+ onDone: withNeedsBossReplyTransition('reviewBossCommitMixed', [
470
595
  { guard: noFindingsAfter('continueIr'), target: '#continueIr', actions: rememberCaptainOutput },
471
596
  { guard: noFindingsAfter('summarizeSpecs'), target: '#summarizeSpecs', actions: rememberCaptainOutput },
472
597
  { guard: noFindingsAfter('done'), target: '#done', actions: rememberCaptainOutput },
@@ -475,7 +600,7 @@ export const codingMachine = setup({
475
600
  target: '#respondToReview',
476
601
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'commit' })],
477
602
  },
478
- ],
603
+ ]),
479
604
  onError: captainError,
480
605
  },
481
606
  },
@@ -487,6 +612,7 @@ export const codingMachine = setup({
487
612
  input: ({ context }) => ({
488
613
  player: 'Reviewer',
489
614
  sourceItem: 'CODE-8',
615
+ ...bossReplyInputFields(context),
490
616
  irNumber: context.irNumber,
491
617
  taskDescription: context.taskDescription,
492
618
  prompt: [
@@ -505,9 +631,10 @@ export const codingMachine = setup({
505
631
  result: {
506
632
  noFindings: 'The IR-task spec-only commit has no review findings.',
507
633
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
634
+ needsBossReply: needsBossReplyDescription,
508
635
  },
509
636
  }),
510
- onDone: [
637
+ onDone: withNeedsBossReplyTransition('reviewIrTaskCommitSpecs', [
511
638
  { guard: noFindingsAfter('continueIr'), target: '#continueIr', actions: rememberCaptainOutput },
512
639
  { guard: noFindingsAfter('summarizeSpecs'), target: '#summarizeSpecs', actions: rememberCaptainOutput },
513
640
  { guard: noFindingsAfter('done'), target: '#done', actions: rememberCaptainOutput },
@@ -516,7 +643,7 @@ export const codingMachine = setup({
516
643
  target: '#respondToReview',
517
644
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'commit' })],
518
645
  },
519
- ],
646
+ ]),
520
647
  onError: captainError,
521
648
  },
522
649
  },
@@ -528,6 +655,7 @@ export const codingMachine = setup({
528
655
  input: ({ context }) => ({
529
656
  player: 'Reviewer',
530
657
  sourceItem: 'CODE-9',
658
+ ...bossReplyInputFields(context),
531
659
  irNumber: context.irNumber,
532
660
  taskDescription: context.taskDescription,
533
661
  prompt: [
@@ -541,9 +669,10 @@ export const codingMachine = setup({
541
669
  result: {
542
670
  noFindings: 'The IR-task code-only commit has no review findings.',
543
671
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
672
+ needsBossReply: needsBossReplyDescription,
544
673
  },
545
674
  }),
546
- onDone: [
675
+ onDone: withNeedsBossReplyTransition('reviewIrTaskCommitCode', [
547
676
  { guard: noFindingsAfter('continueIr'), target: '#continueIr', actions: rememberCaptainOutput },
548
677
  { guard: noFindingsAfter('summarizeSpecs'), target: '#summarizeSpecs', actions: rememberCaptainOutput },
549
678
  { guard: noFindingsAfter('done'), target: '#done', actions: rememberCaptainOutput },
@@ -552,7 +681,7 @@ export const codingMachine = setup({
552
681
  target: '#respondToReview',
553
682
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'commit' })],
554
683
  },
555
- ],
684
+ ]),
556
685
  onError: captainError,
557
686
  },
558
687
  },
@@ -564,6 +693,7 @@ export const codingMachine = setup({
564
693
  input: ({ context }) => ({
565
694
  player: 'Reviewer',
566
695
  sourceItem: 'CODE-10',
696
+ ...bossReplyInputFields(context),
567
697
  irNumber: context.irNumber,
568
698
  taskDescription: context.taskDescription,
569
699
  prompt: [
@@ -584,9 +714,10 @@ export const codingMachine = setup({
584
714
  result: {
585
715
  noFindings: 'The IR-task mixed commit has no review findings.',
586
716
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
717
+ needsBossReply: needsBossReplyDescription,
587
718
  },
588
719
  }),
589
- onDone: [
720
+ onDone: withNeedsBossReplyTransition('reviewIrTaskCommitMixed', [
590
721
  { guard: noFindingsAfter('continueIr'), target: '#continueIr', actions: rememberCaptainOutput },
591
722
  { guard: noFindingsAfter('summarizeSpecs'), target: '#summarizeSpecs', actions: rememberCaptainOutput },
592
723
  { guard: noFindingsAfter('done'), target: '#done', actions: rememberCaptainOutput },
@@ -595,7 +726,7 @@ export const codingMachine = setup({
595
726
  target: '#respondToReview',
596
727
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'commit' })],
597
728
  },
598
- ],
729
+ ]),
599
730
  onError: captainError,
600
731
  },
601
732
  },
@@ -604,9 +735,10 @@ export const codingMachine = setup({
604
735
  description: 'CODE-11: Reviewer reviews uncommitted Coder changes that touch only @specs/{user,dev,test}/ with no accompanying rebuttals.',
605
736
  invoke: {
606
737
  src: 'captain',
607
- input: () => ({
738
+ input: ({ context }) => ({
608
739
  player: 'Reviewer',
609
740
  sourceItem: 'CODE-11',
741
+ ...bossReplyInputFields(context),
610
742
  prompt: [
611
743
  'Review the unstaged/untracked changes.',
612
744
  'Understand the intent.',
@@ -623,9 +755,10 @@ export const codingMachine = setup({
623
755
  result: {
624
756
  noFindings: 'The uncommitted spec-only changes are ready to commit.',
625
757
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
758
+ needsBossReply: needsBossReplyDescription,
626
759
  },
627
760
  }),
628
- onDone: [
761
+ onDone: withNeedsBossReplyTransition('reviewChangesSpecs', [
629
762
  {
630
763
  guard: guardIs('noFindings'),
631
764
  target: '#commitJoint',
@@ -636,7 +769,7 @@ export const codingMachine = setup({
636
769
  target: '#respondToReview',
637
770
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'changes' })],
638
771
  },
639
- ],
772
+ ]),
640
773
  onError: captainError,
641
774
  },
642
775
  },
@@ -645,9 +778,10 @@ export const codingMachine = setup({
645
778
  description: 'CODE-12: Reviewer reviews uncommitted Coder changes that touch only files outside @specs/{user,dev,test}/ with no accompanying rebuttals.',
646
779
  invoke: {
647
780
  src: 'captain',
648
- input: () => ({
781
+ input: ({ context }) => ({
649
782
  player: 'Reviewer',
650
783
  sourceItem: 'CODE-12',
784
+ ...bossReplyInputFields(context),
651
785
  prompt: [
652
786
  'Review the unstaged/untracked changes.',
653
787
  'Understand the intent.',
@@ -659,9 +793,10 @@ export const codingMachine = setup({
659
793
  result: {
660
794
  noFindings: 'The uncommitted code-only changes are ready to commit.',
661
795
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
796
+ needsBossReply: needsBossReplyDescription,
662
797
  },
663
798
  }),
664
- onDone: [
799
+ onDone: withNeedsBossReplyTransition('reviewChangesCode', [
665
800
  {
666
801
  guard: guardIs('noFindings'),
667
802
  target: '#commitJoint',
@@ -672,7 +807,7 @@ export const codingMachine = setup({
672
807
  target: '#respondToReview',
673
808
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'changes' })],
674
809
  },
675
- ],
810
+ ]),
676
811
  onError: captainError,
677
812
  },
678
813
  },
@@ -681,9 +816,10 @@ export const codingMachine = setup({
681
816
  description: 'CODE-13: Reviewer reviews uncommitted Coder changes that touch both @specs/{user,dev,test}/ and other files with no accompanying rebuttals.',
682
817
  invoke: {
683
818
  src: 'captain',
684
- input: () => ({
819
+ input: ({ context }) => ({
685
820
  player: 'Reviewer',
686
821
  sourceItem: 'CODE-13',
822
+ ...bossReplyInputFields(context),
687
823
  prompt: [
688
824
  'Review the unstaged/untracked changes.',
689
825
  'Understand the intent.',
@@ -702,9 +838,10 @@ export const codingMachine = setup({
702
838
  result: {
703
839
  noFindings: 'The uncommitted mixed changes are ready to commit.',
704
840
  hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
841
+ needsBossReply: needsBossReplyDescription,
705
842
  },
706
843
  }),
707
- onDone: [
844
+ onDone: withNeedsBossReplyTransition('reviewChangesMixed', [
708
845
  {
709
846
  guard: guardIs('noFindings'),
710
847
  target: '#commitJoint',
@@ -715,7 +852,7 @@ export const codingMachine = setup({
715
852
  target: '#respondToReview',
716
853
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'changes' })],
717
854
  },
718
- ],
855
+ ]),
719
856
  onError: captainError,
720
857
  },
721
858
  },
@@ -727,6 +864,7 @@ export const codingMachine = setup({
727
864
  input: ({ context }) => ({
728
865
  player: 'Reviewer',
729
866
  sourceItem: 'CODE-15',
867
+ ...bossReplyInputFields(context),
730
868
  reviews: context.reviews,
731
869
  challenges: context.challenges,
732
870
  prompt: [
@@ -746,9 +884,10 @@ export const codingMachine = setup({
746
884
  result: {
747
885
  approved: 'The new spec-only changes are ready to commit and every rebuttal was accepted (or no items remained open).',
748
886
  needsRevision: 'The combined round needs more work: the review produced findings, one or more rebuttals were rejected, or both. Output shall include `reviews: <numbered list of any new findings or rejected rebuttals to address>`.',
887
+ needsBossReply: needsBossReplyDescription,
749
888
  },
750
889
  }),
751
- onDone: [
890
+ onDone: withNeedsBossReplyTransition('reviewChangesAndChallengesSpecs', [
752
891
  {
753
892
  guard: guardIs('approved'),
754
893
  target: '#commitJoint',
@@ -759,7 +898,7 @@ export const codingMachine = setup({
759
898
  target: '#respondToReview',
760
899
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'changes' })],
761
900
  },
762
- ],
901
+ ]),
763
902
  onError: captainError,
764
903
  },
765
904
  },
@@ -771,6 +910,7 @@ export const codingMachine = setup({
771
910
  input: ({ context }) => ({
772
911
  player: 'Reviewer',
773
912
  sourceItem: 'CODE-16',
913
+ ...bossReplyInputFields(context),
774
914
  reviews: context.reviews,
775
915
  challenges: context.challenges,
776
916
  prompt: [
@@ -785,9 +925,10 @@ export const codingMachine = setup({
785
925
  result: {
786
926
  approved: 'The new code-only changes are ready to commit and every rebuttal was accepted (or no items remained open).',
787
927
  needsRevision: 'The combined round needs more work: the review produced findings, one or more rebuttals were rejected, or both. Output shall include `reviews: <numbered list of any new findings or rejected rebuttals to address>`.',
928
+ needsBossReply: needsBossReplyDescription,
788
929
  },
789
930
  }),
790
- onDone: [
931
+ onDone: withNeedsBossReplyTransition('reviewChangesAndChallengesCode', [
791
932
  {
792
933
  guard: guardIs('approved'),
793
934
  target: '#commitJoint',
@@ -798,7 +939,7 @@ export const codingMachine = setup({
798
939
  target: '#respondToReview',
799
940
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'changes' })],
800
941
  },
801
- ],
942
+ ]),
802
943
  onError: captainError,
803
944
  },
804
945
  },
@@ -810,6 +951,7 @@ export const codingMachine = setup({
810
951
  input: ({ context }) => ({
811
952
  player: 'Reviewer',
812
953
  sourceItem: 'CODE-17',
954
+ ...bossReplyInputFields(context),
813
955
  reviews: context.reviews,
814
956
  challenges: context.challenges,
815
957
  prompt: [
@@ -831,9 +973,10 @@ export const codingMachine = setup({
831
973
  result: {
832
974
  approved: 'The new mixed changes are ready to commit and every rebuttal was accepted (or no items remained open).',
833
975
  needsRevision: 'The combined round needs more work: the review produced findings, one or more rebuttals were rejected, or both. Output shall include `reviews: <numbered list of any new findings or rejected rebuttals to address>`.',
976
+ needsBossReply: needsBossReplyDescription,
834
977
  },
835
978
  }),
836
- onDone: [
979
+ onDone: withNeedsBossReplyTransition('reviewChangesAndChallengesMixed', [
837
980
  {
838
981
  guard: guardIs('approved'),
839
982
  target: '#commitJoint',
@@ -844,7 +987,7 @@ export const codingMachine = setup({
844
987
  target: '#respondToReview',
845
988
  actions: [rememberCaptainOutput, assign({ reviewSubject: () => 'changes' })],
846
989
  },
847
- ],
990
+ ]),
848
991
  onError: captainError,
849
992
  },
850
993
  },
@@ -856,6 +999,7 @@ export const codingMachine = setup({
856
999
  input: ({ context }) => ({
857
1000
  player: 'Reviewer',
858
1001
  sourceItem: 'CODE-14',
1002
+ ...bossReplyInputFields(context),
859
1003
  reviews: context.reviews,
860
1004
  challenges: context.challenges,
861
1005
  prompt: [
@@ -865,9 +1009,10 @@ export const codingMachine = setup({
865
1009
  challengeAccepted: 'Reviewer accepted the rebuttal — no further review edits are required.',
866
1010
  challengeRejected: 'Reviewer rejected the rebuttal — Coder must respond again.',
867
1011
  noOpenItems: 'No review items remain open.',
1012
+ needsBossReply: needsBossReplyDescription,
868
1013
  },
869
1014
  }),
870
- onDone: [
1015
+ onDone: withNeedsBossReplyTransition('adjudicateChallenges', [
871
1016
  {
872
1017
  guard: ({ context, event }) => (outputOf(event)?.guard === 'challengeAccepted' ||
873
1018
  outputOf(event)?.guard === 'noOpenItems') &&
@@ -904,7 +1049,7 @@ export const codingMachine = setup({
904
1049
  target: '#respondToReview',
905
1050
  actions: rememberCaptainOutput,
906
1051
  },
907
- ],
1052
+ ]),
908
1053
  onError: captainError,
909
1054
  },
910
1055
  },
@@ -916,6 +1061,7 @@ export const codingMachine = setup({
916
1061
  input: ({ context }) => ({
917
1062
  player: 'Committer',
918
1063
  sourceItem: 'CODE-18',
1064
+ ...bossReplyInputFields(context),
919
1065
  coderPlayer: context.coderPlayer,
920
1066
  prompt: [
921
1067
  'Make a commit of the changes that belong in the repo, following @specs/dev/git.md (reread if necessary).',
@@ -927,9 +1073,10 @@ export const codingMachine = setup({
927
1073
  committedMixed: 'Committed changes that span both @specs/{user,dev,test}/ and other files.',
928
1074
  noRelevantChanges: 'There are no relevant changes to commit.',
929
1075
  needsBossInput: 'Committing requires additional Boss input.',
1076
+ needsBossReply: needsBossReplyDescription,
930
1077
  },
931
1078
  }),
932
- onDone: [
1079
+ onDone: withNeedsBossReplyTransition('commitCoderInitial', [
933
1080
  {
934
1081
  guard: guardAndOrigin('committedSpecs', 'bossIntent'),
935
1082
  target: '#reviewBossCommitSpecs',
@@ -962,7 +1109,7 @@ export const codingMachine = setup({
962
1109
  },
963
1110
  { guard: guardIs('noRelevantChanges'), target: '#ready', actions: rememberCaptainOutput },
964
1111
  { guard: guardIs('needsBossInput'), target: '#ready', actions: rememberCaptainOutput },
965
- ],
1112
+ ]),
966
1113
  onError: captainError,
967
1114
  },
968
1115
  },
@@ -974,6 +1121,7 @@ export const codingMachine = setup({
974
1121
  input: ({ context }) => ({
975
1122
  player: 'Committer',
976
1123
  sourceItem: 'CODE-19',
1124
+ ...bossReplyInputFields(context),
977
1125
  coderPlayer: context.coderPlayer,
978
1126
  reviewerPlayer: context.reviewerPlayer,
979
1127
  prompt: [
@@ -984,9 +1132,10 @@ export const codingMachine = setup({
984
1132
  committed: 'Relevant changes were committed.',
985
1133
  noRelevantChanges: 'There are no relevant changes to commit.',
986
1134
  needsBossInput: 'Committing requires additional Boss input.',
1135
+ needsBossReply: needsBossReplyDescription,
987
1136
  },
988
1137
  }),
989
- onDone: [
1138
+ onDone: withNeedsBossReplyTransition('commitJoint', [
990
1139
  {
991
1140
  guard: ({ context, event }) => outputOf(event)?.guard === 'committed' && context.afterReview === 'continueIr',
992
1141
  target: '#continueIr',
@@ -1004,7 +1153,7 @@ export const codingMachine = setup({
1004
1153
  },
1005
1154
  { guard: guardIs('noRelevantChanges'), target: '#done', actions: rememberCaptainOutput },
1006
1155
  { guard: guardIs('needsBossInput'), target: '#ready', actions: rememberCaptainOutput },
1007
- ],
1156
+ ]),
1008
1157
  onError: captainError,
1009
1158
  },
1010
1159
  },