@sublang/playbook 10.0.0 → 12.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 (59) hide show
  1. package/README.md +1 -1
  2. package/docs/cli.md +67 -19
  3. package/docs/configuration.md +103 -40
  4. package/docs/embedding.md +88 -0
  5. package/package.json +30 -4
  6. package/reference/sdlc/code.md +35 -15
  7. package/reference/sdlc/code.playbook/bin/interactive-session.js +58 -6
  8. package/reference/sdlc/code.playbook/bin/launch-config.js +499 -241
  9. package/reference/sdlc/code.playbook/bin/playbook.js +236 -187
  10. package/reference/sdlc/code.playbook/bin/replay-observer.js +221 -0
  11. package/reference/sdlc/code.playbook/bin/run.js +355 -203
  12. package/reference/sdlc/code.playbook/bin/session-store.js +1512 -136
  13. package/reference/sdlc/code.playbook/code.fsm.d.ts +22 -19
  14. package/reference/sdlc/code.playbook/code.fsm.js +116 -52
  15. package/reference/sdlc/code.playbook/code.fsm.ts +149 -64
  16. package/reference/sdlc/code.playbook/code.gears.md +40 -20
  17. package/reference/sdlc/code.playbook/code.playbook.js +23 -2
  18. package/reference/sdlc/code.playbook/code.playbook.ts +23 -2
  19. package/reference/sdlc/code.playbook/playbook-captain.d.ts +4 -1
  20. package/reference/sdlc/code.playbook/playbook-captain.js +21 -3
  21. package/reference/sdlc/code.playbook/playbook-captain.ts +42 -6
  22. package/reference/sdlc/code.playbook/playbook.config.template.yaml +31 -11
  23. package/reference/sdlc/code.playbook/session-store.d.ts +82 -0
  24. package/reference/sdlc/code.playbook/session-store.js +113 -0
  25. package/reference/sdlc/decide.md +24 -15
  26. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +13 -6
  27. package/reference/sdlc/decide.playbook/decide.fsm.js +54 -27
  28. package/reference/sdlc/decide.playbook/decide.fsm.ts +68 -29
  29. package/reference/sdlc/decide.playbook/decide.gears.md +25 -19
  30. package/reference/sdlc/decide.playbook/decide.playbook.js +11 -3
  31. package/reference/sdlc/decide.playbook/decide.playbook.ts +11 -3
  32. package/reference/sdlc/decide.playbook/decide.registry.js +1 -1
  33. package/reference/sdlc/decide.playbook/decide.registry.ts +1 -1
  34. package/reference/sdlc/dev.md +52 -0
  35. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +261 -0
  36. package/reference/sdlc/dev.playbook/dev.fsm.js +723 -0
  37. package/reference/sdlc/dev.playbook/dev.fsm.ts +988 -0
  38. package/reference/sdlc/dev.playbook/dev.gears.md +91 -0
  39. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +21 -0
  40. package/reference/sdlc/dev.playbook/dev.playbook.js +143 -0
  41. package/reference/sdlc/dev.playbook/dev.playbook.ts +246 -0
  42. package/reference/sdlc/dev.playbook/dev.registry.d.ts +40 -0
  43. package/reference/sdlc/dev.playbook/dev.registry.js +64 -0
  44. package/reference/sdlc/dev.playbook/dev.registry.ts +120 -0
  45. package/reference/sdlc/review.md +36 -18
  46. package/reference/sdlc/review.playbook/review.fsm.d.ts +15 -2
  47. package/reference/sdlc/review.playbook/review.fsm.js +77 -27
  48. package/reference/sdlc/review.playbook/review.fsm.ts +96 -30
  49. package/reference/sdlc/review.playbook/review.gears.md +52 -26
  50. package/reference/sdlc/review.playbook/review.playbook.js +17 -7
  51. package/reference/sdlc/review.playbook/review.playbook.ts +17 -7
  52. package/reference/sdlc/review.playbook/review.registry.js +1 -1
  53. package/reference/sdlc/review.playbook/review.registry.ts +1 -1
  54. package/slc/link.md +12 -5
  55. package/slc/text2gears.md +3 -0
  56. package/src/xstate-playbook-runtime.js +5 -2
  57. package/src/xstate-playbook-runtime.ts +5 -2
  58. package/src/xstate-runtime.js +13 -1
  59. package/src/xstate-runtime.ts +13 -1
@@ -0,0 +1,120 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ import createPlaybookRuntime, {
5
+ type DevPlaybookHostCapabilities,
6
+ type PlaybookRuntime,
7
+ } from './dev.playbook.js';
8
+
9
+ export interface PlaybookSummaryPolicy {
10
+ stateCountLabels: Readonly<Record<string, string>>;
11
+ copyPasteGuardNames: readonly string[];
12
+ savedCountsLine(
13
+ counts: { interruptions: number; copyPastes: number },
14
+ rounds: number,
15
+ ): string;
16
+ }
17
+
18
+ export type DevOptions = Readonly<Record<string, never>>;
19
+
20
+ export interface DevPlaybookRegistryEntry {
21
+ id: 'dev';
22
+ command: 'dev';
23
+ intent: string;
24
+ artifactSchema: 3;
25
+ runtimeProfile: {
26
+ readonly kind: 'shared-factory';
27
+ readonly compat: {
28
+ readonly artifactSchema: 3;
29
+ readonly runtimeAbi: number;
30
+ };
31
+ };
32
+ requiredRoleIds: readonly ['analyst'];
33
+ concurrentRoleSets: readonly [];
34
+ summaryPolicy: PlaybookSummaryPolicy;
35
+ validateOptions(optionSlice: unknown): DevOptions;
36
+ createRuntime(
37
+ options: DevOptions,
38
+ hostCapabilities: DevPlaybookHostCapabilities,
39
+ ): PlaybookRuntime;
40
+ }
41
+
42
+ // CODE and DECIDE own and label the rounds of the paths DEV starts. DEV's
43
+ // suspended call states only delegate to those children and must not
44
+ // double-count their rounds; only the Analyst planning rounds are DEV's own.
45
+ export const devStateCountLabels = {
46
+ planAnalysis: 'planning round',
47
+ } as const;
48
+
49
+ export const devCopyPasteGuardNames = ['code', 'decideThenCode'] as const;
50
+
51
+ function countNoun(
52
+ count: number,
53
+ singular: string,
54
+ plural = `${singular}s`,
55
+ ): string {
56
+ return `${count} ${count === 1 ? singular : plural}`;
57
+ }
58
+
59
+ export function devSavedCountsLine(
60
+ counts: { interruptions: number; copyPastes: number },
61
+ rounds: number,
62
+ ): string {
63
+ return [
64
+ 'Saved you',
65
+ countNoun(counts.interruptions, 'interruption'),
66
+ 'and',
67
+ countNoun(counts.copyPastes, 'copy-paste'),
68
+ 'across',
69
+ countNoun(rounds, 'round'),
70
+ 'of planning.',
71
+ ].join(' ');
72
+ }
73
+
74
+ export const devSummaryPolicy: PlaybookSummaryPolicy = {
75
+ stateCountLabels: devStateCountLabels,
76
+ copyPasteGuardNames: devCopyPasteGuardNames,
77
+ savedCountsLine: devSavedCountsLine,
78
+ };
79
+
80
+ export function validateDevOptions(optionSlice: unknown): DevOptions {
81
+ if (optionSlice === undefined) return Object.freeze({});
82
+ if (
83
+ optionSlice === null ||
84
+ typeof optionSlice !== 'object' ||
85
+ Array.isArray(optionSlice)
86
+ ) {
87
+ throw new Error('captain.options.playbooks.dev.options must be an object');
88
+ }
89
+ const keys = Object.keys(optionSlice);
90
+ if (keys.length > 0) {
91
+ throw new Error(
92
+ `Unknown config field captain.options.playbooks.dev.options.${keys[0]}`,
93
+ );
94
+ }
95
+ return Object.freeze({});
96
+ }
97
+
98
+ export const devPlaybookRegistryEntry: DevPlaybookRegistryEntry = {
99
+ id: 'dev',
100
+ command: 'dev',
101
+ intent:
102
+ 'analyze a development request that needs planning before choosing direct implementation or a durable decision first',
103
+ artifactSchema: 3,
104
+ runtimeProfile: Object.freeze({
105
+ kind: 'shared-factory',
106
+ compat: createPlaybookRuntime.compat,
107
+ }),
108
+ requiredRoleIds: ['analyst'],
109
+ concurrentRoleSets: [],
110
+ summaryPolicy: devSummaryPolicy,
111
+ validateOptions: validateDevOptions,
112
+ createRuntime(options, hostCapabilities) {
113
+ return createPlaybookRuntime({
114
+ configuredOptions: options,
115
+ hostCapabilities,
116
+ });
117
+ },
118
+ };
119
+
120
+ export default devPlaybookRegistryEntry;
@@ -9,45 +9,55 @@ Roles:
9
9
  - Coder
10
10
  - Reviewer
11
11
 
12
- The caller supplies the initial intent or any specific context and relevant run results.
12
+ The caller supplies:
13
13
 
14
- `review` examines committed work only.
15
- Every review round begins with a new or existing commit.
14
+ - the original intent;
15
+ - the review scope in the caller's own words;
16
+ - optional relevant context and run results.
16
17
 
17
- At the first review round, Captain shall relay to Reviewer the complete initial review request in quotes (`>`).
18
+ The caller's review scope is the baseline for every round, and each review-fix commit joins that scope as it lands, so every later round reviews the cumulative committed state.
19
+ `review` examines committed work only.
20
+ Captain shall take the evaluated repository revision from repository authority, not from either player's prose.
18
21
 
19
22
  At the first review round, Captain shall give Reviewer the following instruction:
20
23
 
21
24
  ```markdown
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, scope, and rationale.
25
+ A new review begins for the review scope.
26
+ Keep to the original intent and follow what it asks.
27
+ When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.
24
28
  ```
25
29
 
26
30
  After every review-fix commit, Captain shall give Reviewer the following instruction:
27
31
 
28
32
  ```markdown
29
- Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.
33
+ A new review round begins for the review scope in the cumulative committed state, with particular attention to the latest review-fix commit.
34
+ Keep to the original intent and follow what it asks.
35
+ Read the latest review-fix commit's message and see Coder's feedback below.
30
36
  ```
31
37
 
32
38
  When Coder rejects every finding and makes no commit, Captain shall give Reviewer the following instruction:
33
39
 
34
40
  ```markdown
35
41
  No new commit was made because Coder rejected every finding.
42
+ See Coder's feedback below.
36
43
  ```
37
44
 
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.
45
+ At the start of *every* review round, Captain shall relay to Reviewer the original intent, the review scope and context, the exact repository revision being evaluated, any Coder feedback from the preceding round, and any relevant run results, in quotes (`>`) after the instruction.
39
46
 
40
47
  At the start of *every* review round, Captain shall append the following instruction to the end of the prompt:
41
48
 
42
49
  ```markdown
43
50
  Understand the full picture and think systematically about the underlying design.
44
- Continue to identify issues or improvements, if any (numbered; no duplication).
45
- For any rebuttal, accept or challenge it.
46
- Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.
51
+ Continue to identify issues or improvements, if any, without duplication.
52
+ Number the findings consistently across rounds.
47
53
  Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.
48
- For specs, flag stale, missing, over-specified, or under-specified ones.
54
+ For specs, flag stale, missing, over-specified, or under-specified ones, if any.
49
55
  Avoid unnecessary complexity in code or tests, but flag any fundamental design flaw when leaving it would cost more in later patches than fixing it now.
50
56
 
57
+ If an issue represents a class of defect, find every instance within the review scope worth fixing rather than surfacing one or two per round, which drags out the review.
58
+ For any rebuttal, accept or challenge it.
59
+ Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.
60
+
51
61
  Do not re-run tests or builds whose inputs have not changed since any previous reported run.
52
62
  Do not edit files or commit; report findings only.
53
63
 
@@ -55,26 +65,34 @@ Consult @specs/map.md for context if needed; verify it remains accurate.
55
65
  Consult @specs/meta.md for spec requirements if needed; verify affected specs follow it.
56
66
  ```
57
67
 
58
- When Reviewer raises or keeps any finding, Captain shall relay the Reviewer's findings and any relevant run results to Coder, in quotes (`>`), along with the following prompt:
68
+ Finding numbers are references within this review only.
69
+ No review transition shall depend on numbering or any fixed presentation format of either player's reply.
70
+
71
+ When Reviewer raises or keeps any finding, Captain shall relay the original intent, the review scope and context, the exact repository revision being evaluated, the Reviewer's findings, and any relevant run results to Coder, in quotes (`>`), along with the following prompt:
59
72
 
60
73
  ```markdown
61
74
  For each review item, accept or reject it.
62
75
  Before deciding, understand the full picture and think systematically about the underlying design.
76
+ Keep to the original intent and follow what it asks.
63
77
  Reject anything that is not essential or is not worth fixing now.
64
- If you accept an item, fix its root cause, including any fundamental design flaw - do not patch around it.
78
+ If you accept an item, fix its root cause, including any fundamental design flaw do not patch around it; if it represents a class of defect, find every instance within the review scope worth fixing rather than addressing one or two per round, which drags out the review.
65
79
  If you reject an item, give the reasoning and cite code or test output that supports it.
66
80
  Do not re-run tests or builds whose inputs have not changed since any previous reported run.
67
81
 
68
- If you accept any item, make minimal changes and commit them as one new commit; never amend the reviewed commit.
82
+ If you accept any item, make minimal changes and add one new review-fix commit; never rewrite any existing commit.
69
83
  Follow @specs/packages/git.md.
70
84
  Make the commit message explain concisely what changed and why, including relevant verification.
71
- Coder is <coder-llm>; Reviewer is <reviewer-llm>; format model tokens in conventional human form.
85
+ Identify every new commit you make.
86
+ Coder is <coder-llm>; Reviewer is <reviewer-llm>.
72
87
 
73
88
  If you reject every item, change nothing and make no commit.
74
89
  Report every disposition, all relevant run results, and every rebuttal.
75
90
  ```
76
91
 
92
+ Captain shall use the repository-effect receipt as the authoritative identity of any review-fix commit.
93
+
77
94
  When Coder makes a new commit or decides to reject all findings, Captain shall relay the above required context to Reviewer and begin the next review round.
78
95
  Reviewer shall read the context information and follow the corresponding instructions.
79
- Rounds continue until Reviewer raises no unsettled findings.
80
- `review` then returns the approved latest commit and the fact that no findings remain to its caller.
96
+ Rounds continue until Reviewer affirmatively reports that the requested review is complete and no unsettled findings remain.
97
+ A progress report, status update, or promise of a later result supports no review outcome.
98
+ `review` then returns the exact repository revision at which the review scope was evaluated and the fact that no unsettled findings remain within that scope.
@@ -12,14 +12,24 @@ export interface PendingBossQuestion {
12
12
  question: string;
13
13
  }
14
14
  export interface ReviewOutput {
15
- approvedCommit: 'latest';
16
15
  noUnsettledFindings: true;
16
+ /**
17
+ * Exact receipt-derived repository revision at which the review scope was
18
+ * evaluated: the closing clean round's `unchanged` receipt observes it as
19
+ * HEAD — the last review-fix commit when one landed, or the caller-supplied
20
+ * scope revision when none did (DR-045).
21
+ */
22
+ evaluatedRevision: string;
17
23
  }
18
24
  export type ReviewInput = Readonly<Record<string, never>>;
19
25
  export interface ReviewContext {
20
26
  callerInput?: string;
21
27
  reviewerOutput?: string;
22
28
  coderOutput?: string;
29
+ /** Receipt-derived OID of the latest review-fix commit (never player prose). */
30
+ latestCommit?: string;
31
+ /** Observed HEAD of the closing clean round's unchanged receipt (DR-045). */
32
+ evaluatedRevision?: string;
23
33
  lastError?: unknown;
24
34
  pendingBossQuestion?: PendingBossQuestion;
25
35
  bossReply?: string;
@@ -45,6 +55,7 @@ export interface PlayerInput {
45
55
  callerInput?: string;
46
56
  reviewerOutput?: string;
47
57
  coderOutput?: string;
58
+ latestCommit?: string;
48
59
  pendingBossQuestion?: PendingBossQuestion;
49
60
  bossReply?: string;
50
61
  }
@@ -53,9 +64,11 @@ export type PlayerOutput = {
53
64
  reviewerOutput: string;
54
65
  } | {
55
66
  guard: 'noFindings';
67
+ evaluatedRevision: string;
56
68
  } | {
57
69
  guard: 'committed';
58
70
  coderOutput: string;
71
+ latestCommit: string;
59
72
  } | {
60
73
  guard: 'rejectedAll';
61
74
  coderOutput: string;
@@ -109,7 +122,7 @@ export declare const reviewMachine: import("xstate").StateMachine<ReviewContext,
109
122
  type: "rememberCoderOutput";
110
123
  params: import("xstate").NonReducibleUnknown;
111
124
  } | {
112
- type: "clearBossReplyContext";
125
+ type: "rememberEvaluatedRevision";
113
126
  params: import("xstate").NonReducibleUnknown;
114
127
  } | {
115
128
  type: "setPendingReviewInitial";
@@ -6,13 +6,16 @@ import { assign, fromPromise, setup } from 'xstate';
6
6
  const NEEDS_BOSS_REPLY_DESCRIPTION = "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>`.";
7
7
  const SHARED_REVIEW_INSTRUCTION = [
8
8
  'Understand the full picture and think systematically about the underlying design.',
9
- 'Continue to identify issues or improvements, if any (numbered; no duplication).',
10
- 'For any rebuttal, accept or challenge it.',
11
- 'Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.',
9
+ 'Continue to identify issues or improvements, if any, without duplication.',
10
+ 'Number the findings consistently across rounds.',
12
11
  'Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.',
13
- 'For specs, flag stale, missing, over-specified, or under-specified ones.',
12
+ 'For specs, flag stale, missing, over-specified, or under-specified ones, if any.',
14
13
  'Avoid unnecessary complexity in code or tests, but flag any fundamental design flaw when leaving it would cost more in later patches than fixing it now.',
15
14
  '',
15
+ 'If an issue represents a class of defect, find every instance within the review scope worth fixing rather than surfacing one or two per round, which drags out the review.',
16
+ 'For any rebuttal, accept or challenge it.',
17
+ 'Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.',
18
+ '',
16
19
  'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
17
20
  'Do not edit files or commit; report findings only.',
18
21
  '',
@@ -20,69 +23,79 @@ const SHARED_REVIEW_INSTRUCTION = [
20
23
  'Consult @specs/meta.md for spec requirements if needed; verify affected specs follow it.',
21
24
  ].join('\n');
22
25
  const INITIAL_REVIEW_PROMPT = [
23
- 'A new review begins on the latest commit.',
24
- 'Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.',
26
+ 'A new review begins for the review scope.',
27
+ 'Keep to the original intent and follow what it asks.',
28
+ 'When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.',
25
29
  '',
26
30
  '> <caller-input>',
27
31
  '',
28
32
  SHARED_REVIEW_INSTRUCTION,
29
33
  ].join('\n');
30
34
  const POST_COMMIT_REVIEW_PROMPT = [
31
- 'Review the latest commit and resulting repository state; read the commit message first for its intent, scope, and rationale.',
35
+ 'A new review round begins for the review scope in the cumulative committed state, with particular attention to the latest review-fix commit.',
36
+ 'Keep to the original intent and follow what it asks.',
37
+ "Read the latest review-fix commit's message and see Coder's feedback below.",
32
38
  '',
39
+ '> <caller-input>',
40
+ '> <latest-commit>',
33
41
  '> <coder-output>',
34
42
  '',
35
43
  SHARED_REVIEW_INSTRUCTION,
36
44
  ].join('\n');
37
45
  const REBUTTAL_REVIEW_PROMPT = [
38
46
  'No new commit was made because Coder rejected every finding.',
47
+ "See Coder's feedback below.",
39
48
  '',
49
+ '> <caller-input>',
40
50
  '> <coder-output>',
41
51
  '',
42
52
  SHARED_REVIEW_INSTRUCTION,
43
53
  ].join('\n');
44
54
  const CODER_DISPOSITION_PROMPT = [
55
+ '> <caller-input>',
45
56
  '> <reviewer-output>',
46
57
  '',
47
58
  'For each review item, accept or reject it.',
48
59
  'Before deciding, understand the full picture and think systematically about the underlying design.',
60
+ 'Keep to the original intent and follow what it asks.',
49
61
  'Reject anything that is not essential or is not worth fixing now.',
50
- 'If you accept an item, fix its root cause, including any fundamental design flaw - do not patch around it.',
62
+ 'If you accept an item, fix its root cause, including any fundamental design flaw do not patch around it; if it represents a class of defect, find every instance within the review scope worth fixing rather than addressing one or two per round, which drags out the review.',
51
63
  'If you reject an item, give the reasoning and cite code or test output that supports it.',
52
64
  'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
53
65
  '',
54
- 'If you accept any item, make minimal changes and commit them as one new commit; never amend the reviewed commit.',
66
+ 'If you accept any item, make minimal changes and add one new review-fix commit; never rewrite any existing commit.',
55
67
  'Follow @specs/packages/git.md.',
56
68
  'Make the commit message explain concisely what changed and why, including relevant verification.',
57
- 'Coder is <coder-llm>; Reviewer is <reviewer-llm>; format model tokens in conventional human form.',
69
+ 'Identify every new commit you make.',
70
+ 'Coder is <coder-llm>; Reviewer is <reviewer-llm>.',
58
71
  '',
59
72
  'If you reject every item, change nothing and make no commit.',
60
73
  'Report every disposition, all relevant run results, and every rebuttal.',
61
74
  ].join('\n');
62
75
  const REVIEW_RESULTS = {
63
76
  hasFindings: 'Reviewer raised one or more unsettled findings. Output shall include `reviewerOutput: <verbatim final text>`.',
64
- noFindings: 'Reviewer raised no unsettled findings.',
77
+ noFindings: 'Reviewer affirmatively reported the requested review complete with no unsettled findings remaining; a progress report, status update, or promise of a later result supports no review outcome. Output shall include `evaluatedRevision: <repository revision>`.',
65
78
  needsBossReply: NEEDS_BOSS_REPLY_DESCRIPTION,
66
79
  };
67
80
  const REBUTTAL_RESULTS = {
68
81
  hasFindings: 'Reviewer kept one or more unsettled findings. Output shall include `reviewerOutput: <verbatim final text>`.',
69
- noFindings: 'Reviewer accepted the rebuttals and no unsettled findings remain.',
82
+ noFindings: 'Reviewer accepted the rebuttals and affirmatively reported the requested review complete with no unsettled findings remaining; a progress report, status update, or promise of a later result supports no review outcome. Output shall include `evaluatedRevision: <repository revision>`.',
70
83
  needsBossReply: NEEDS_BOSS_REPLY_DESCRIPTION,
71
84
  };
72
85
  const CODER_RESULTS = {
73
- committed: 'Coder accepted at least one item and made one new commit. Output shall include `coderOutput: <verbatim final text>`.',
86
+ committed: 'Coder accepted at least one item and added one new review-fix commit. Output shall include `coderOutput: <verbatim final text>` and `latestCommit: <commit identity>`.',
74
87
  rejectedAll: 'Coder rejected every item and made no commit. Output shall include `coderOutput: <verbatim final text>`.',
75
88
  needsBossReply: NEEDS_BOSS_REPLY_DESCRIPTION,
76
89
  };
77
90
  const stateDescriptions = {
78
- ready: 'Waits for a caller to start a committed-work review.',
79
- reviewInitial: 'REVIEW-1: Reviewer examines the latest commit against the caller input.',
91
+ ready: 'Waits for a caller to start a scoped committed-work review.',
92
+ reviewInitial: "REVIEW-1: Reviewer reviews the caller's review scope against the original intent.",
80
93
  addressFindings: 'REVIEW-2: Coder accepts or rejects every current review finding.',
81
- reviewAfterCommit: 'REVIEW-3: Reviewer examines the new review-fix commit and repository state.',
94
+ reviewAfterCommit: 'REVIEW-3: Reviewer reviews the cumulative committed state after a review-fix commit.',
82
95
  reviewAfterRebuttal: 'REVIEW-4: Reviewer adjudicates an all-rejected Coder disposition.',
83
96
  awaitBossReply: 'Waits for Boss to answer the active player question.',
84
97
  failed: 'Retains a recoverable REVIEW failure for the caller.',
85
- done: 'The latest commit is approved with no unsettled findings.',
98
+ done: 'The requested review is complete: no unsettled findings remain within the review scope.',
86
99
  };
87
100
  const playbookMeta = (stateId, role) => ({
88
101
  playbook: {
@@ -130,10 +143,16 @@ export const reviewMachine = setup({
130
143
  return (output.guard === 'hasFindings' &&
131
144
  isNonEmptyString(output.reviewerOutput));
132
145
  },
133
- noFindings: ({ event }) => outputOf(event).guard === 'noFindings',
146
+ noFindings: ({ event }) => {
147
+ const output = outputOf(event);
148
+ return (output.guard === 'noFindings' &&
149
+ isNonEmptyString(output.evaluatedRevision));
150
+ },
134
151
  committed: ({ event }) => {
135
152
  const output = outputOf(event);
136
- return (output.guard === 'committed' && isNonEmptyString(output.coderOutput));
153
+ return (output.guard === 'committed' &&
154
+ isNonEmptyString(output.coderOutput) &&
155
+ isNonEmptyString(output.latestCommit));
137
156
  },
138
157
  rejectedAll: ({ event }) => {
139
158
  const output = outputOf(event);
@@ -175,6 +194,8 @@ export const reviewMachine = setup({
175
194
  callerInput: ({ event }) => event.type === 'START_REVIEW' ? event.callerInput : undefined,
176
195
  reviewerOutput: undefined,
177
196
  coderOutput: undefined,
197
+ latestCommit: undefined,
198
+ evaluatedRevision: undefined,
178
199
  lastError: undefined,
179
200
  pendingBossQuestion: undefined,
180
201
  bossReply: undefined,
@@ -183,6 +204,8 @@ export const reviewMachine = setup({
183
204
  callerInput: ({ event }) => event.type === 'BOSS_INTERRUPT' ? event.bossIntent : undefined,
184
205
  reviewerOutput: undefined,
185
206
  coderOutput: undefined,
207
+ latestCommit: undefined,
208
+ evaluatedRevision: undefined,
186
209
  lastError: undefined,
187
210
  pendingBossQuestion: undefined,
188
211
  bossReply: undefined,
@@ -205,11 +228,29 @@ export const reviewMachine = setup({
205
228
  ? output.coderOutput
206
229
  : undefined;
207
230
  },
231
+ // The receipt-derived review-fix commit identity replaces the prior one
232
+ // only when Coder committed; an all-rejected round leaves the evaluated
233
+ // revision unchanged.
234
+ latestCommit: ({ context, event }) => {
235
+ const output = outputOf(event);
236
+ return output.guard === 'committed'
237
+ ? output.latestCommit
238
+ : context.latestCommit;
239
+ },
208
240
  lastError: undefined,
209
241
  pendingBossQuestion: undefined,
210
242
  bossReply: undefined,
211
243
  }),
212
- clearBossReplyContext: assign({
244
+ rememberEvaluatedRevision: assign({
245
+ // DR-045: the closing clean round's unchanged receipt observes the
246
+ // exact revision the review scope was evaluated at.
247
+ evaluatedRevision: ({ context, event }) => {
248
+ const output = outputOf(event);
249
+ return output.guard === 'noFindings'
250
+ ? output.evaluatedRevision
251
+ : context.evaluatedRevision;
252
+ },
253
+ lastError: undefined,
213
254
  pendingBossQuestion: undefined,
214
255
  bossReply: undefined,
215
256
  }),
@@ -345,7 +386,7 @@ export const reviewMachine = setup({
345
386
  acceptedOutcome: 'noFindings',
346
387
  },
347
388
  },
348
- 'clearBossReplyContext',
389
+ 'rememberEvaluatedRevision',
349
390
  ],
350
391
  },
351
392
  {
@@ -383,6 +424,7 @@ export const reviewMachine = setup({
383
424
  role: 'coder',
384
425
  prompt: CODER_DISPOSITION_PROMPT,
385
426
  result: CODER_RESULTS,
427
+ callerInput: context.callerInput,
386
428
  reviewerOutput: context.reviewerOutput,
387
429
  pendingBossQuestion: context.pendingBossQuestion,
388
430
  bossReply: context.bossReply,
@@ -453,6 +495,8 @@ export const reviewMachine = setup({
453
495
  role: 'reviewer',
454
496
  prompt: POST_COMMIT_REVIEW_PROMPT,
455
497
  result: REVIEW_RESULTS,
498
+ callerInput: context.callerInput,
499
+ latestCommit: context.latestCommit,
456
500
  coderOutput: context.coderOutput,
457
501
  pendingBossQuestion: context.pendingBossQuestion,
458
502
  bossReply: context.bossReply,
@@ -485,7 +529,7 @@ export const reviewMachine = setup({
485
529
  acceptedOutcome: 'noFindings',
486
530
  },
487
531
  },
488
- 'clearBossReplyContext',
532
+ 'rememberEvaluatedRevision',
489
533
  ],
490
534
  },
491
535
  {
@@ -523,6 +567,7 @@ export const reviewMachine = setup({
523
567
  role: 'reviewer',
524
568
  prompt: REBUTTAL_REVIEW_PROMPT,
525
569
  result: REBUTTAL_RESULTS,
570
+ callerInput: context.callerInput,
526
571
  coderOutput: context.coderOutput,
527
572
  pendingBossQuestion: context.pendingBossQuestion,
528
573
  bossReply: context.bossReply,
@@ -555,7 +600,7 @@ export const reviewMachine = setup({
555
600
  acceptedOutcome: 'noFindings',
556
601
  },
557
602
  },
558
- 'clearBossReplyContext',
603
+ 'rememberEvaluatedRevision',
559
604
  ],
560
605
  },
561
606
  {
@@ -644,8 +689,13 @@ export const reviewMachine = setup({
644
689
  type: 'final',
645
690
  },
646
691
  },
647
- output: () => ({
648
- approvedCommit: 'latest',
649
- noUnsettledFindings: true,
650
- }),
692
+ output: ({ context }) => {
693
+ if (!isNonEmptyString(context.evaluatedRevision)) {
694
+ throw new Error('REVIEW reached done without a receipt-observed evaluated revision');
695
+ }
696
+ return {
697
+ noUnsettledFindings: true,
698
+ evaluatedRevision: context.evaluatedRevision,
699
+ };
700
+ },
651
701
  });