@sublang/playbook 7.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +20 -7
  2. package/docs/cli.md +88 -43
  3. package/docs/configuration.md +221 -119
  4. package/docs/embedding.md +78 -27
  5. package/package.json +4 -3
  6. package/reference/sdlc/captain.playbook/captain.playbook.js +16 -5
  7. package/reference/sdlc/captain.playbook/captain.playbook.ts +20 -6
  8. package/reference/sdlc/code.md +1 -1
  9. package/reference/sdlc/code.playbook/bin/interactive-session.js +816 -0
  10. package/reference/sdlc/code.playbook/bin/launch-config.js +1078 -116
  11. package/reference/sdlc/code.playbook/bin/playbook.js +489 -34
  12. package/reference/sdlc/code.playbook/bin/run.js +283 -298
  13. package/reference/sdlc/code.playbook/bin/session-store.js +818 -26
  14. package/reference/sdlc/code.playbook/code.fsm.d.ts +9 -6
  15. package/reference/sdlc/code.playbook/code.fsm.introspect.js +2 -2
  16. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +2 -2
  17. package/reference/sdlc/code.playbook/code.fsm.js +18 -15
  18. package/reference/sdlc/code.playbook/code.fsm.ts +21 -21
  19. package/reference/sdlc/code.playbook/code.gears.md +1 -1
  20. package/reference/sdlc/code.playbook/code.playbook.d.ts +2 -1
  21. package/reference/sdlc/code.playbook/code.playbook.js +25 -15
  22. package/reference/sdlc/code.playbook/code.playbook.ts +34 -17
  23. package/reference/sdlc/code.playbook/code.registry.d.ts +5 -13
  24. package/reference/sdlc/code.playbook/code.registry.js +3 -10
  25. package/reference/sdlc/code.playbook/code.registry.ts +7 -32
  26. package/reference/sdlc/code.playbook/playbook-captain.d.ts +39 -14
  27. package/reference/sdlc/code.playbook/playbook-captain.js +1014 -299
  28. package/reference/sdlc/code.playbook/playbook-captain.ts +1450 -406
  29. package/reference/sdlc/code.playbook/playbook.config.template.yaml +41 -49
  30. package/reference/sdlc/decide.md +4 -4
  31. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +10 -10
  32. package/reference/sdlc/decide.playbook/decide.fsm.js +21 -14
  33. package/reference/sdlc/decide.playbook/decide.fsm.ts +27 -23
  34. package/reference/sdlc/decide.playbook/decide.gears.md +3 -5
  35. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +11 -13
  36. package/reference/sdlc/decide.playbook/decide.playbook.js +465 -246
  37. package/reference/sdlc/decide.playbook/decide.playbook.ts +623 -283
  38. package/reference/sdlc/decide.playbook/decide.registry.d.ts +5 -13
  39. package/reference/sdlc/decide.playbook/decide.registry.js +3 -9
  40. package/reference/sdlc/decide.playbook/decide.registry.ts +7 -31
  41. package/reference/sdlc/review.md +4 -5
  42. package/reference/sdlc/review.playbook/review.fsm.d.ts +9 -11
  43. package/reference/sdlc/review.playbook/review.fsm.js +30 -24
  44. package/reference/sdlc/review.playbook/review.fsm.ts +39 -35
  45. package/reference/sdlc/review.playbook/review.gears.md +6 -5
  46. package/reference/sdlc/review.playbook/review.playbook.d.ts +2 -1
  47. package/reference/sdlc/review.playbook/review.playbook.js +29 -23
  48. package/reference/sdlc/review.playbook/review.playbook.ts +38 -28
  49. package/reference/sdlc/review.playbook/review.registry.d.ts +5 -13
  50. package/reference/sdlc/review.playbook/review.registry.js +3 -16
  51. package/reference/sdlc/review.playbook/review.registry.ts +7 -38
  52. package/slc/gears2fsm.md +45 -24
  53. package/slc/link.md +297 -135
  54. package/slc/text2gears.md +19 -18
  55. package/src/runtime.d.ts +21 -16
  56. package/src/runtime.ts +20 -23
  57. package/src/xstate-playbook-runtime.d.ts +34 -20
  58. package/src/xstate-playbook-runtime.js +973 -400
  59. package/src/xstate-playbook-runtime.ts +1203 -457
  60. package/src/xstate-runtime.d.ts +17 -7
  61. package/src/xstate-runtime.js +198 -81
  62. package/src/xstate-runtime.ts +339 -112
@@ -1,4 +1,4 @@
1
- import { type PlaybookRuntime, type PlaybookRuntimeOptions } from './decide.playbook.js';
1
+ import { type PlaybookRuntime } from './decide.playbook.js';
2
2
  export interface PlaybookSummaryPolicy {
3
3
  stateCountLabels: Readonly<Record<string, string>>;
4
4
  copyPasteGuardNames: readonly string[];
@@ -7,24 +7,17 @@ export interface PlaybookSummaryPolicy {
7
7
  copyPastes: number;
8
8
  }, rounds: number): string;
9
9
  }
10
- export interface RegistryPlayer {
11
- id: string;
12
- adapter?: string;
13
- model?: string;
14
- }
15
- export interface CreateDecideRuntimeOptions {
16
- captainOptions: unknown;
17
- players: readonly RegistryPlayer[];
18
- }
19
10
  export type DecideOptions = Readonly<Record<string, never>>;
20
11
  export interface DecidePlaybookRegistryEntry {
21
12
  id: 'decide';
22
13
  command: 'decide';
23
14
  intent: string;
15
+ artifactSchema: 2;
24
16
  requiredRoleIds: readonly ['coder', 'reviewer'];
17
+ concurrentRoleSets: readonly [readonly ['coder', 'reviewer']];
25
18
  summaryPolicy: PlaybookSummaryPolicy;
26
- validateOptions(captainOptions: unknown): DecideOptions;
27
- createRuntime(options: CreateDecideRuntimeOptions): PlaybookRuntime;
19
+ validateOptions(optionSlice: unknown): DecideOptions;
20
+ createRuntime(options: DecideOptions): PlaybookRuntime;
28
21
  }
29
22
  export declare const decideStateCountLabels: {
30
23
  readonly independentProposals: "proposal round";
@@ -36,6 +29,5 @@ export declare function decideSavedCountsLine(counts: {
36
29
  }, rounds: number): string;
37
30
  export declare const decideSummaryPolicy: PlaybookSummaryPolicy;
38
31
  export declare function validateDecideOptions(optionSlice: unknown): DecideOptions;
39
- export declare function createDecideRuntimeOptions({ captainOptions, players, }: CreateDecideRuntimeOptions): PlaybookRuntimeOptions;
40
32
  export declare const decidePlaybookRegistryEntry: DecidePlaybookRegistryEntry;
41
33
  export default decidePlaybookRegistryEntry;
@@ -38,23 +38,17 @@ export function validateDecideOptions(optionSlice) {
38
38
  }
39
39
  return Object.freeze({});
40
40
  }
41
- function playerIdentity(players, id) {
42
- const player = players.find((entry) => entry.id === id);
43
- return player?.model ?? player?.adapter ?? id;
44
- }
45
- export function createDecideRuntimeOptions({ captainOptions, players, }) {
46
- validateDecideOptions(captainOptions);
47
- return { coderLlm: playerIdentity(players, 'coder') };
48
- }
49
41
  export const decidePlaybookRegistryEntry = {
50
42
  id: 'decide',
51
43
  command: 'decide',
52
44
  intent: 'turn independent Coder and Reviewer proposals into an approved spec-design commit',
45
+ artifactSchema: 2,
53
46
  requiredRoleIds: ['coder', 'reviewer'],
47
+ concurrentRoleSets: [['coder', 'reviewer']],
54
48
  summaryPolicy: decideSummaryPolicy,
55
49
  validateOptions: validateDecideOptions,
56
50
  createRuntime(options) {
57
- return createPlaybookRuntime(createDecideRuntimeOptions(options));
51
+ return createPlaybookRuntime(options);
58
52
  },
59
53
  };
60
54
  export default decidePlaybookRegistryEntry;
@@ -3,7 +3,6 @@
3
3
 
4
4
  import createPlaybookRuntime, {
5
5
  type PlaybookRuntime,
6
- type PlaybookRuntimeOptions,
7
6
  } from './decide.playbook.js';
8
7
 
9
8
  export interface PlaybookSummaryPolicy {
@@ -15,27 +14,18 @@ export interface PlaybookSummaryPolicy {
15
14
  ): string;
16
15
  }
17
16
 
18
- export interface RegistryPlayer {
19
- id: string;
20
- adapter?: string;
21
- model?: string;
22
- }
23
-
24
- export interface CreateDecideRuntimeOptions {
25
- captainOptions: unknown;
26
- players: readonly RegistryPlayer[];
27
- }
28
-
29
17
  export type DecideOptions = Readonly<Record<string, never>>;
30
18
 
31
19
  export interface DecidePlaybookRegistryEntry {
32
20
  id: 'decide';
33
21
  command: 'decide';
34
22
  intent: string;
23
+ artifactSchema: 2;
35
24
  requiredRoleIds: readonly ['coder', 'reviewer'];
25
+ concurrentRoleSets: readonly [readonly ['coder', 'reviewer']];
36
26
  summaryPolicy: PlaybookSummaryPolicy;
37
- validateOptions(captainOptions: unknown): DecideOptions;
38
- createRuntime(options: CreateDecideRuntimeOptions): PlaybookRuntime;
27
+ validateOptions(optionSlice: unknown): DecideOptions;
28
+ createRuntime(options: DecideOptions): PlaybookRuntime;
39
29
  }
40
30
 
41
31
  export const decideStateCountLabels = {
@@ -93,32 +83,18 @@ export function validateDecideOptions(optionSlice: unknown): DecideOptions {
93
83
  return Object.freeze({});
94
84
  }
95
85
 
96
- function playerIdentity(
97
- players: readonly RegistryPlayer[],
98
- id: string,
99
- ): string {
100
- const player = players.find((entry) => entry.id === id);
101
- return player?.model ?? player?.adapter ?? id;
102
- }
103
-
104
- export function createDecideRuntimeOptions({
105
- captainOptions,
106
- players,
107
- }: CreateDecideRuntimeOptions): PlaybookRuntimeOptions {
108
- validateDecideOptions(captainOptions);
109
- return { coderLlm: playerIdentity(players, 'coder') };
110
- }
111
-
112
86
  export const decidePlaybookRegistryEntry: DecidePlaybookRegistryEntry = {
113
87
  id: 'decide',
114
88
  command: 'decide',
115
89
  intent:
116
90
  'turn independent Coder and Reviewer proposals into an approved spec-design commit',
91
+ artifactSchema: 2,
117
92
  requiredRoleIds: ['coder', 'reviewer'],
93
+ concurrentRoleSets: [['coder', 'reviewer']],
118
94
  summaryPolicy: decideSummaryPolicy,
119
95
  validateOptions: validateDecideOptions,
120
96
  createRuntime(options) {
121
- return createPlaybookRuntime(createDecideRuntimeOptions(options));
97
+ return createPlaybookRuntime(options);
122
98
  },
123
99
  };
124
100
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Review
6
6
 
7
- Players:
7
+ Roles:
8
8
 
9
9
  - Coder
10
10
  - Reviewer
@@ -20,21 +20,19 @@ At the first review round, Captain shall give Reviewer the following instruction
20
20
 
21
21
  ```markdown
22
22
  A new review begins on the latest commit.
23
- Review the latest commit and resulting repository state; read the commit message first for its intent and rationale.
23
+ Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.
24
24
  ```
25
25
 
26
26
  After every review-fix commit, Captain shall give Reviewer the following instruction:
27
27
 
28
28
  ```markdown
29
- Review the latest commit and resulting repository state; read the commit message first for its intent and rationale.
29
+ Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.
30
30
  ```
31
31
 
32
32
  When Coder rejects every finding and makes no commit, Captain shall give Reviewer the following instruction:
33
33
 
34
34
  ```markdown
35
35
  No new commit was made because Coder rejected every finding.
36
- For any rebuttal, accept or challenge it.
37
- State which findings, if any, remain.
38
36
  ```
39
37
 
40
38
  At the start of *every* review round, Captain shall relay to Reviewer any Coder feedback from the preceding round and any relevant run results, in quotes (`>`) after the instruction.
@@ -44,6 +42,7 @@ At the start of *every* review round, Captain shall append the following instruc
44
42
  ```markdown
45
43
  Understand the full picture and think systematically about the underlying design.
46
44
  Continue to identify issues or improvements, if any (numbered; no duplication).
45
+ For any rebuttal, accept or challenge it.
47
46
  Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.
48
47
  Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.
49
48
  For specs, flag stale, missing, over-specified, or under-specified ones.
@@ -1,22 +1,22 @@
1
- type Player = 'Coder' | 'Reviewer';
1
+ type Role = 'coder' | 'reviewer';
2
2
  type JumpableStateId = 'reviewInitial';
3
3
  export type ResumableStateId = 'reviewInitial' | 'addressFindings' | 'reviewAfterCommit' | 'reviewAfterRebuttal';
4
4
  export interface PendingBossQuestion {
5
5
  questionId: ResumableStateId;
6
6
  resumeStateId: ResumableStateId;
7
7
  sourceItem: `REVIEW-${1 | 2 | 3 | 4}`;
8
- player: Player;
8
+ asker: {
9
+ readonly kind: 'role';
10
+ readonly roleId: Role;
11
+ };
9
12
  question: string;
10
13
  }
11
14
  export interface ReviewOutput {
12
15
  approvedCommit: 'latest';
13
16
  noUnsettledFindings: true;
14
17
  }
15
- export interface ReviewInput {
16
- coderLlm: string;
17
- reviewerLlm: string;
18
- }
19
- export interface ReviewContext extends ReviewInput {
18
+ export type ReviewInput = Readonly<Record<string, never>>;
19
+ export interface ReviewContext {
20
20
  callerInput?: string;
21
21
  reviewerOutput?: string;
22
22
  coderOutput?: string;
@@ -39,14 +39,12 @@ export type ReviewEvent = {
39
39
  export interface PlayerInput {
40
40
  stateId: ResumableStateId;
41
41
  sourceItem: `REVIEW-${1 | 2 | 3 | 4}`;
42
- player: Player;
42
+ role: Role;
43
43
  prompt: string;
44
44
  result: Readonly<Record<string, string>>;
45
45
  callerInput?: string;
46
46
  reviewerOutput?: string;
47
47
  coderOutput?: string;
48
- coderLlm?: string;
49
- reviewerLlm?: string;
50
48
  pendingBossQuestion?: PendingBossQuestion;
51
49
  bossReply?: string;
52
50
  }
@@ -151,7 +149,7 @@ export declare const reviewMachine: import("xstate").StateMachine<ReviewContext,
151
149
  } | {
152
150
  type: "restartInitialReview";
153
151
  params: unknown;
154
- }, never, "done" | "failed" | "awaitBossReply" | "ready" | "reviewInitial" | "addressFindings" | "reviewAfterCommit" | "reviewAfterRebuttal", string, ReviewInput, ReviewOutput, import("xstate").EventObject, import("xstate").MetaObject, {
152
+ }, never, "done" | "failed" | "awaitBossReply" | "ready" | "reviewInitial" | "addressFindings" | "reviewAfterCommit" | "reviewAfterRebuttal", string, Readonly<Record<string, never>>, ReviewOutput, import("xstate").EventObject, import("xstate").MetaObject, {
155
153
  id: "review";
156
154
  states: {
157
155
  readonly ready: {
@@ -7,6 +7,7 @@ const NEEDS_BOSS_REPLY_DESCRIPTION = "The acting agent's prose surfaces a clarif
7
7
  const SHARED_REVIEW_INSTRUCTION = [
8
8
  'Understand the full picture and think systematically about the underlying design.',
9
9
  'Continue to identify issues or improvements, if any (numbered; no duplication).',
10
+ 'For any rebuttal, accept or challenge it.',
10
11
  'Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.',
11
12
  'Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.',
12
13
  'For specs, flag stale, missing, over-specified, or under-specified ones.',
@@ -20,14 +21,14 @@ const SHARED_REVIEW_INSTRUCTION = [
20
21
  ].join('\n');
21
22
  const INITIAL_REVIEW_PROMPT = [
22
23
  'A new review begins on the latest commit.',
23
- 'Review the latest commit and resulting repository state; read the commit message first for its intent and rationale.',
24
+ 'Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.',
24
25
  '',
25
26
  '> <caller-input>',
26
27
  '',
27
28
  SHARED_REVIEW_INSTRUCTION,
28
29
  ].join('\n');
29
30
  const POST_COMMIT_REVIEW_PROMPT = [
30
- 'Review the latest commit and resulting repository state; read the commit message first for its intent and rationale.',
31
+ 'Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.',
31
32
  '',
32
33
  '> <coder-output>',
33
34
  '',
@@ -35,8 +36,6 @@ const POST_COMMIT_REVIEW_PROMPT = [
35
36
  ].join('\n');
36
37
  const REBUTTAL_REVIEW_PROMPT = [
37
38
  'No new commit was made because Coder rejected every finding.',
38
- 'For any rebuttal, accept or challenge it.',
39
- 'State which findings, if any, remain.',
40
39
  '',
41
40
  '> <coder-output>',
42
41
  '',
@@ -85,20 +84,32 @@ const stateDescriptions = {
85
84
  failed: 'Retains a recoverable REVIEW failure for the caller.',
86
85
  done: 'The latest commit is approved with no unsettled findings.',
87
86
  };
88
- const playbookMeta = (stateId, player) => ({
87
+ const playbookMeta = (stateId, role) => ({
89
88
  playbook: {
90
89
  stateId,
91
90
  description: stateDescriptions[stateId],
92
- ...(player === undefined ? {} : { player }),
91
+ ...(role === undefined ? {} : { role }),
93
92
  },
94
93
  });
95
94
  const outputOf = (event) => event.output;
96
95
  const isNonEmptyString = (value) => typeof value === 'string' && value.trim().length > 0;
97
96
  const stateMetadata = {
98
- reviewInitial: { sourceItem: 'REVIEW-1', player: 'Reviewer' },
99
- addressFindings: { sourceItem: 'REVIEW-2', player: 'Coder' },
100
- reviewAfterCommit: { sourceItem: 'REVIEW-3', player: 'Reviewer' },
101
- reviewAfterRebuttal: { sourceItem: 'REVIEW-4', player: 'Reviewer' },
97
+ reviewInitial: {
98
+ sourceItem: 'REVIEW-1',
99
+ asker: { kind: 'role', roleId: 'reviewer' },
100
+ },
101
+ addressFindings: {
102
+ sourceItem: 'REVIEW-2',
103
+ asker: { kind: 'role', roleId: 'coder' },
104
+ },
105
+ reviewAfterCommit: {
106
+ sourceItem: 'REVIEW-3',
107
+ asker: { kind: 'role', roleId: 'reviewer' },
108
+ },
109
+ reviewAfterRebuttal: {
110
+ sourceItem: 'REVIEW-4',
111
+ asker: { kind: 'role', roleId: 'reviewer' },
112
+ },
102
113
  };
103
114
  const playerPlaceholder = fromPromise(async () => {
104
115
  throw new Error('player actor must be provided by the runner');
@@ -264,10 +275,7 @@ export const reviewMachine = setup({
264
275
  },
265
276
  }).createMachine({
266
277
  id: 'review',
267
- context: ({ input }) => ({
268
- coderLlm: input.coderLlm,
269
- reviewerLlm: input.reviewerLlm,
270
- }),
278
+ context: {},
271
279
  initial: 'ready',
272
280
  on: {
273
281
  BOSS_INTERRUPT: {
@@ -294,14 +302,14 @@ export const reviewMachine = setup({
294
302
  reviewInitial: {
295
303
  id: 'reviewInitial',
296
304
  description: stateDescriptions.reviewInitial,
297
- meta: playbookMeta('reviewInitial', 'Reviewer'),
305
+ meta: playbookMeta('reviewInitial', 'reviewer'),
298
306
  tags: ['playbook.busy'],
299
307
  invoke: {
300
308
  src: 'player',
301
309
  input: ({ context }) => ({
302
310
  stateId: 'reviewInitial',
303
311
  sourceItem: 'REVIEW-1',
304
- player: 'Reviewer',
312
+ role: 'reviewer',
305
313
  prompt: INITIAL_REVIEW_PROMPT,
306
314
  result: REVIEW_RESULTS,
307
315
  callerInput: context.callerInput,
@@ -334,19 +342,17 @@ export const reviewMachine = setup({
334
342
  addressFindings: {
335
343
  id: 'addressFindings',
336
344
  description: stateDescriptions.addressFindings,
337
- meta: playbookMeta('addressFindings', 'Coder'),
345
+ meta: playbookMeta('addressFindings', 'coder'),
338
346
  tags: ['playbook.busy'],
339
347
  invoke: {
340
348
  src: 'player',
341
349
  input: ({ context }) => ({
342
350
  stateId: 'addressFindings',
343
351
  sourceItem: 'REVIEW-2',
344
- player: 'Coder',
352
+ role: 'coder',
345
353
  prompt: CODER_DISPOSITION_PROMPT,
346
354
  result: CODER_RESULTS,
347
355
  reviewerOutput: context.reviewerOutput,
348
- coderLlm: context.coderLlm,
349
- reviewerLlm: context.reviewerLlm,
350
356
  pendingBossQuestion: context.pendingBossQuestion,
351
357
  bossReply: context.bossReply,
352
358
  }),
@@ -376,14 +382,14 @@ export const reviewMachine = setup({
376
382
  reviewAfterCommit: {
377
383
  id: 'reviewAfterCommit',
378
384
  description: stateDescriptions.reviewAfterCommit,
379
- meta: playbookMeta('reviewAfterCommit', 'Reviewer'),
385
+ meta: playbookMeta('reviewAfterCommit', 'reviewer'),
380
386
  tags: ['playbook.busy'],
381
387
  invoke: {
382
388
  src: 'player',
383
389
  input: ({ context }) => ({
384
390
  stateId: 'reviewAfterCommit',
385
391
  sourceItem: 'REVIEW-3',
386
- player: 'Reviewer',
392
+ role: 'reviewer',
387
393
  prompt: POST_COMMIT_REVIEW_PROMPT,
388
394
  result: REVIEW_RESULTS,
389
395
  coderOutput: context.coderOutput,
@@ -416,14 +422,14 @@ export const reviewMachine = setup({
416
422
  reviewAfterRebuttal: {
417
423
  id: 'reviewAfterRebuttal',
418
424
  description: stateDescriptions.reviewAfterRebuttal,
419
- meta: playbookMeta('reviewAfterRebuttal', 'Reviewer'),
425
+ meta: playbookMeta('reviewAfterRebuttal', 'reviewer'),
420
426
  tags: ['playbook.busy'],
421
427
  invoke: {
422
428
  src: 'player',
423
429
  input: ({ context }) => ({
424
430
  stateId: 'reviewAfterRebuttal',
425
431
  sourceItem: 'REVIEW-4',
426
- player: 'Reviewer',
432
+ role: 'reviewer',
427
433
  prompt: REBUTTAL_REVIEW_PROMPT,
428
434
  result: REBUTTAL_RESULTS,
429
435
  coderOutput: context.coderOutput,
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { assign, fromPromise, setup } from 'xstate';
8
8
 
9
- type Player = 'Coder' | 'Reviewer';
9
+ type Role = 'coder' | 'reviewer';
10
10
 
11
11
  type JumpableStateId = 'reviewInitial';
12
12
 
@@ -20,7 +20,7 @@ export interface PendingBossQuestion {
20
20
  questionId: ResumableStateId;
21
21
  resumeStateId: ResumableStateId;
22
22
  sourceItem: `REVIEW-${1 | 2 | 3 | 4}`;
23
- player: Player;
23
+ asker: { readonly kind: 'role'; readonly roleId: Role };
24
24
  question: string;
25
25
  }
26
26
 
@@ -29,12 +29,9 @@ export interface ReviewOutput {
29
29
  noUnsettledFindings: true;
30
30
  }
31
31
 
32
- export interface ReviewInput {
33
- coderLlm: string;
34
- reviewerLlm: string;
35
- }
32
+ export type ReviewInput = Readonly<Record<string, never>>;
36
33
 
37
- export interface ReviewContext extends ReviewInput {
34
+ export interface ReviewContext {
38
35
  callerInput?: string;
39
36
  reviewerOutput?: string;
40
37
  coderOutput?: string;
@@ -59,14 +56,12 @@ export type ReviewEvent =
59
56
  export interface PlayerInput {
60
57
  stateId: ResumableStateId;
61
58
  sourceItem: `REVIEW-${1 | 2 | 3 | 4}`;
62
- player: Player;
59
+ role: Role;
63
60
  prompt: string;
64
61
  result: Readonly<Record<string, string>>;
65
62
  callerInput?: string;
66
63
  reviewerOutput?: string;
67
64
  coderOutput?: string;
68
- coderLlm?: string;
69
- reviewerLlm?: string;
70
65
  pendingBossQuestion?: PendingBossQuestion;
71
66
  bossReply?: string;
72
67
  }
@@ -86,6 +81,7 @@ const NEEDS_BOSS_REPLY_DESCRIPTION =
86
81
  const SHARED_REVIEW_INSTRUCTION = [
87
82
  'Understand the full picture and think systematically about the underlying design.',
88
83
  'Continue to identify issues or improvements, if any (numbered; no duplication).',
84
+ 'For any rebuttal, accept or challenge it.',
89
85
  'Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.',
90
86
  'Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.',
91
87
  'For specs, flag stale, missing, over-specified, or under-specified ones.',
@@ -100,7 +96,7 @@ const SHARED_REVIEW_INSTRUCTION = [
100
96
 
101
97
  const INITIAL_REVIEW_PROMPT = [
102
98
  'A new review begins on the latest commit.',
103
- 'Review the latest commit and resulting repository state; read the commit message first for its intent and rationale.',
99
+ 'Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.',
104
100
  '',
105
101
  '> <caller-input>',
106
102
  '',
@@ -108,7 +104,7 @@ const INITIAL_REVIEW_PROMPT = [
108
104
  ].join('\n');
109
105
 
110
106
  const POST_COMMIT_REVIEW_PROMPT = [
111
- 'Review the latest commit and resulting repository state; read the commit message first for its intent and rationale.',
107
+ 'Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.',
112
108
  '',
113
109
  '> <coder-output>',
114
110
  '',
@@ -117,8 +113,6 @@ const POST_COMMIT_REVIEW_PROMPT = [
117
113
 
118
114
  const REBUTTAL_REVIEW_PROMPT = [
119
115
  'No new commit was made because Coder rejected every finding.',
120
- 'For any rebuttal, accept or challenge it.',
121
- 'State which findings, if any, remain.',
122
116
  '',
123
117
  '> <coder-output>',
124
118
  '',
@@ -184,12 +178,12 @@ const stateDescriptions = {
184
178
 
185
179
  const playbookMeta = <StateId extends keyof typeof stateDescriptions>(
186
180
  stateId: StateId,
187
- player?: Player,
181
+ role?: Role,
188
182
  ) => ({
189
183
  playbook: {
190
184
  stateId,
191
185
  description: stateDescriptions[stateId],
192
- ...(player === undefined ? {} : { player }),
186
+ ...(role === undefined ? {} : { role }),
193
187
  },
194
188
  });
195
189
 
@@ -200,13 +194,28 @@ const isNonEmptyString = (value: unknown): value is string =>
200
194
  typeof value === 'string' && value.trim().length > 0;
201
195
 
202
196
  const stateMetadata = {
203
- reviewInitial: { sourceItem: 'REVIEW-1', player: 'Reviewer' },
204
- addressFindings: { sourceItem: 'REVIEW-2', player: 'Coder' },
205
- reviewAfterCommit: { sourceItem: 'REVIEW-3', player: 'Reviewer' },
206
- reviewAfterRebuttal: { sourceItem: 'REVIEW-4', player: 'Reviewer' },
197
+ reviewInitial: {
198
+ sourceItem: 'REVIEW-1',
199
+ asker: { kind: 'role', roleId: 'reviewer' },
200
+ },
201
+ addressFindings: {
202
+ sourceItem: 'REVIEW-2',
203
+ asker: { kind: 'role', roleId: 'coder' },
204
+ },
205
+ reviewAfterCommit: {
206
+ sourceItem: 'REVIEW-3',
207
+ asker: { kind: 'role', roleId: 'reviewer' },
208
+ },
209
+ reviewAfterRebuttal: {
210
+ sourceItem: 'REVIEW-4',
211
+ asker: { kind: 'role', roleId: 'reviewer' },
212
+ },
207
213
  } as const satisfies Record<
208
214
  ResumableStateId,
209
- { sourceItem: `REVIEW-${1 | 2 | 3 | 4}`; player: Player }
215
+ {
216
+ sourceItem: `REVIEW-${1 | 2 | 3 | 4}`;
217
+ asker: { readonly kind: 'role'; readonly roleId: Role };
218
+ }
210
219
  >;
211
220
 
212
221
  const playerPlaceholder = fromPromise<PlayerOutput, PlayerInput>(async () => {
@@ -392,10 +401,7 @@ export const reviewMachine = setup({
392
401
  },
393
402
  }).createMachine({
394
403
  id: 'review',
395
- context: ({ input }) => ({
396
- coderLlm: input.coderLlm,
397
- reviewerLlm: input.reviewerLlm,
398
- }),
404
+ context: {},
399
405
  initial: 'ready',
400
406
  on: {
401
407
  BOSS_INTERRUPT: {
@@ -422,14 +428,14 @@ export const reviewMachine = setup({
422
428
  reviewInitial: {
423
429
  id: 'reviewInitial',
424
430
  description: stateDescriptions.reviewInitial,
425
- meta: playbookMeta('reviewInitial', 'Reviewer'),
431
+ meta: playbookMeta('reviewInitial', 'reviewer'),
426
432
  tags: ['playbook.busy'],
427
433
  invoke: {
428
434
  src: 'player',
429
435
  input: ({ context }): PlayerInput => ({
430
436
  stateId: 'reviewInitial',
431
437
  sourceItem: 'REVIEW-1',
432
- player: 'Reviewer',
438
+ role: 'reviewer',
433
439
  prompt: INITIAL_REVIEW_PROMPT,
434
440
  result: REVIEW_RESULTS,
435
441
  callerInput: context.callerInput,
@@ -462,19 +468,17 @@ export const reviewMachine = setup({
462
468
  addressFindings: {
463
469
  id: 'addressFindings',
464
470
  description: stateDescriptions.addressFindings,
465
- meta: playbookMeta('addressFindings', 'Coder'),
471
+ meta: playbookMeta('addressFindings', 'coder'),
466
472
  tags: ['playbook.busy'],
467
473
  invoke: {
468
474
  src: 'player',
469
475
  input: ({ context }): PlayerInput => ({
470
476
  stateId: 'addressFindings',
471
477
  sourceItem: 'REVIEW-2',
472
- player: 'Coder',
478
+ role: 'coder',
473
479
  prompt: CODER_DISPOSITION_PROMPT,
474
480
  result: CODER_RESULTS,
475
481
  reviewerOutput: context.reviewerOutput,
476
- coderLlm: context.coderLlm,
477
- reviewerLlm: context.reviewerLlm,
478
482
  pendingBossQuestion: context.pendingBossQuestion,
479
483
  bossReply: context.bossReply,
480
484
  }),
@@ -504,14 +508,14 @@ export const reviewMachine = setup({
504
508
  reviewAfterCommit: {
505
509
  id: 'reviewAfterCommit',
506
510
  description: stateDescriptions.reviewAfterCommit,
507
- meta: playbookMeta('reviewAfterCommit', 'Reviewer'),
511
+ meta: playbookMeta('reviewAfterCommit', 'reviewer'),
508
512
  tags: ['playbook.busy'],
509
513
  invoke: {
510
514
  src: 'player',
511
515
  input: ({ context }): PlayerInput => ({
512
516
  stateId: 'reviewAfterCommit',
513
517
  sourceItem: 'REVIEW-3',
514
- player: 'Reviewer',
518
+ role: 'reviewer',
515
519
  prompt: POST_COMMIT_REVIEW_PROMPT,
516
520
  result: REVIEW_RESULTS,
517
521
  coderOutput: context.coderOutput,
@@ -544,14 +548,14 @@ export const reviewMachine = setup({
544
548
  reviewAfterRebuttal: {
545
549
  id: 'reviewAfterRebuttal',
546
550
  description: stateDescriptions.reviewAfterRebuttal,
547
- meta: playbookMeta('reviewAfterRebuttal', 'Reviewer'),
551
+ meta: playbookMeta('reviewAfterRebuttal', 'reviewer'),
548
552
  tags: ['playbook.busy'],
549
553
  invoke: {
550
554
  src: 'player',
551
555
  input: ({ context }): PlayerInput => ({
552
556
  stateId: 'reviewAfterRebuttal',
553
557
  sourceItem: 'REVIEW-4',
554
- player: 'Reviewer',
558
+ role: 'reviewer',
555
559
  prompt: REBUTTAL_REVIEW_PROMPT,
556
560
  result: REBUTTAL_RESULTS,
557
561
  coderOutput: context.coderOutput,
@@ -3,7 +3,7 @@
3
3
 
4
4
  # REVIEW: Commit Review Workflow
5
5
 
6
- Players:
6
+ Roles:
7
7
 
8
8
  - Coder
9
9
  - Reviewer
@@ -13,12 +13,13 @@ Players:
13
13
  When the caller starts a review, Captain shall relay the complete caller input to Reviewer with the first-round and shared review instructions:
14
14
 
15
15
  > A new review begins on the latest commit.
16
- > Review the latest commit and resulting repository state; read the commit message first for its intent and rationale.
16
+ > Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.
17
17
  >
18
18
  > > <caller-input>
19
19
  >
20
20
  > Understand the full picture and think systematically about the underlying design.
21
21
  > Continue to identify issues or improvements, if any (numbered; no duplication).
22
+ > For any rebuttal, accept or challenge it.
22
23
  > Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.
23
24
  > Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.
24
25
  > For specs, flag stale, missing, over-specified, or under-specified ones.
@@ -63,12 +64,13 @@ Results:
63
64
 
64
65
  When Coder makes a review-fix commit, Captain shall relay Coder's output to Reviewer with the next-round and shared review instructions:
65
66
 
66
- > Review the latest commit and resulting repository state; read the commit message first for its intent and rationale.
67
+ > Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.
67
68
  >
68
69
  > > <coder-output>
69
70
  >
70
71
  > Understand the full picture and think systematically about the underlying design.
71
72
  > Continue to identify issues or improvements, if any (numbered; no duplication).
73
+ > For any rebuttal, accept or challenge it.
72
74
  > Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.
73
75
  > Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.
74
76
  > For specs, flag stale, missing, over-specified, or under-specified ones.
@@ -89,13 +91,12 @@ Results:
89
91
  When Coder rejects every finding and makes no commit, Captain shall relay Coder's output to Reviewer with the rebuttal and shared review instructions:
90
92
 
91
93
  > No new commit was made because Coder rejected every finding.
92
- > For any rebuttal, accept or challenge it.
93
- > State which findings, if any, remain.
94
94
  >
95
95
  > > <coder-output>
96
96
  >
97
97
  > Understand the full picture and think systematically about the underlying design.
98
98
  > Continue to identify issues or improvements, if any (numbered; no duplication).
99
+ > For any rebuttal, accept or challenge it.
99
100
  > Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.
100
101
  > Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.
101
102
  > For specs, flag stale, missing, over-specified, or under-specified ones.