@sublang/playbook 5.0.0 → 7.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 (72) hide show
  1. package/README.md +22 -14
  2. package/docs/cli.md +138 -90
  3. package/docs/configuration.md +113 -29
  4. package/docs/embedding.md +24 -16
  5. package/package.json +42 -21
  6. package/reference/sdlc/captain.playbook/captain.playbook.js +2 -0
  7. package/reference/sdlc/captain.playbook/captain.playbook.ts +2 -0
  8. package/reference/sdlc/code.md +55 -97
  9. package/reference/sdlc/code.playbook/bin/launch-config.js +938 -0
  10. package/reference/sdlc/code.playbook/bin/playbook.js +145 -562
  11. package/reference/sdlc/code.playbook/bin/provision.js +84 -38
  12. package/reference/sdlc/code.playbook/bin/run.js +1171 -983
  13. package/reference/sdlc/code.playbook/bin/session-store.js +1169 -0
  14. package/reference/sdlc/code.playbook/code.fsm.d.ts +229 -94
  15. package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +26 -44
  16. package/reference/sdlc/code.playbook/code.fsm.introspect.js +61 -66
  17. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +100 -149
  18. package/reference/sdlc/code.playbook/code.fsm.js +587 -1347
  19. package/reference/sdlc/code.playbook/code.fsm.ts +809 -1650
  20. package/reference/sdlc/code.playbook/code.gears.md +51 -263
  21. package/reference/sdlc/code.playbook/code.playbook.d.ts +8 -47
  22. package/reference/sdlc/code.playbook/code.playbook.js +69 -656
  23. package/reference/sdlc/code.playbook/code.playbook.ts +90 -867
  24. package/reference/sdlc/code.playbook/code.registry.d.ts +9 -25
  25. package/reference/sdlc/code.playbook/code.registry.js +20 -78
  26. package/reference/sdlc/code.playbook/code.registry.ts +58 -122
  27. package/reference/sdlc/code.playbook/playbook-captain.d.ts +70 -3
  28. package/reference/sdlc/code.playbook/playbook-captain.js +954 -80
  29. package/reference/sdlc/code.playbook/playbook-captain.ts +1408 -80
  30. package/reference/sdlc/code.playbook/playbook.config.template.yaml +37 -36
  31. package/reference/sdlc/decide.md +54 -0
  32. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +261 -0
  33. package/reference/sdlc/decide.playbook/decide.fsm.js +894 -0
  34. package/reference/sdlc/decide.playbook/decide.fsm.ts +1152 -0
  35. package/reference/sdlc/decide.playbook/decide.gears.md +88 -0
  36. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +67 -0
  37. package/reference/sdlc/{discuss.playbook/discuss.playbook.js → decide.playbook/decide.playbook.js} +545 -372
  38. package/reference/sdlc/{discuss.playbook/discuss.playbook.ts → decide.playbook/decide.playbook.ts} +665 -454
  39. package/reference/sdlc/decide.playbook/decide.registry.d.ts +41 -0
  40. package/reference/sdlc/decide.playbook/decide.registry.js +60 -0
  41. package/reference/sdlc/decide.playbook/decide.registry.ts +125 -0
  42. package/reference/sdlc/review.md +81 -0
  43. package/reference/sdlc/review.playbook/review.fsm.d.ts +183 -0
  44. package/reference/sdlc/review.playbook/review.fsm.js +524 -0
  45. package/reference/sdlc/review.playbook/review.fsm.ts +652 -0
  46. package/reference/sdlc/review.playbook/review.gears.md +112 -0
  47. package/reference/sdlc/review.playbook/review.playbook.d.ts +12 -0
  48. package/reference/sdlc/review.playbook/review.playbook.js +112 -0
  49. package/reference/sdlc/review.playbook/review.playbook.ts +201 -0
  50. package/reference/sdlc/review.playbook/review.registry.d.ts +43 -0
  51. package/reference/sdlc/review.playbook/review.registry.js +73 -0
  52. package/reference/sdlc/review.playbook/review.registry.ts +138 -0
  53. package/slc/gears2fsm.md +13 -4
  54. package/slc/link.md +83 -14
  55. package/slc/text2gears.md +22 -2
  56. package/src/runtime.d.ts +21 -2
  57. package/src/runtime.ts +38 -6
  58. package/src/xstate-playbook-runtime.d.ts +9 -2
  59. package/src/xstate-playbook-runtime.js +319 -35
  60. package/src/xstate-playbook-runtime.ts +412 -41
  61. package/src/xstate-runtime.d.ts +19 -2
  62. package/src/xstate-runtime.js +384 -57
  63. package/src/xstate-runtime.ts +542 -71
  64. package/reference/sdlc/discuss.md +0 -93
  65. package/reference/sdlc/discuss.playbook/discuss.fsm.d.ts +0 -396
  66. package/reference/sdlc/discuss.playbook/discuss.fsm.js +0 -2067
  67. package/reference/sdlc/discuss.playbook/discuss.fsm.ts +0 -2465
  68. package/reference/sdlc/discuss.playbook/discuss.gears.md +0 -258
  69. package/reference/sdlc/discuss.playbook/discuss.playbook.d.ts +0 -113
  70. package/reference/sdlc/discuss.playbook/discuss.registry.d.ts +0 -58
  71. package/reference/sdlc/discuss.playbook/discuss.registry.js +0 -97
  72. package/reference/sdlc/discuss.playbook/discuss.registry.ts +0 -153
@@ -1,1445 +1,685 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
- // XState v5's generics do not flow through helpers declared outside the
4
- // machine (e.g., shared guard/action factories, the `readyEvents` block),
5
- // so strict checking emits structural-mismatch noise without surfacing real
6
- // bugs. The runner overrides `player` via `.provide({ actors: { ... } })`
7
- // and re-asserts the public surface (`CodingContext`, `CodingEvent`,
8
- // `PlayerInput`, `PlayerOutput`) at the boundary.
9
- // @ts-nocheck
3
+ //
4
+ // Generated by slc/gears2fsm.md from ./code.gears.md.
10
5
  import { assign, fromPromise, setup } from 'xstate';
11
- const jumpableStateIds = [
12
- 'ready',
13
- 'planAndImplement',
14
- 'respondToReview',
15
- 'continueIr',
16
- 'summarizeSpecs',
17
- 'reviewBossCommitSpecs',
18
- 'reviewBossCommitCode',
19
- 'reviewBossCommitMixed',
20
- 'reviewIrTaskCommitSpecs',
21
- 'reviewIrTaskCommitCode',
22
- 'reviewIrTaskCommitMixed',
23
- 'reviewChangesSpecs',
24
- 'reviewChangesCode',
25
- 'reviewChangesMixed',
26
- 'reviewChangesAndChallengesSpecs',
27
- 'reviewChangesAndChallengesCode',
28
- 'reviewChangesAndChallengesMixed',
29
- 'adjudicateChallenges',
30
- 'commitCoderInitial',
31
- 'commitJoint',
32
- 'failed',
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
- ];
55
- const stateDescriptions = {
56
- ready: 'Idle hub: waits for Boss to start or resume a coding sub-procedure.',
57
- awaitBossReply: 'Waiting for Boss to answer a player question.',
58
- planAndImplement: 'CODE-1: Coder assesses a Boss intent and either implements a single-commit change or drafts an IR.',
59
- respondToReview: 'CODE-2: Coder addresses or challenges Reviewer findings.',
60
- continueIr: 'CODE-3: Coder continues an IR after the previous task or IR draft passed review.',
61
- summarizeSpecs: 'CODE-4: Coder summarizes a completed IR into minimal spec items.',
62
- reviewBossCommitSpecs: 'CODE-5: Reviewer reviews a Boss-intent commit whose changes are only in spec item files.',
63
- reviewBossCommitCode: 'CODE-6: Reviewer reviews a Boss-intent commit whose changes are only outside spec item files.',
64
- reviewBossCommitMixed: 'CODE-7: Reviewer reviews a Boss-intent commit whose changes span both spec item files and other files.',
65
- reviewIrTaskCommitSpecs: 'CODE-8: Reviewer reviews an IR-task commit whose changes are only in spec item files.',
66
- reviewIrTaskCommitCode: 'CODE-9: Reviewer reviews an IR-task commit whose changes are only outside spec item files.',
67
- reviewIrTaskCommitMixed: 'CODE-10: Reviewer reviews an IR-task commit whose changes span both spec item files and other files.',
68
- reviewChangesSpecs: 'CODE-11: Reviewer reviews uncommitted Coder changes that touch only spec item files with no accompanying rebuttals.',
69
- reviewChangesCode: 'CODE-12: Reviewer reviews uncommitted Coder changes that touch only files outside spec item files with no accompanying rebuttals.',
70
- reviewChangesMixed: 'CODE-13: Reviewer reviews uncommitted Coder changes that touch both spec item files and other files with no accompanying rebuttals.',
71
- reviewChangesAndChallengesSpecs: 'CODE-15: Reviewer reviews uncommitted Coder changes that touch only spec item files and adjudicates accompanying rebuttals in one round.',
72
- reviewChangesAndChallengesCode: 'CODE-16: Reviewer reviews uncommitted Coder changes that touch only files outside spec item files and adjudicates accompanying rebuttals in one round.',
73
- reviewChangesAndChallengesMixed: 'CODE-17: Reviewer reviews uncommitted Coder changes that touch both spec item files and other files and adjudicates accompanying rebuttals in one round.',
74
- adjudicateChallenges: 'CODE-14: Reviewer adjudicates Coder rebuttals against the prior review when Coder produced no code edits this round.',
75
- commitCoderInitial: 'CODE-18: Committer commits Coder Initial Changes when Reviewer has not played since the last commit.',
76
- commitJoint: 'CODE-19: Committer commits changes when both Coder and Reviewer have played since the last commit.',
77
- failed: 'Captures the last Captain error so the runner can report it. Boss may resume from here.',
78
- done: 'The selected coding workflow has completed.',
6
+ const FIRST_PHASE_PROMPT = [
7
+ '> <caller-input>',
8
+ '> <run-results>',
9
+ '',
10
+ 'Assess whether the coding intent can be completed well in one commit.',
11
+ 'If yes, implement and test it, update the affected specs, and ensure @specs/map.md remains accurate.',
12
+ 'Otherwise, decompose it into tasks sized to exactly one commit each, add a new IR under @specs/intents, and do not implement any IR task in this phase.',
13
+ 'Plan affected spec updates before, with, or after their corresponding code changes, either as standalone IR tasks or as explicit work within related tasks.',
14
+ '',
15
+ 'Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.',
16
+ '',
17
+ 'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
18
+ "Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
19
+ 'Make the commit message explain concisely what changed and why, including relevant verification.',
20
+ 'Report it as exactly one final-response line beginning `Commit: `, followed only by the exact commit identity.',
21
+ 'Coder is <coder-llm>; format the model token in conventional human form.',
22
+ ].join('\n');
23
+ const IR_TASK_PROMPT = [
24
+ '> <ir-task>',
25
+ '> <run-results>',
26
+ '',
27
+ 'Read IR-<#> and implement exactly the next unfinished task, including corresponding tests or specs if any.',
28
+ 'Do not implement a later task in this phase.',
29
+ "Mark the IR's progress and deliverables when relevant.",
30
+ 'If the IR will be finished after this phase, double-check that all acceptance criteria are met.',
31
+ '',
32
+ 'Do not re-run tests or builds whose inputs have not changed since any previous reported run.',
33
+ "Make the phase's minimal changes and then one new commit, following @specs/packages/git.md; never amend an existing commit.",
34
+ 'Make the commit message explain concisely what changed and why, including relevant verification.',
35
+ 'Report it as exactly one final-response line beginning `Commit: `, followed only by the exact commit identity.',
36
+ 'Coder is <coder-llm>; format the model token in conventional human form.',
37
+ ].join('\n');
38
+ const FIRST_PHASE_RESULTS = {
39
+ directCommit: 'Coder completed and committed the direct implementation phase. Output shall include `coderOutput: <verbatim final text>` and `latestCommit: <commit identity>`.',
40
+ irCommit: 'Coder created and committed a new IR without implementing an IR task. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, `irNumber`, and `irTask` naming the exact next unfinished task.',
41
+ needsBossReply: "The acting agent's prose surfaces a clarifying question for Boss that the agent cannot answer alone. Output shall include `question: <verbatim question text from the acting agent's prose>`.",
79
42
  };
80
- const playbookMeta = (stateId) => ({
81
- playbook: {
82
- stateId,
83
- description: stateDescriptions[stateId],
84
- },
85
- });
86
- const outputOf = (event) => event.output;
87
- const guardIs = (guard) => ({ event }) => outputOf(event)?.guard === guard;
88
- const guardAndOrigin = (guard, origin) => ({ context, event }) => outputOf(event)?.guard === guard && context.changeOrigin === origin;
89
- const acceptedAfter = (subject) => ({ context, event }) => outputOf(event)?.guard === 'accepted' && context.reviewSubject === subject;
90
- const noFindingsAfter = (afterReview) => ({ context, event }) => outputOf(event)?.guard === 'noFindings' &&
91
- context.afterReview === afterReview;
92
- const rememberCaptainOutput = assign({
93
- lastResult: ({ event }) => outputOf(event),
94
- lastError: () => undefined,
95
- irNumber: ({ context, event }) => outputOf(event)?.irNumber ?? context.irNumber,
96
- taskDescription: ({ context, event, }) => outputOf(event)?.taskDescription ?? context.taskDescription,
97
- reviews: ({ context, event }) => outputOf(event)?.reviews ?? context.reviews,
98
- challenges: ({ context, event, }) => outputOf(event)?.challenges ?? context.challenges,
99
- });
100
- const rememberCaptainError = assign({
101
- lastError: ({ event }) => event.error,
102
- });
103
- const rememberEmptyBossReplyError = assign({
104
- lastError: () => new Error('BOSS_REPLY received empty answer'),
105
- });
106
- const rememberBossInput = assign({
107
- intent: ({ context, event }) => event.intent ?? context.intent,
108
- irNumber: ({ context, event }) => event.irNumber ?? context.irNumber,
109
- });
110
- const needsBossReplyDescription = "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>`.";
111
- const bossReplyInputFields = (context) => ({
112
- pendingBossQuestion: context.pendingBossQuestion,
113
- bossReply: context.bossReply,
114
- });
115
- const withNeedsBossReply = (result) => ({
116
- ...result,
117
- needsBossReply: needsBossReplyDescription,
118
- });
119
- const captainStateMetadata = {
120
- planAndImplement: { sourceItem: 'CODE-1', player: 'Coder' },
121
- respondToReview: { sourceItem: 'CODE-2', player: 'Coder' },
122
- continueIr: { sourceItem: 'CODE-3', player: 'Coder' },
123
- summarizeSpecs: { sourceItem: 'CODE-4', player: 'Coder' },
124
- reviewBossCommitSpecs: { sourceItem: 'CODE-5', player: 'Reviewer' },
125
- reviewBossCommitCode: { sourceItem: 'CODE-6', player: 'Reviewer' },
126
- reviewBossCommitMixed: { sourceItem: 'CODE-7', player: 'Reviewer' },
127
- reviewIrTaskCommitSpecs: { sourceItem: 'CODE-8', player: 'Reviewer' },
128
- reviewIrTaskCommitCode: { sourceItem: 'CODE-9', player: 'Reviewer' },
129
- reviewIrTaskCommitMixed: { sourceItem: 'CODE-10', player: 'Reviewer' },
130
- reviewChangesSpecs: { sourceItem: 'CODE-11', player: 'Reviewer' },
131
- reviewChangesCode: { sourceItem: 'CODE-12', player: 'Reviewer' },
132
- reviewChangesMixed: { sourceItem: 'CODE-13', player: 'Reviewer' },
133
- reviewChangesAndChallengesSpecs: {
134
- sourceItem: 'CODE-15',
135
- player: 'Reviewer',
136
- },
137
- reviewChangesAndChallengesCode: { sourceItem: 'CODE-16', player: 'Reviewer' },
138
- reviewChangesAndChallengesMixed: {
139
- sourceItem: 'CODE-17',
140
- player: 'Reviewer',
141
- },
142
- adjudicateChallenges: { sourceItem: 'CODE-14', player: 'Reviewer' },
143
- commitCoderInitial: { sourceItem: 'CODE-18', player: 'Committer' },
144
- commitJoint: { sourceItem: 'CODE-19', player: 'Committer' },
43
+ const IR_TASK_RESULTS = {
44
+ moreTasks: 'Coder completed and committed the current IR task and at least one task remains. Output shall include `coderOutput: <verbatim final text>`, `latestCommit: <commit identity>`, and `irTask` naming the exact next unfinished task.',
45
+ finalTask: 'Coder completed and committed the final IR task. Output shall include `coderOutput: <verbatim final text>` and `latestCommit: <commit identity>`.',
46
+ needsBossReply: "The acting agent's prose surfaces a clarifying question for Boss that the agent cannot answer alone. Output shall include `question: <verbatim question text from the acting agent's prose>`.",
145
47
  };
146
- const planAndImplementInput = (context) => ({
147
- stateId: 'planAndImplement',
148
- player: 'Coder',
149
- sourceItem: 'CODE-1',
150
- intent: context.intent,
151
- ...bossReplyInputFields(context),
152
- prompt: [
153
- 'Assess whether this can be completed in a single commit, following best practices.',
154
- 'If yes, implement and test, updating both code and specs; otherwise, decompose into tasks as a new IR under @specs/intents (or @specs/iterations in older scaffolds) and stop without implementing any IR task.',
155
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
156
- 'Ensure @specs/map.md reflects the changes.',
157
- 'Do not commit.',
158
- ].join('\n'),
159
- result: withNeedsBossReply({
160
- singleCommitReady: 'Coder produced uncommitted single-commit changes (Initial Changes).',
161
- irDrafted: 'Coder decomposed the intent into a new IR and drafted it uncommitted (Initial Changes).',
162
- }),
163
- });
164
- const continueIrInput = (context) => ({
165
- stateId: 'continueIr',
166
- player: 'Coder',
167
- sourceItem: 'CODE-3',
168
- irNumber: context.irNumber,
169
- ...bossReplyInputFields(context),
170
- prompt: [
171
- 'Continue to implement IR-<#> if not all deliverables and tasks are done.',
172
- 'Implement one task at a time (including corresponding tests if any).',
173
- 'Stop after each task for review do not commit yet.',
174
- 'If relevant, mark progress in the IR.',
175
- ].join('\n'),
176
- result: withNeedsBossReply({
177
- taskReady: 'Coder produced uncommitted changes for the next IR task (Initial Changes). Output shall include `taskDescription: <one-line description of the task just implemented>`.',
178
- iterationDone: 'All IR deliverables and tasks are done.',
179
- }),
180
- });
181
- const summarizeSpecsInput = (context) => ({
182
- stateId: 'summarizeSpecs',
183
- player: 'Coder',
184
- sourceItem: 'CODE-4',
185
- irNumber: context.irNumber,
186
- ...bossReplyInputFields(context),
187
- prompt: [
188
- 'Read IR-<#> and corresponding commits.',
189
- 'According to @specs/meta.md, add or update spec items to fully capture:',
190
- '',
191
- '- the external behavior users rely on,',
192
- '- the internal system behavior, and',
193
- '- the integration/system test cases.',
194
- '',
195
- 'The spec items should be the *minimal* set needed to reimplement code without the IR.',
196
- 'The set should be complete and coherent.',
197
- 'Avoid implementation specifics.',
198
- 'Avoid redundant spec items.',
199
- 'Ensure @specs/map.md reflects the changes.',
200
- ].join('\n'),
201
- result: withNeedsBossReply({
202
- specsReady: 'Coder produced uncommitted spec updates (Initial Changes).',
203
- noSpecChanges: 'Existing specs already capture the realized intent.',
204
- }),
205
- });
206
- const setPendingBossQuestion = (resumeStateId) => assign({
207
- pendingBossQuestion: ({ context, event, }) => {
208
- const input = captainStateMetadata[resumeStateId];
48
+ const STATE_DESCRIPTIONS = {
49
+ ready: 'Waiting for a coding intent.',
50
+ runFirstPhase: 'Coder is implementing one direct phase or committing a new intent record.',
51
+ reviewFirstCommit: 'The REVIEW playbook is checking the first phase commit.',
52
+ runIrTask: 'Coder is implementing exactly one unfinished intent-record task.',
53
+ reviewIrTask: 'The REVIEW playbook is checking the latest task commit.',
54
+ awaitBossReply: 'Waiting for Boss to answer Coder.',
55
+ failed: 'The coding workflow failed and is waiting for a new coding intent.',
56
+ done: 'The coding workflow completed after REVIEW found no unsettled findings.',
57
+ };
58
+ function playbookMeta(stateId, player) {
59
+ return {
60
+ playbook: {
61
+ stateId,
62
+ description: STATE_DESCRIPTIONS[stateId],
63
+ ...(player === undefined ? {} : { player }),
64
+ },
65
+ };
66
+ }
67
+ function isRecord(value) {
68
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
69
+ return false;
70
+ }
71
+ const prototype = Object.getPrototypeOf(value);
72
+ return prototype === Object.prototype || prototype === null;
73
+ }
74
+ function isNonEmptyString(value) {
75
+ return typeof value === 'string' && value.trim().length > 0;
76
+ }
77
+ function playerOutput(event) {
78
+ if (!isRecord(event))
79
+ return undefined;
80
+ return isRecord(event.output) ? event.output : undefined;
81
+ }
82
+ function outputGuard(event, guard) {
83
+ return playerOutput(event)?.guard === guard;
84
+ }
85
+ function outputString(event, field) {
86
+ const value = playerOutput(event)?.[field];
87
+ return isNonEmptyString(value) ? value : undefined;
88
+ }
89
+ function commitOutput(event) {
90
+ const coderOutput = outputString(event, 'coderOutput');
91
+ const latestCommit = outputString(event, 'latestCommit');
92
+ const commitLines = coderOutput
93
+ ?.split('\n')
94
+ .filter((line) => line.startsWith('Commit: '));
95
+ if (coderOutput === undefined ||
96
+ latestCommit === undefined ||
97
+ commitLines?.length !== 1 ||
98
+ commitLines[0] !== `Commit: ${latestCommit}`) {
99
+ return undefined;
100
+ }
101
+ return { coderOutput, latestCommit };
102
+ }
103
+ function isDirectCommit({ event }) {
104
+ return (outputGuard(event, 'directCommit') &&
105
+ commitOutput(event) !== undefined);
106
+ }
107
+ function isIrCommit({ event }) {
108
+ return (outputGuard(event, 'irCommit') &&
109
+ commitOutput(event) !== undefined &&
110
+ outputString(event, 'irNumber') !== undefined &&
111
+ outputString(event, 'irTask') !== undefined);
112
+ }
113
+ function isMoreTasks({ event }) {
114
+ return (outputGuard(event, 'moreTasks') &&
115
+ commitOutput(event) !== undefined &&
116
+ outputString(event, 'irTask') !== undefined);
117
+ }
118
+ function isFinalTask({ event }) {
119
+ return (outputGuard(event, 'finalTask') &&
120
+ commitOutput(event) !== undefined);
121
+ }
122
+ function needsBossReply({ event }) {
123
+ return (outputGuard(event, 'needsBossReply') &&
124
+ outputString(event, 'question') !== undefined);
125
+ }
126
+ function reviewOutput(event) {
127
+ return isRecord(event) ? event.output : undefined;
128
+ }
129
+ function isReviewApprovedValue(value) {
130
+ return (isRecord(value) &&
131
+ Object.keys(value).sort().join('\0') ===
132
+ ['approvedCommit', 'noUnsettledFindings'].sort().join('\0') &&
133
+ value.approvedCommit === 'latest' &&
134
+ value.noUnsettledFindings === true);
135
+ }
136
+ function reviewApprovedFor(phase) {
137
+ return ({ context, event }) => context.phase === phase && isReviewApprovedValue(reviewOutput(event));
138
+ }
139
+ function isStateValue(value, ancestors = new Set()) {
140
+ if (typeof value === 'string')
141
+ return true;
142
+ if (!isRecord(value) || ancestors.has(value))
143
+ return false;
144
+ const next = new Set(ancestors).add(value);
145
+ return Reflect.ownKeys(value).every((key) => typeof key === 'string' &&
146
+ Object.prototype.propertyIsEnumerable.call(value, key) &&
147
+ isStateValue(value[key], next));
148
+ }
149
+ function isStringArray(value) {
150
+ return (Array.isArray(value) &&
151
+ Reflect.ownKeys(value).length === value.length + 1 &&
152
+ Reflect.ownKeys(value).every((key) => {
153
+ if (key === 'length')
154
+ return true;
155
+ return (typeof key === 'string' &&
156
+ /^(0|[1-9][0-9]*)$/.test(key) &&
157
+ Number(key) < value.length);
158
+ }) &&
159
+ value.every((entry) => isNonEmptyString(entry)) &&
160
+ new Set(value).size === value.length);
161
+ }
162
+ function isPlaybookState(value) {
163
+ if (!isRecord(value))
164
+ return false;
165
+ const allowed = new Set([
166
+ 'value',
167
+ 'activeStateIds',
168
+ 'tags',
169
+ 'status',
170
+ 'quiescent',
171
+ 'stateId',
172
+ ]);
173
+ if (Reflect.ownKeys(value).some((key) => typeof key !== 'string' || !allowed.has(key)) ||
174
+ !Object.prototype.hasOwnProperty.call(value, 'value') ||
175
+ !Object.prototype.hasOwnProperty.call(value, 'activeStateIds') ||
176
+ !Object.prototype.hasOwnProperty.call(value, 'tags') ||
177
+ !Object.prototype.hasOwnProperty.call(value, 'status') ||
178
+ !Object.prototype.hasOwnProperty.call(value, 'quiescent')) {
179
+ return false;
180
+ }
181
+ return (isStateValue(value.value) &&
182
+ isStringArray(value.activeStateIds) &&
183
+ isStringArray(value.tags) &&
184
+ (value.status === 'active' ||
185
+ value.status === 'done' ||
186
+ value.status === 'error' ||
187
+ value.status === 'stopped') &&
188
+ typeof value.quiescent === 'boolean' &&
189
+ (!Object.prototype.hasOwnProperty.call(value, 'stateId') ||
190
+ (isNonEmptyString(value.stateId) &&
191
+ value.activeStateIds.length === 1 &&
192
+ value.activeStateIds[0] === value.stateId)));
193
+ }
194
+ function nestedResultFromError(error) {
195
+ if (!(error instanceof Error))
196
+ return undefined;
197
+ const result = error.result;
198
+ return isRecord(result) ? result : undefined;
199
+ }
200
+ function normalizedReviewFailure(error) {
201
+ const result = nestedResultFromError(error);
202
+ if (result === undefined)
203
+ return undefined;
204
+ const allowed = new Set([
205
+ 'status',
206
+ 'playbookId',
207
+ 'childSessionId',
208
+ 'state',
209
+ 'error',
210
+ ]);
211
+ if (Reflect.ownKeys(result).some((key) => typeof key !== 'string' || !allowed.has(key)) ||
212
+ (result.status !== 'aborted' && result.status !== 'error') ||
213
+ result.playbookId !== 'review') {
214
+ return undefined;
215
+ }
216
+ if (Object.prototype.hasOwnProperty.call(result, 'childSessionId') &&
217
+ !isNonEmptyString(result.childSessionId)) {
218
+ return undefined;
219
+ }
220
+ if (Object.prototype.hasOwnProperty.call(result, 'state') &&
221
+ !isPlaybookState(result.state)) {
222
+ return undefined;
223
+ }
224
+ let normalizedError;
225
+ if (Object.prototype.hasOwnProperty.call(result, 'error')) {
226
+ if (!isRecord(result.error))
227
+ return undefined;
228
+ const errorKeys = Reflect.ownKeys(result.error);
229
+ if (errorKeys.some((key) => typeof key !== 'string' ||
230
+ (key !== 'name' && key !== 'message' && key !== 'stack')) ||
231
+ !Object.prototype.hasOwnProperty.call(result.error, 'name') ||
232
+ !Object.prototype.hasOwnProperty.call(result.error, 'message') ||
233
+ !isNonEmptyString(result.error.name) ||
234
+ typeof result.error.message !== 'string' ||
235
+ (Object.prototype.hasOwnProperty.call(result.error, 'stack') &&
236
+ typeof result.error.stack !== 'string')) {
237
+ return undefined;
238
+ }
239
+ normalizedError = {
240
+ name: result.error.name,
241
+ message: result.error.message,
242
+ };
243
+ }
244
+ else if (result.status === 'error') {
245
+ return undefined;
246
+ }
247
+ return {
248
+ status: result.status,
249
+ ...(normalizedError === undefined ? {} : { error: normalizedError }),
250
+ };
251
+ }
252
+ function authoredReviewFailure({ event }) {
253
+ const error = isRecord(event) ? event.error : undefined;
254
+ return normalizedReviewFailure(error) !== undefined;
255
+ }
256
+ function compactError(value) {
257
+ if (value instanceof Error) {
209
258
  return {
210
- questionId: resumeStateId,
211
- resumeStateId,
212
- sourceItem: input.sourceItem,
213
- player: input.player,
214
- question: outputOf(event)?.question ?? '',
259
+ name: value.name || 'Error',
260
+ message: value.message,
215
261
  };
262
+ }
263
+ return { name: 'Error', message: String(value) };
264
+ }
265
+ function authoredReviewError(event) {
266
+ const outer = isRecord(event) ? event.error : undefined;
267
+ const failure = normalizedReviewFailure(outer);
268
+ if (failure?.error !== undefined)
269
+ return failure.error;
270
+ if (failure?.status === 'aborted') {
271
+ return { name: 'AbortError', message: 'REVIEW was aborted.' };
272
+ }
273
+ return compactError(outer);
274
+ }
275
+ function quoteRelay(label, value) {
276
+ const lines = value.split('\n');
277
+ return [`> ${label}: ${lines[0] ?? ''}`, ...lines.slice(1).map((line) => `> ${line}`)].join('\n');
278
+ }
279
+ function initialReviewText(context) {
280
+ return [
281
+ quoteRelay('Initial intent', context.callerInput ?? ''),
282
+ quoteRelay('Coder output', context.coderOutput ?? ''),
283
+ ].join('\n');
284
+ }
285
+ function irTaskReviewText(context) {
286
+ return [
287
+ quoteRelay('IR task', context.irTask ?? ''),
288
+ quoteRelay('Coder output', context.coderOutput ?? ''),
289
+ ].join('\n');
290
+ }
291
+ const machineSetup = setup({
292
+ types: {},
293
+ actors: {
294
+ player: fromPromise(async () => {
295
+ throw new Error('player actor must be provided by the runner');
296
+ }),
297
+ playbook: fromPromise(async () => {
298
+ throw new Error('playbook actor must be provided by the runner');
299
+ }),
216
300
  },
217
- bossReply: () => undefined,
218
- });
219
- const rememberBossReply = assign({
220
- bossReply: ({ event }) => event.answer ?? '',
221
- });
222
- const clearBossReplyContext = assign({
223
- pendingBossQuestion: () => undefined,
224
- bossReply: () => undefined,
225
- });
226
- const actionsArray = (actions) => actions === undefined ? [] : Array.isArray(actions) ? actions : [actions];
227
- const withClearBossReplyContext = (transition) => ({
228
- ...transition,
229
- actions: [clearBossReplyContext, ...actionsArray(transition.actions)],
230
- });
231
- const bossInterrupts = (ids, extraActions = []) => ids.map((id) => ({
232
- target: `#${id}`,
233
- guard: ({ event }) => event.type === 'BOSS_INTERRUPT' && event.targetId === id,
234
- reenter: true,
235
- actions: [...extraActions, rememberBossInput],
236
- }));
237
- const resumableStates = (ids) => ids.map((id) => ({
238
- target: `#${id}`,
239
- guard: ({ context, event, }) => event.type === 'BOSS_REPLY' &&
240
- context.pendingBossQuestion?.resumeStateId === id &&
241
- (event.questionId === undefined ||
242
- event.questionId === context.pendingBossQuestion.questionId),
243
- reenter: true,
244
- actions: rememberBossReply,
245
- }));
246
- const bossReplyIsEmpty = ({ event }) => event.type === 'BOSS_REPLY' && event.answer.trim() === '';
247
- const withNeedsBossReplyTransition = (resumeStateId, transitions) => [
248
- ...transitions.map((transition) => withClearBossReplyContext(transition)),
249
- {
250
- guard: guardIs('needsBossReply'),
251
- target: '#awaitBossReply',
252
- actions: [rememberCaptainOutput, setPendingBossQuestion(resumeStateId)],
253
- },
254
- ];
255
- const captainError = {
256
- target: '#failed',
257
- actions: rememberCaptainError,
258
- };
259
- const readyEvents = {
260
- START_CODING: {
261
- target: '#planAndImplement',
262
- actions: [
263
- rememberBossInput,
264
- assign({
265
- workflow: () => 'singleCommit',
266
- changeOrigin: () => 'bossIntent',
267
- afterReview: () => 'done',
268
- }),
269
- ],
270
- },
271
- CONTINUE_IR: {
272
- target: '#continueIr',
273
- actions: [
274
- rememberBossInput,
275
- assign({
276
- workflow: () => 'iteration',
277
- changeOrigin: () => 'irTask',
278
- afterReview: () => 'continueIr',
279
- }),
280
- ],
301
+ guards: {
302
+ isDirectCommit,
303
+ isIrCommit,
304
+ isMoreTasks,
305
+ isFinalTask,
306
+ needsBossReply,
307
+ reviewApprovedDirect: reviewApprovedFor('direct'),
308
+ reviewApprovedIrCreated: reviewApprovedFor('ir-created'),
309
+ reviewApprovedMoreTasks: reviewApprovedFor('ir-task-more'),
310
+ reviewApprovedFinalTask: reviewApprovedFor('ir-task-final'),
311
+ authoredReviewFailure,
312
+ emptyBossReply: ({ event }) => event.type === 'BOSS_REPLY' && event.answer.trim().length === 0,
313
+ resumesFirstPhase: ({ context, event }) => event.type === 'BOSS_REPLY' &&
314
+ event.answer.trim().length > 0 &&
315
+ context.pendingBossQuestion?.resumeStateId === 'runFirstPhase' &&
316
+ (event.questionId === undefined || event.questionId === 'runFirstPhase'),
317
+ resumesIrTask: ({ context, event }) => event.type === 'BOSS_REPLY' &&
318
+ event.answer.trim().length > 0 &&
319
+ context.pendingBossQuestion?.resumeStateId === 'runIrTask' &&
320
+ (event.questionId === undefined || event.questionId === 'runIrTask'),
281
321
  },
282
- SUMMARIZE_IR: {
283
- target: '#summarizeSpecs',
284
- actions: [
285
- rememberBossInput,
286
- assign({
287
- workflow: () => 'specSummary',
288
- changeOrigin: () => 'irTask',
289
- afterReview: () => 'done',
290
- }),
291
- ],
322
+ actions: {
323
+ startCoding: assign(({ context, event }) => {
324
+ if (event.type !== 'START_CODE')
325
+ return {};
326
+ return {
327
+ coderPlayer: context.coderPlayer,
328
+ runResults: context.runResults,
329
+ callerInput: event.callerInput,
330
+ coderOutput: undefined,
331
+ latestCommit: undefined,
332
+ irNumber: undefined,
333
+ irTask: undefined,
334
+ nextIrTask: undefined,
335
+ phase: undefined,
336
+ completion: undefined,
337
+ reviewError: undefined,
338
+ lastError: undefined,
339
+ pendingBossQuestion: undefined,
340
+ bossReply: undefined,
341
+ };
342
+ }),
343
+ rememberDirectCommit: assign(({ event }) => ({
344
+ coderOutput: outputString(event, 'coderOutput'),
345
+ latestCommit: outputString(event, 'latestCommit'),
346
+ phase: 'direct',
347
+ pendingBossQuestion: undefined,
348
+ bossReply: undefined,
349
+ })),
350
+ rememberIrCommit: assign(({ event }) => ({
351
+ coderOutput: outputString(event, 'coderOutput'),
352
+ latestCommit: outputString(event, 'latestCommit'),
353
+ irNumber: outputString(event, 'irNumber'),
354
+ irTask: outputString(event, 'irTask'),
355
+ nextIrTask: undefined,
356
+ phase: 'ir-created',
357
+ pendingBossQuestion: undefined,
358
+ bossReply: undefined,
359
+ })),
360
+ rememberMoreTasks: assign(({ event }) => ({
361
+ coderOutput: outputString(event, 'coderOutput'),
362
+ latestCommit: outputString(event, 'latestCommit'),
363
+ nextIrTask: outputString(event, 'irTask'),
364
+ phase: 'ir-task-more',
365
+ pendingBossQuestion: undefined,
366
+ bossReply: undefined,
367
+ })),
368
+ rememberFinalTask: assign(({ event }) => ({
369
+ coderOutput: outputString(event, 'coderOutput'),
370
+ latestCommit: outputString(event, 'latestCommit'),
371
+ nextIrTask: undefined,
372
+ phase: 'ir-task-final',
373
+ pendingBossQuestion: undefined,
374
+ bossReply: undefined,
375
+ })),
376
+ advanceToNextIrTask: assign(({ context }) => ({
377
+ irTask: context.nextIrTask,
378
+ nextIrTask: undefined,
379
+ })),
380
+ rememberPendingQuestion: assign(({ context, event }) => {
381
+ const question = outputString(event, 'question');
382
+ const output = playerOutput(event);
383
+ const stateId = context.irNumber === undefined ? 'runFirstPhase' : 'runIrTask';
384
+ if (question === undefined || output?.guard !== 'needsBossReply') {
385
+ return { lastError: new Error('Coder question output was malformed') };
386
+ }
387
+ return {
388
+ pendingBossQuestion: {
389
+ questionId: stateId,
390
+ resumeStateId: stateId,
391
+ sourceItem: stateId === 'runFirstPhase' ? 'CODE-1' : 'CODE-3',
392
+ player: 'Coder',
393
+ question,
394
+ },
395
+ bossReply: undefined,
396
+ };
397
+ }),
398
+ rememberBossReply: assign(({ event }) => event.type === 'BOSS_REPLY' ? { bossReply: event.answer } : {}),
399
+ rememberEmptyBossReplyError: assign({
400
+ lastError: () => new Error('BOSS_REPLY received an empty answer'),
401
+ pendingBossQuestion: undefined,
402
+ bossReply: undefined,
403
+ }),
404
+ completeSuccessfully: assign({
405
+ completion: 'complete',
406
+ reviewError: undefined,
407
+ }),
408
+ completeWithReviewFailure: assign(({ event }) => ({
409
+ completion: 'review-failed',
410
+ reviewError: authoredReviewError(event),
411
+ })),
412
+ completeWithInvalidReviewOutput: assign(({ event }) => ({
413
+ completion: 'review-failed',
414
+ reviewError: {
415
+ name: 'ReviewContractError',
416
+ message: `REVIEW returned an invalid approval result: ` +
417
+ JSON.stringify(reviewOutput(event)),
418
+ },
419
+ })),
420
+ rememberActorError: assign(({ event }) => ({
421
+ lastError: event.error,
422
+ })),
423
+ rememberMalformedPlayerOutput: assign(({ context }) => ({
424
+ lastError: new Error(`Coder result for ${context.irNumber === undefined ? 'CODE-1' : 'CODE-3'} ` +
425
+ 'did not match a declared outcome.'),
426
+ })),
292
427
  },
293
- };
294
- const awaitBossReplyEvents = {
295
- START_CODING: withClearBossReplyContext(readyEvents.START_CODING),
296
- CONTINUE_IR: withClearBossReplyContext(readyEvents.CONTINUE_IR),
297
- SUMMARIZE_IR: withClearBossReplyContext(readyEvents.SUMMARIZE_IR),
298
- BOSS_INTERRUPT: bossInterrupts(jumpableStateIds, [clearBossReplyContext]),
299
- BOSS_REPLY: [
300
- {
301
- guard: bossReplyIsEmpty,
302
- target: '#failed',
303
- actions: [clearBossReplyContext, rememberEmptyBossReplyError],
304
- },
305
- ...resumableStates(resumableStateIds),
306
- ],
307
- };
308
- const playerPlaceholder = fromPromise(async () => {
309
- throw new Error('player actor must be provided by the runner');
310
428
  });
311
- export const codingMachine = setup({
312
- types: {},
313
- actors: {
314
- player: playerPlaceholder,
315
- },
316
- }).createMachine({
317
- id: 'coding',
429
+ export const codingMachine = machineSetup.createMachine({
430
+ id: 'code',
431
+ initial: 'ready',
318
432
  context: ({ input }) => ({
319
- intent: input?.intent,
320
- irNumber: input?.irNumber,
321
- coderPlayer: input?.coderPlayer,
322
- reviewerPlayer: input?.reviewerPlayer,
323
- committerPlayer: input?.committerPlayer,
433
+ coderPlayer: isNonEmptyString(input.coderPlayer) ? input.coderPlayer : 'Coder',
434
+ runResults: typeof input.runResults === 'string' ? input.runResults : '',
324
435
  }),
325
- initial: 'ready',
326
- on: {
327
- BOSS_INTERRUPT: bossInterrupts(jumpableStateIds),
436
+ output: ({ context }) => {
437
+ const lastCodeCommit = context.latestCommit;
438
+ if (!isNonEmptyString(lastCodeCommit)) {
439
+ throw new Error('CODE reached a final state without a commit identity');
440
+ }
441
+ const lastCodeOutput = context.coderOutput ?? '';
442
+ if (context.completion === 'review-failed') {
443
+ return {
444
+ status: 'review-failed',
445
+ lastCodeCommit,
446
+ lastCodeOutput,
447
+ error: context.reviewError ?? {
448
+ name: 'Error',
449
+ message: 'REVIEW failed without error detail.',
450
+ },
451
+ };
452
+ }
453
+ return { status: 'complete', lastCodeCommit, lastCodeOutput };
328
454
  },
329
455
  states: {
330
456
  ready: {
331
457
  id: 'ready',
332
- description: stateDescriptions.ready,
458
+ description: STATE_DESCRIPTIONS.ready,
333
459
  meta: playbookMeta('ready'),
334
460
  tags: ['playbook.parked'],
335
- on: readyEvents,
336
- },
337
- awaitBossReply: {
338
- id: 'awaitBossReply',
339
- description: stateDescriptions.awaitBossReply,
340
- meta: playbookMeta('awaitBossReply'),
341
- tags: ['playbook.parked'],
342
- on: awaitBossReplyEvents,
343
- },
344
- planAndImplement: {
345
- id: 'planAndImplement',
346
- description: stateDescriptions.planAndImplement,
347
- meta: playbookMeta('planAndImplement'),
348
- tags: ['playbook.busy'],
349
- invoke: {
350
- src: 'player',
351
- input: ({ context }) => planAndImplementInput(context),
352
- onDone: withNeedsBossReplyTransition('planAndImplement', [
353
- {
354
- guard: guardIs('singleCommitReady'),
355
- target: '#commitCoderInitial',
356
- actions: [
357
- rememberCaptainOutput,
358
- assign({
359
- workflow: () => 'singleCommit',
360
- changeOrigin: () => 'bossIntent',
361
- afterReview: () => 'done',
362
- }),
363
- ],
364
- },
365
- {
366
- guard: guardIs('irDrafted'),
367
- target: '#commitCoderInitial',
368
- actions: [
369
- rememberCaptainOutput,
370
- assign({
371
- workflow: () => 'iteration',
372
- changeOrigin: () => 'bossIntent',
373
- afterReview: () => 'continueIr',
374
- }),
375
- ],
376
- },
377
- ]),
378
- onError: captainError,
461
+ on: {
462
+ START_CODE: { target: 'runFirstPhase', actions: 'startCoding' },
379
463
  },
380
464
  },
381
- respondToReview: {
382
- id: 'respondToReview',
383
- description: stateDescriptions.respondToReview,
384
- meta: playbookMeta('respondToReview'),
465
+ runFirstPhase: {
466
+ id: 'runFirstPhase',
467
+ description: STATE_DESCRIPTIONS.runFirstPhase,
468
+ meta: playbookMeta('runFirstPhase', 'Coder'),
385
469
  tags: ['playbook.busy'],
386
470
  invoke: {
387
471
  src: 'player',
388
472
  input: ({ context }) => ({
389
- stateId: 'respondToReview',
473
+ stateId: 'runFirstPhase',
390
474
  player: 'Coder',
391
- sourceItem: 'CODE-2',
392
- ...bossReplyInputFields(context),
393
- reviews: context.reviews,
394
- prompt: [
395
- 'For each review item below for the above changes, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
396
- 'Stage all current changes that belong in the repo before making any edits, and leave your edits unstaged/untracked.',
397
- ].join('\n'),
398
- result: {
399
- changesMadeSpecs: 'Coder accepted items and produced unstaged/untracked edits only in spec item files (@specs/packages/, @specs/compositions/, or legacy @specs/{user,dev,test}/), without raising any rebuttals.',
400
- changesMadeCode: 'Coder accepted items and produced unstaged/untracked edits only outside spec item files (any other files, including @specs/ decision, intent, or legacy iteration records, @specs/map.md, and @specs/meta.md), without raising any rebuttals.',
401
- changesMadeMixed: 'Coder accepted items and produced unstaged/untracked edits spanning both spec item files and other files, without raising any rebuttals.',
402
- changesMadeSpecsAndChallenged: 'Coder produced unstaged/untracked edits only in spec item files (@specs/packages/, @specs/compositions/, or legacy @specs/{user,dev,test}/) AND challenged one or more review items. Output shall include `challenges: <numbered rebuttals, one per challenged item>`.',
403
- changesMadeCodeAndChallenged: 'Coder produced unstaged/untracked edits only outside spec item files AND challenged one or more review items. Output shall include `challenges: <numbered rebuttals, one per challenged item>`.',
404
- changesMadeMixedAndChallenged: 'Coder produced unstaged/untracked edits spanning both spec item files and other files AND challenged one or more review items. Output shall include `challenges: <numbered rebuttals, one per challenged item>`.',
405
- challengesRaised: 'Coder challenged one or more review items without producing any code edits. Output shall include `challenges: <numbered rebuttals, one per challenged item>`.',
406
- accepted: 'Coder accepted the review outcome without further edits.',
407
- needsBossReply: needsBossReplyDescription,
408
- },
409
- }),
410
- onDone: withNeedsBossReplyTransition('respondToReview', [
411
- {
412
- guard: guardIs('changesMadeSpecs'),
413
- target: '#reviewChangesSpecs',
414
- actions: rememberCaptainOutput,
415
- },
416
- {
417
- guard: guardIs('changesMadeCode'),
418
- target: '#reviewChangesCode',
419
- actions: rememberCaptainOutput,
420
- },
421
- {
422
- guard: guardIs('changesMadeMixed'),
423
- target: '#reviewChangesMixed',
424
- actions: rememberCaptainOutput,
425
- },
426
- {
427
- guard: guardIs('changesMadeSpecsAndChallenged'),
428
- target: '#reviewChangesAndChallengesSpecs',
429
- actions: rememberCaptainOutput,
430
- },
431
- {
432
- guard: guardIs('changesMadeCodeAndChallenged'),
433
- target: '#reviewChangesAndChallengesCode',
434
- actions: rememberCaptainOutput,
435
- },
436
- {
437
- guard: guardIs('changesMadeMixedAndChallenged'),
438
- target: '#reviewChangesAndChallengesMixed',
439
- actions: rememberCaptainOutput,
440
- },
441
- {
442
- guard: guardIs('challengesRaised'),
443
- target: '#adjudicateChallenges',
444
- actions: rememberCaptainOutput,
445
- },
446
- {
447
- guard: acceptedAfter('changes'),
448
- target: '#commitJoint',
449
- actions: rememberCaptainOutput,
450
- },
451
- {
452
- guard: ({ context, event }) => outputOf(event)?.guard === 'accepted' &&
453
- context.reviewSubject === 'commit' &&
454
- context.afterReview === 'continueIr',
455
- target: '#continueIr',
456
- actions: rememberCaptainOutput,
457
- },
458
- {
459
- guard: ({ context, event }) => outputOf(event)?.guard === 'accepted' &&
460
- context.reviewSubject === 'commit' &&
461
- context.afterReview === 'summarizeSpecs',
462
- target: '#summarizeSpecs',
463
- actions: rememberCaptainOutput,
464
- },
465
- {
466
- guard: ({ context, event }) => outputOf(event)?.guard === 'accepted' &&
467
- context.reviewSubject === 'commit' &&
468
- context.afterReview === 'done',
469
- target: '#done',
470
- actions: rememberCaptainOutput,
471
- },
472
- ]),
473
- onError: captainError,
474
- },
475
- },
476
- continueIr: {
477
- id: 'continueIr',
478
- description: stateDescriptions.continueIr,
479
- meta: playbookMeta('continueIr'),
480
- tags: ['playbook.busy'],
481
- invoke: {
482
- src: 'player',
483
- input: ({ context }) => continueIrInput(context),
484
- onDone: withNeedsBossReplyTransition('continueIr', [
485
- {
486
- guard: guardIs('taskReady'),
487
- target: '#commitCoderInitial',
488
- actions: [
489
- rememberCaptainOutput,
490
- assign({
491
- workflow: () => 'iteration',
492
- changeOrigin: () => 'irTask',
493
- afterReview: () => 'continueIr',
494
- }),
495
- ],
496
- },
497
- {
498
- guard: guardIs('iterationDone'),
499
- target: '#summarizeSpecs',
500
- actions: [
501
- rememberCaptainOutput,
502
- assign({
503
- workflow: () => 'specSummary',
504
- afterReview: () => 'done',
505
- }),
506
- ],
507
- },
508
- ]),
509
- onError: captainError,
510
- },
511
- },
512
- summarizeSpecs: {
513
- id: 'summarizeSpecs',
514
- description: stateDescriptions.summarizeSpecs,
515
- meta: playbookMeta('summarizeSpecs'),
516
- tags: ['playbook.busy'],
517
- invoke: {
518
- src: 'player',
519
- input: ({ context }) => summarizeSpecsInput(context),
520
- onDone: withNeedsBossReplyTransition('summarizeSpecs', [
521
- {
522
- guard: guardIs('specsReady'),
523
- target: '#commitCoderInitial',
524
- actions: [
525
- rememberCaptainOutput,
526
- assign({
527
- workflow: () => 'specSummary',
528
- changeOrigin: () => 'irTask',
529
- afterReview: () => 'done',
530
- }),
531
- ],
532
- },
533
- {
534
- guard: guardIs('noSpecChanges'),
535
- target: '#done',
536
- actions: rememberCaptainOutput,
537
- },
538
- ]),
539
- onError: captainError,
540
- },
541
- },
542
- reviewBossCommitSpecs: {
543
- id: 'reviewBossCommitSpecs',
544
- description: stateDescriptions.reviewBossCommitSpecs,
545
- meta: playbookMeta('reviewBossCommitSpecs'),
546
- tags: ['playbook.busy'],
547
- invoke: {
548
- src: 'player',
549
- input: ({ context }) => ({
550
- stateId: 'reviewBossCommitSpecs',
551
- player: 'Reviewer',
552
- sourceItem: 'CODE-5',
553
- ...bossReplyInputFields(context),
554
- intent: context.intent,
555
- prompt: [
556
- 'Review the latest commit.',
557
- 'Refer to the commit message.',
558
- 'Verify any affected spec items are:',
559
- '',
560
- '- Complete & coherent: sufficient for you to reimplement code.',
561
- '- Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.',
562
- '- Minimal: essential and concise; every item earns its place; also check with other items.',
563
- '- Well organized: spec packages are finely scoped, with high cohesion and low coupling.',
564
- '',
565
- 'Flag anything missing, redundant, over-specified, or under-specified.',
566
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
567
- 'Verify @specs/map.md reflects the changes.',
568
- "If the change is ready to commit or push, don't raise nitpicks.",
569
- 'Do not edit files or commit; report findings only.',
570
- ].join('\n'),
571
- result: {
572
- noFindings: 'The spec-only commit has no review findings.',
573
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
574
- needsBossReply: needsBossReplyDescription,
575
- },
576
- }),
577
- onDone: withNeedsBossReplyTransition('reviewBossCommitSpecs', [
578
- {
579
- guard: noFindingsAfter('continueIr'),
580
- target: '#continueIr',
581
- actions: rememberCaptainOutput,
582
- },
583
- {
584
- guard: noFindingsAfter('summarizeSpecs'),
585
- target: '#summarizeSpecs',
586
- actions: rememberCaptainOutput,
587
- },
588
- {
589
- guard: noFindingsAfter('done'),
590
- target: '#done',
591
- actions: rememberCaptainOutput,
592
- },
593
- {
594
- guard: guardIs('hasFindings'),
595
- target: '#respondToReview',
596
- actions: [
597
- rememberCaptainOutput,
598
- assign({ reviewSubject: () => 'commit' }),
599
- ],
600
- },
601
- ]),
602
- onError: captainError,
603
- },
604
- },
605
- reviewBossCommitCode: {
606
- id: 'reviewBossCommitCode',
607
- description: stateDescriptions.reviewBossCommitCode,
608
- meta: playbookMeta('reviewBossCommitCode'),
609
- tags: ['playbook.busy'],
610
- invoke: {
611
- src: 'player',
612
- input: ({ context }) => ({
613
- stateId: 'reviewBossCommitCode',
614
- player: 'Reviewer',
615
- sourceItem: 'CODE-6',
616
- ...bossReplyInputFields(context),
617
- intent: context.intent,
618
- prompt: [
619
- 'Review the latest commit.',
620
- 'Refer to the commit message.',
621
- 'Flag any issues or improvements (numbered; no duplication).',
622
- "Think thoroughly — don't just approve or reject.",
623
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
624
- 'Verify @specs/map.md reflects the changes.',
625
- "If the change is ready to commit or push, don't raise nitpicks.",
626
- 'Do not edit files or commit; report findings only.',
627
- ].join('\n'),
628
- result: {
629
- noFindings: 'The code-only commit has no review findings.',
630
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
631
- needsBossReply: needsBossReplyDescription,
632
- },
633
- }),
634
- onDone: withNeedsBossReplyTransition('reviewBossCommitCode', [
635
- {
636
- guard: noFindingsAfter('continueIr'),
637
- target: '#continueIr',
638
- actions: rememberCaptainOutput,
639
- },
640
- {
641
- guard: noFindingsAfter('summarizeSpecs'),
642
- target: '#summarizeSpecs',
643
- actions: rememberCaptainOutput,
644
- },
645
- {
646
- guard: noFindingsAfter('done'),
647
- target: '#done',
648
- actions: rememberCaptainOutput,
649
- },
650
- {
651
- guard: guardIs('hasFindings'),
652
- target: '#respondToReview',
653
- actions: [
654
- rememberCaptainOutput,
655
- assign({ reviewSubject: () => 'commit' }),
656
- ],
657
- },
658
- ]),
659
- onError: captainError,
660
- },
661
- },
662
- reviewBossCommitMixed: {
663
- id: 'reviewBossCommitMixed',
664
- description: stateDescriptions.reviewBossCommitMixed,
665
- meta: playbookMeta('reviewBossCommitMixed'),
666
- tags: ['playbook.busy'],
667
- invoke: {
668
- src: 'player',
669
- input: ({ context }) => ({
670
- stateId: 'reviewBossCommitMixed',
671
- player: 'Reviewer',
672
- sourceItem: 'CODE-7',
673
- ...bossReplyInputFields(context),
674
- intent: context.intent,
675
- prompt: [
676
- 'Review the latest commit.',
677
- 'Refer to the commit message.',
678
- 'Flag any issues or improvements (numbered; no duplication).',
679
- "Think thoroughly — don't just approve or reject.",
680
- 'Verify any affected spec items are:',
681
- '',
682
- '- Complete & coherent: sufficient for you to reimplement code.',
683
- '- Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.',
684
- '- Minimal: essential and concise; every item earns its place; also check with other items.',
685
- '- Well organized: spec packages are finely scoped, with high cohesion and low coupling.',
686
- '',
687
- 'Flag anything missing, redundant, over-specified, or under-specified.',
688
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
689
- 'Verify @specs/map.md reflects the changes.',
690
- "If the change is ready to commit or push, don't raise nitpicks.",
691
- 'Do not edit files or commit; report findings only.',
692
- ].join('\n'),
693
- result: {
694
- noFindings: 'The mixed commit has no review findings.',
695
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
696
- needsBossReply: needsBossReplyDescription,
697
- },
698
- }),
699
- onDone: withNeedsBossReplyTransition('reviewBossCommitMixed', [
700
- {
701
- guard: noFindingsAfter('continueIr'),
702
- target: '#continueIr',
703
- actions: rememberCaptainOutput,
704
- },
705
- {
706
- guard: noFindingsAfter('summarizeSpecs'),
707
- target: '#summarizeSpecs',
708
- actions: rememberCaptainOutput,
709
- },
710
- {
711
- guard: noFindingsAfter('done'),
712
- target: '#done',
713
- actions: rememberCaptainOutput,
714
- },
715
- {
716
- guard: guardIs('hasFindings'),
717
- target: '#respondToReview',
718
- actions: [
719
- rememberCaptainOutput,
720
- assign({ reviewSubject: () => 'commit' }),
721
- ],
722
- },
723
- ]),
724
- onError: captainError,
725
- },
726
- },
727
- reviewIrTaskCommitSpecs: {
728
- id: 'reviewIrTaskCommitSpecs',
729
- description: stateDescriptions.reviewIrTaskCommitSpecs,
730
- meta: playbookMeta('reviewIrTaskCommitSpecs'),
731
- tags: ['playbook.busy'],
732
- invoke: {
733
- src: 'player',
734
- input: ({ context }) => ({
735
- stateId: 'reviewIrTaskCommitSpecs',
736
- player: 'Reviewer',
737
- sourceItem: 'CODE-8',
738
- ...bossReplyInputFields(context),
739
- irNumber: context.irNumber,
740
- taskDescription: context.taskDescription,
741
- prompt: [
742
- 'Review the latest commit.',
743
- 'Refer to the commit message.',
744
- 'Verify any affected spec items are:',
745
- '',
746
- '- Complete & coherent: sufficient for you to reimplement code.',
747
- '- Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.',
748
- '- Minimal: essential and concise; every item earns its place; also check with other items.',
749
- '- Well organized: spec packages are finely scoped, with high cohesion and low coupling.',
750
- '',
751
- 'Flag anything missing, redundant, over-specified, or under-specified.',
752
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
753
- 'Verify @specs/map.md reflects the changes.',
754
- "If the change is ready to commit or push, don't raise nitpicks.",
755
- 'Do not edit files or commit; report findings only.',
756
- ].join('\n'),
757
- result: {
758
- noFindings: 'The IR-task spec-only commit has no review findings.',
759
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
760
- needsBossReply: needsBossReplyDescription,
761
- },
762
- }),
763
- onDone: withNeedsBossReplyTransition('reviewIrTaskCommitSpecs', [
764
- {
765
- guard: noFindingsAfter('continueIr'),
766
- target: '#continueIr',
767
- actions: rememberCaptainOutput,
768
- },
769
- {
770
- guard: noFindingsAfter('summarizeSpecs'),
771
- target: '#summarizeSpecs',
772
- actions: rememberCaptainOutput,
773
- },
774
- {
775
- guard: noFindingsAfter('done'),
776
- target: '#done',
777
- actions: rememberCaptainOutput,
778
- },
779
- {
780
- guard: guardIs('hasFindings'),
781
- target: '#respondToReview',
782
- actions: [
783
- rememberCaptainOutput,
784
- assign({ reviewSubject: () => 'commit' }),
785
- ],
786
- },
787
- ]),
788
- onError: captainError,
789
- },
790
- },
791
- reviewIrTaskCommitCode: {
792
- id: 'reviewIrTaskCommitCode',
793
- description: stateDescriptions.reviewIrTaskCommitCode,
794
- meta: playbookMeta('reviewIrTaskCommitCode'),
795
- tags: ['playbook.busy'],
796
- invoke: {
797
- src: 'player',
798
- input: ({ context }) => ({
799
- stateId: 'reviewIrTaskCommitCode',
800
- player: 'Reviewer',
801
- sourceItem: 'CODE-9',
802
- ...bossReplyInputFields(context),
803
- irNumber: context.irNumber,
804
- taskDescription: context.taskDescription,
805
- prompt: [
806
- 'Review the latest commit.',
807
- 'Refer to the commit message.',
808
- 'Flag any issues or improvements (numbered; no duplication).',
809
- "Think thoroughly — don't just approve or reject.",
810
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
811
- 'Verify @specs/map.md reflects the changes.',
812
- "If the change is ready to commit or push, don't raise nitpicks.",
813
- 'Do not edit files or commit; report findings only.',
814
- ].join('\n'),
815
- result: {
816
- noFindings: 'The IR-task code-only commit has no review findings.',
817
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
818
- needsBossReply: needsBossReplyDescription,
819
- },
820
- }),
821
- onDone: withNeedsBossReplyTransition('reviewIrTaskCommitCode', [
822
- {
823
- guard: noFindingsAfter('continueIr'),
824
- target: '#continueIr',
825
- actions: rememberCaptainOutput,
826
- },
827
- {
828
- guard: noFindingsAfter('summarizeSpecs'),
829
- target: '#summarizeSpecs',
830
- actions: rememberCaptainOutput,
831
- },
832
- {
833
- guard: noFindingsAfter('done'),
834
- target: '#done',
835
- actions: rememberCaptainOutput,
836
- },
837
- {
838
- guard: guardIs('hasFindings'),
839
- target: '#respondToReview',
840
- actions: [
841
- rememberCaptainOutput,
842
- assign({ reviewSubject: () => 'commit' }),
843
- ],
844
- },
845
- ]),
846
- onError: captainError,
847
- },
848
- },
849
- reviewIrTaskCommitMixed: {
850
- id: 'reviewIrTaskCommitMixed',
851
- description: stateDescriptions.reviewIrTaskCommitMixed,
852
- meta: playbookMeta('reviewIrTaskCommitMixed'),
853
- tags: ['playbook.busy'],
854
- invoke: {
855
- src: 'player',
856
- input: ({ context }) => ({
857
- stateId: 'reviewIrTaskCommitMixed',
858
- player: 'Reviewer',
859
- sourceItem: 'CODE-10',
860
- ...bossReplyInputFields(context),
861
- irNumber: context.irNumber,
862
- taskDescription: context.taskDescription,
863
- prompt: [
864
- 'Review the latest commit.',
865
- 'Refer to the commit message.',
866
- 'Flag any issues or improvements (numbered; no duplication).',
867
- "Think thoroughly — don't just approve or reject.",
868
- 'Verify any affected spec items are:',
869
- '',
870
- '- Complete & coherent: sufficient for you to reimplement code.',
871
- '- Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.',
872
- '- Minimal: essential and concise; every item earns its place; also check with other items.',
873
- '- Well organized: spec packages are finely scoped, with high cohesion and low coupling.',
874
- '',
875
- 'Flag anything missing, redundant, over-specified, or under-specified.',
876
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
877
- 'Verify @specs/map.md reflects the changes.',
878
- "If the change is ready to commit or push, don't raise nitpicks.",
879
- 'Do not edit files or commit; report findings only.',
880
- ].join('\n'),
881
- result: {
882
- noFindings: 'The IR-task mixed commit has no review findings.',
883
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
884
- needsBossReply: needsBossReplyDescription,
885
- },
886
- }),
887
- onDone: withNeedsBossReplyTransition('reviewIrTaskCommitMixed', [
888
- {
889
- guard: noFindingsAfter('continueIr'),
890
- target: '#continueIr',
891
- actions: rememberCaptainOutput,
892
- },
893
- {
894
- guard: noFindingsAfter('summarizeSpecs'),
895
- target: '#summarizeSpecs',
896
- actions: rememberCaptainOutput,
897
- },
898
- {
899
- guard: noFindingsAfter('done'),
900
- target: '#done',
901
- actions: rememberCaptainOutput,
902
- },
903
- {
904
- guard: guardIs('hasFindings'),
905
- target: '#respondToReview',
906
- actions: [
907
- rememberCaptainOutput,
908
- assign({ reviewSubject: () => 'commit' }),
909
- ],
910
- },
911
- ]),
912
- onError: captainError,
913
- },
914
- },
915
- reviewChangesSpecs: {
916
- id: 'reviewChangesSpecs',
917
- description: stateDescriptions.reviewChangesSpecs,
918
- meta: playbookMeta('reviewChangesSpecs'),
919
- tags: ['playbook.busy'],
920
- invoke: {
921
- src: 'player',
922
- input: ({ context }) => ({
923
- stateId: 'reviewChangesSpecs',
924
- player: 'Reviewer',
925
- sourceItem: 'CODE-11',
926
- ...bossReplyInputFields(context),
927
- prompt: [
928
- 'Review the unstaged and untracked changes in the context of the staged changes.',
929
- 'Understand the intent.',
930
- 'Verify any affected spec items are:',
931
- '',
932
- '- Complete & coherent: sufficient for you to reimplement code.',
933
- '- Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.',
934
- '- Minimal: essential and concise; every item earns its place; also check with other items.',
935
- '- Well organized: spec packages are finely scoped, with high cohesion and low coupling.',
936
- '',
937
- 'Flag anything missing, redundant, over-specified, or under-specified.',
938
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
939
- 'Verify @specs/map.md reflects the changes.',
940
- "If the change is ready to commit or push, don't raise nitpicks.",
941
- 'Do not edit files or commit; report findings only.',
942
- ].join('\n'),
943
- result: {
944
- noFindings: 'The uncommitted spec-only changes are ready to commit.',
945
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
946
- needsBossReply: needsBossReplyDescription,
947
- },
948
- }),
949
- onDone: withNeedsBossReplyTransition('reviewChangesSpecs', [
950
- {
951
- guard: guardIs('noFindings'),
952
- target: '#commitJoint',
953
- actions: rememberCaptainOutput,
954
- },
955
- {
956
- guard: guardIs('hasFindings'),
957
- target: '#respondToReview',
958
- actions: [
959
- rememberCaptainOutput,
960
- assign({ reviewSubject: () => 'changes' }),
961
- ],
962
- },
963
- ]),
964
- onError: captainError,
965
- },
966
- },
967
- reviewChangesCode: {
968
- id: 'reviewChangesCode',
969
- description: stateDescriptions.reviewChangesCode,
970
- meta: playbookMeta('reviewChangesCode'),
971
- tags: ['playbook.busy'],
972
- invoke: {
973
- src: 'player',
974
- input: ({ context }) => ({
975
- stateId: 'reviewChangesCode',
976
- player: 'Reviewer',
977
- sourceItem: 'CODE-12',
978
- ...bossReplyInputFields(context),
979
- prompt: [
980
- 'Review the unstaged and untracked changes in the context of the staged changes.',
981
- 'Understand the intent.',
982
- 'Flag any issues or improvements (numbered; no duplication).',
983
- "Think thoroughly — don't just approve or reject.",
984
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
985
- 'Verify @specs/map.md reflects the changes.',
986
- "If the change is ready to commit or push, don't raise nitpicks.",
987
- 'Do not edit files or commit; report findings only.',
988
- ].join('\n'),
989
- result: {
990
- noFindings: 'The uncommitted code-only changes are ready to commit.',
991
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
992
- needsBossReply: needsBossReplyDescription,
993
- },
994
- }),
995
- onDone: withNeedsBossReplyTransition('reviewChangesCode', [
996
- {
997
- guard: guardIs('noFindings'),
998
- target: '#commitJoint',
999
- actions: rememberCaptainOutput,
1000
- },
1001
- {
1002
- guard: guardIs('hasFindings'),
1003
- target: '#respondToReview',
1004
- actions: [
1005
- rememberCaptainOutput,
1006
- assign({ reviewSubject: () => 'changes' }),
1007
- ],
1008
- },
1009
- ]),
1010
- onError: captainError,
1011
- },
1012
- },
1013
- reviewChangesMixed: {
1014
- id: 'reviewChangesMixed',
1015
- description: stateDescriptions.reviewChangesMixed,
1016
- meta: playbookMeta('reviewChangesMixed'),
1017
- tags: ['playbook.busy'],
1018
- invoke: {
1019
- src: 'player',
1020
- input: ({ context }) => ({
1021
- stateId: 'reviewChangesMixed',
1022
- player: 'Reviewer',
1023
- sourceItem: 'CODE-13',
1024
- ...bossReplyInputFields(context),
1025
- prompt: [
1026
- 'Review the unstaged and untracked changes in the context of the staged changes.',
1027
- 'Understand the intent.',
1028
- 'Flag any issues or improvements (numbered; no duplication).',
1029
- "Think thoroughly — don't just approve or reject.",
1030
- 'Verify any affected spec items are:',
1031
- '',
1032
- '- Complete & coherent: sufficient for you to reimplement code.',
1033
- '- Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.',
1034
- '- Minimal: essential and concise; every item earns its place; also check with other items.',
1035
- '- Well organized: spec packages are finely scoped, with high cohesion and low coupling.',
1036
- '',
1037
- 'Flag anything missing, redundant, over-specified, or under-specified.',
1038
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
1039
- 'Verify @specs/map.md reflects the changes.',
1040
- "If the change is ready to commit or push, don't raise nitpicks.",
1041
- 'Do not edit files or commit; report findings only.',
1042
- ].join('\n'),
1043
- result: {
1044
- noFindings: 'The uncommitted mixed changes are ready to commit.',
1045
- hasFindings: 'The review produced findings for Coder. Output shall include `reviews: <numbered list of findings, no duplication>`.',
1046
- needsBossReply: needsBossReplyDescription,
1047
- },
475
+ sourceItem: 'CODE-1',
476
+ prompt: FIRST_PHASE_PROMPT,
477
+ result: FIRST_PHASE_RESULTS,
478
+ callerInput: context.callerInput ?? '',
479
+ runResults: context.runResults,
480
+ coderPlayer: context.coderPlayer,
481
+ ...(context.pendingBossQuestion === undefined
482
+ ? {}
483
+ : { pendingBossQuestion: context.pendingBossQuestion }),
484
+ ...(context.bossReply === undefined
485
+ ? {}
486
+ : { bossReply: context.bossReply }),
1048
487
  }),
1049
- onDone: withNeedsBossReplyTransition('reviewChangesMixed', [
488
+ onDone: [
1050
489
  {
1051
- guard: guardIs('noFindings'),
1052
- target: '#commitJoint',
1053
- actions: rememberCaptainOutput,
490
+ guard: 'isDirectCommit',
491
+ target: 'reviewFirstCommit',
492
+ actions: 'rememberDirectCommit',
1054
493
  },
1055
494
  {
1056
- guard: guardIs('hasFindings'),
1057
- target: '#respondToReview',
1058
- actions: [
1059
- rememberCaptainOutput,
1060
- assign({ reviewSubject: () => 'changes' }),
1061
- ],
1062
- },
1063
- ]),
1064
- onError: captainError,
1065
- },
1066
- },
1067
- reviewChangesAndChallengesSpecs: {
1068
- id: 'reviewChangesAndChallengesSpecs',
1069
- description: stateDescriptions.reviewChangesAndChallengesSpecs,
1070
- meta: playbookMeta('reviewChangesAndChallengesSpecs'),
1071
- tags: ['playbook.busy'],
1072
- invoke: {
1073
- src: 'player',
1074
- input: ({ context }) => ({
1075
- stateId: 'reviewChangesAndChallengesSpecs',
1076
- player: 'Reviewer',
1077
- sourceItem: 'CODE-15',
1078
- ...bossReplyInputFields(context),
1079
- reviews: context.reviews,
1080
- challenges: context.challenges,
1081
- prompt: [
1082
- 'Review the unstaged and untracked changes in the context of the staged changes.',
1083
- 'Understand the intent.',
1084
- 'Verify any affected spec items are:',
1085
- '',
1086
- '- Complete & coherent: sufficient for you to reimplement code.',
1087
- '- Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.',
1088
- '- Minimal: essential and concise; every item earns its place; also check with other items.',
1089
- '- Well organized: spec packages are finely scoped, with high cohesion and low coupling.',
1090
- '',
1091
- 'Flag anything missing, redundant, over-specified, or under-specified.',
1092
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
1093
- 'Verify @specs/map.md reflects the changes.',
1094
- "If the change is ready to commit or push, don't raise nitpicks.",
1095
- 'Do not edit files or commit; report findings only.',
1096
- 'For each rebuttal below, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
1097
- ].join('\n'),
1098
- result: {
1099
- approved: 'The new spec-only changes are ready to commit and every rebuttal was accepted (or no items remained open).',
1100
- 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>`.',
1101
- needsBossReply: needsBossReplyDescription,
495
+ guard: 'isIrCommit',
496
+ target: 'reviewFirstCommit',
497
+ actions: 'rememberIrCommit',
1102
498
  },
1103
- }),
1104
- onDone: withNeedsBossReplyTransition('reviewChangesAndChallengesSpecs', [
1105
499
  {
1106
- guard: guardIs('approved'),
1107
- target: '#commitJoint',
1108
- actions: rememberCaptainOutput,
500
+ guard: 'needsBossReply',
501
+ target: 'awaitBossReply',
502
+ actions: 'rememberPendingQuestion',
1109
503
  },
1110
504
  {
1111
- guard: guardIs('needsRevision'),
1112
- target: '#respondToReview',
1113
- actions: [
1114
- rememberCaptainOutput,
1115
- assign({ reviewSubject: () => 'changes' }),
1116
- ],
505
+ target: 'failed',
506
+ actions: 'rememberMalformedPlayerOutput',
1117
507
  },
1118
- ]),
1119
- onError: captainError,
508
+ ],
509
+ onError: { target: 'failed', actions: 'rememberActorError' },
1120
510
  },
1121
511
  },
1122
- reviewChangesAndChallengesCode: {
1123
- id: 'reviewChangesAndChallengesCode',
1124
- description: stateDescriptions.reviewChangesAndChallengesCode,
1125
- meta: playbookMeta('reviewChangesAndChallengesCode'),
1126
- tags: ['playbook.busy'],
512
+ reviewFirstCommit: {
513
+ id: 'reviewFirstCommit',
514
+ description: STATE_DESCRIPTIONS.reviewFirstCommit,
515
+ meta: playbookMeta('reviewFirstCommit'),
516
+ tags: ['playbook.suspended'],
1127
517
  invoke: {
1128
- src: 'player',
518
+ src: 'playbook',
1129
519
  input: ({ context }) => ({
1130
- stateId: 'reviewChangesAndChallengesCode',
1131
- player: 'Reviewer',
1132
- sourceItem: 'CODE-16',
1133
- ...bossReplyInputFields(context),
1134
- reviews: context.reviews,
1135
- challenges: context.challenges,
1136
- prompt: [
1137
- 'Review the unstaged and untracked changes in the context of the staged changes.',
1138
- 'Understand the intent.',
1139
- 'Flag any issues or improvements (numbered; no duplication).',
1140
- "Think thoroughly — don't just approve or reject.",
1141
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
1142
- 'Verify @specs/map.md reflects the changes.',
1143
- "If the change is ready to commit or push, don't raise nitpicks.",
1144
- 'Do not edit files or commit; report findings only.',
1145
- 'For each rebuttal below, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
1146
- ].join('\n'),
1147
- result: {
1148
- approved: 'The new code-only changes are ready to commit and every rebuttal was accepted (or no items remained open).',
1149
- 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>`.',
1150
- needsBossReply: needsBossReplyDescription,
1151
- },
520
+ stateId: 'reviewFirstCommit',
521
+ sourceItem: 'CODE-2',
522
+ playbookId: 'review',
523
+ text: initialReviewText(context),
1152
524
  }),
1153
- onDone: withNeedsBossReplyTransition('reviewChangesAndChallengesCode', [
525
+ onDone: [
1154
526
  {
1155
- guard: guardIs('approved'),
1156
- target: '#commitJoint',
1157
- actions: rememberCaptainOutput,
527
+ guard: 'reviewApprovedDirect',
528
+ target: 'done',
529
+ actions: 'completeSuccessfully',
1158
530
  },
1159
531
  {
1160
- guard: guardIs('needsRevision'),
1161
- target: '#respondToReview',
1162
- actions: [
1163
- rememberCaptainOutput,
1164
- assign({ reviewSubject: () => 'changes' }),
1165
- ],
1166
- },
1167
- ]),
1168
- onError: captainError,
1169
- },
1170
- },
1171
- reviewChangesAndChallengesMixed: {
1172
- id: 'reviewChangesAndChallengesMixed',
1173
- description: stateDescriptions.reviewChangesAndChallengesMixed,
1174
- meta: playbookMeta('reviewChangesAndChallengesMixed'),
1175
- tags: ['playbook.busy'],
1176
- invoke: {
1177
- src: 'player',
1178
- input: ({ context }) => ({
1179
- stateId: 'reviewChangesAndChallengesMixed',
1180
- player: 'Reviewer',
1181
- sourceItem: 'CODE-17',
1182
- ...bossReplyInputFields(context),
1183
- reviews: context.reviews,
1184
- challenges: context.challenges,
1185
- prompt: [
1186
- 'Review the unstaged and untracked changes in the context of the staged changes.',
1187
- 'Understand the intent.',
1188
- 'Flag any issues or improvements (numbered; no duplication).',
1189
- "Think thoroughly — don't just approve or reject.",
1190
- 'Verify any affected spec items are:',
1191
- '',
1192
- '- Complete & coherent: sufficient for you to reimplement code.',
1193
- '- Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.',
1194
- '- Minimal: essential and concise; every item earns its place; also check with other items.',
1195
- '- Well organized: spec packages are finely scoped, with high cohesion and low coupling.',
1196
- '',
1197
- 'Flag anything missing, redundant, over-specified, or under-specified.',
1198
- 'For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.',
1199
- 'Verify @specs/map.md reflects the changes.',
1200
- "If the change is ready to commit or push, don't raise nitpicks.",
1201
- 'Do not edit files or commit; report findings only.',
1202
- 'For each rebuttal below, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
1203
- ].join('\n'),
1204
- result: {
1205
- approved: 'The new mixed changes are ready to commit and every rebuttal was accepted (or no items remained open).',
1206
- 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>`.',
1207
- needsBossReply: needsBossReplyDescription,
532
+ guard: 'reviewApprovedIrCreated',
533
+ target: 'runIrTask',
1208
534
  },
1209
- }),
1210
- onDone: withNeedsBossReplyTransition('reviewChangesAndChallengesMixed', [
1211
535
  {
1212
- guard: guardIs('approved'),
1213
- target: '#commitJoint',
1214
- actions: rememberCaptainOutput,
536
+ target: 'done',
537
+ actions: 'completeWithInvalidReviewOutput',
1215
538
  },
539
+ ],
540
+ onError: [
1216
541
  {
1217
- guard: guardIs('needsRevision'),
1218
- target: '#respondToReview',
1219
- actions: [
1220
- rememberCaptainOutput,
1221
- assign({ reviewSubject: () => 'changes' }),
1222
- ],
542
+ guard: 'authoredReviewFailure',
543
+ target: 'done',
544
+ actions: 'completeWithReviewFailure',
1223
545
  },
1224
- ]),
1225
- onError: captainError,
546
+ { target: 'failed', actions: 'rememberActorError' },
547
+ ],
1226
548
  },
1227
549
  },
1228
- adjudicateChallenges: {
1229
- id: 'adjudicateChallenges',
1230
- description: stateDescriptions.adjudicateChallenges,
1231
- meta: playbookMeta('adjudicateChallenges'),
550
+ runIrTask: {
551
+ id: 'runIrTask',
552
+ description: STATE_DESCRIPTIONS.runIrTask,
553
+ meta: playbookMeta('runIrTask', 'Coder'),
1232
554
  tags: ['playbook.busy'],
1233
555
  invoke: {
1234
556
  src: 'player',
1235
557
  input: ({ context }) => ({
1236
- stateId: 'adjudicateChallenges',
1237
- player: 'Reviewer',
1238
- sourceItem: 'CODE-14',
1239
- ...bossReplyInputFields(context),
1240
- reviews: context.reviews,
1241
- challenges: context.challenges,
1242
- prompt: [
1243
- 'For each rebuttal below, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
1244
- 'Do not edit files or commit; report findings only.',
1245
- ].join('\n'),
1246
- result: {
1247
- challengeAccepted: 'Reviewer accepted the rebuttal no further review edits are required.',
1248
- challengeRejected: 'Reviewer rejected the rebuttal — Coder must respond again.',
1249
- noOpenItems: 'No review items remain open.',
1250
- needsBossReply: needsBossReplyDescription,
1251
- },
558
+ stateId: 'runIrTask',
559
+ player: 'Coder',
560
+ sourceItem: 'CODE-3',
561
+ prompt: IR_TASK_PROMPT,
562
+ result: IR_TASK_RESULTS,
563
+ callerInput: context.callerInput ?? '',
564
+ runResults: context.runResults,
565
+ coderPlayer: context.coderPlayer,
566
+ ...(context.irNumber === undefined
567
+ ? {}
568
+ : { irNumber: context.irNumber }),
569
+ ...(context.irTask === undefined ? {} : { irTask: context.irTask }),
570
+ ...(context.pendingBossQuestion === undefined
571
+ ? {}
572
+ : { pendingBossQuestion: context.pendingBossQuestion }),
573
+ ...(context.bossReply === undefined
574
+ ? {}
575
+ : { bossReply: context.bossReply }),
1252
576
  }),
1253
- onDone: withNeedsBossReplyTransition('adjudicateChallenges', [
577
+ onDone: [
1254
578
  {
1255
- guard: ({ context, event }) => (outputOf(event)?.guard === 'challengeAccepted' ||
1256
- outputOf(event)?.guard === 'noOpenItems') &&
1257
- context.reviewSubject === 'changes',
1258
- target: '#commitJoint',
1259
- actions: rememberCaptainOutput,
579
+ guard: 'isMoreTasks',
580
+ target: 'reviewIrTask',
581
+ actions: 'rememberMoreTasks',
1260
582
  },
1261
583
  {
1262
- guard: ({ context, event }) => (outputOf(event)?.guard === 'challengeAccepted' ||
1263
- outputOf(event)?.guard === 'noOpenItems') &&
1264
- context.reviewSubject === 'commit' &&
1265
- context.afterReview === 'continueIr',
1266
- target: '#continueIr',
1267
- actions: rememberCaptainOutput,
584
+ guard: 'isFinalTask',
585
+ target: 'reviewIrTask',
586
+ actions: 'rememberFinalTask',
1268
587
  },
1269
588
  {
1270
- guard: ({ context, event }) => (outputOf(event)?.guard === 'challengeAccepted' ||
1271
- outputOf(event)?.guard === 'noOpenItems') &&
1272
- context.reviewSubject === 'commit' &&
1273
- context.afterReview === 'summarizeSpecs',
1274
- target: '#summarizeSpecs',
1275
- actions: rememberCaptainOutput,
589
+ guard: 'needsBossReply',
590
+ target: 'awaitBossReply',
591
+ actions: 'rememberPendingQuestion',
1276
592
  },
1277
593
  {
1278
- guard: ({ context, event }) => (outputOf(event)?.guard === 'challengeAccepted' ||
1279
- outputOf(event)?.guard === 'noOpenItems') &&
1280
- context.reviewSubject === 'commit' &&
1281
- context.afterReview === 'done',
1282
- target: '#done',
1283
- actions: rememberCaptainOutput,
594
+ target: 'failed',
595
+ actions: 'rememberMalformedPlayerOutput',
1284
596
  },
1285
- {
1286
- guard: guardIs('challengeRejected'),
1287
- target: '#respondToReview',
1288
- actions: rememberCaptainOutput,
1289
- },
1290
- ]),
1291
- onError: captainError,
597
+ ],
598
+ onError: { target: 'failed', actions: 'rememberActorError' },
1292
599
  },
1293
600
  },
1294
- commitCoderInitial: {
1295
- id: 'commitCoderInitial',
1296
- description: stateDescriptions.commitCoderInitial,
1297
- meta: playbookMeta('commitCoderInitial'),
1298
- tags: ['playbook.busy'],
601
+ reviewIrTask: {
602
+ id: 'reviewIrTask',
603
+ description: STATE_DESCRIPTIONS.reviewIrTask,
604
+ meta: playbookMeta('reviewIrTask'),
605
+ tags: ['playbook.suspended'],
1299
606
  invoke: {
1300
- src: 'player',
607
+ src: 'playbook',
1301
608
  input: ({ context }) => ({
1302
- stateId: 'commitCoderInitial',
1303
- player: 'Committer',
1304
- sourceItem: 'CODE-18',
1305
- ...bossReplyInputFields(context),
1306
- coderPlayer: context.coderPlayer,
1307
- committerPlayer: context.committerPlayer,
1308
- prompt: [
1309
- 'Make a commit of the changes that belong in the repo, following @specs/packages/git.md (reread if necessary).',
1310
- "If that spec is absent, follow the legacy @specs/dev/git.md; if neither exists, follow the repository's existing commit conventions and do not search elsewhere.",
1311
- 'Write the commit message concisely.',
1312
- 'Coder is <coder-llm>.',
1313
- 'Format the `Co-authored-by` `<model>` token as the conventional human form of the substituted id (e.g., `claude-opus-4-7` → `Claude-Opus-4.7`, `gpt-5.5` → `GPT-5.5`).',
1314
- ].join('\n'),
1315
- result: {
1316
- committedSpecs: 'Committed changes that touch only spec item files (@specs/packages/, @specs/compositions/, or legacy @specs/{user,dev,test}/).',
1317
- committedCode: 'Committed changes that touch only files that are not spec item files (any other files, including @specs/ decision, intent, or legacy iteration records, @specs/map.md, and @specs/meta.md).',
1318
- committedMixed: 'Committed changes that span both spec item files and other files.',
1319
- noRelevantChanges: 'There are no relevant changes to commit.',
1320
- needsBossInput: 'Committing requires additional Boss input.',
1321
- needsBossReply: needsBossReplyDescription,
1322
- },
609
+ stateId: 'reviewIrTask',
610
+ sourceItem: 'CODE-4',
611
+ playbookId: 'review',
612
+ text: irTaskReviewText(context),
1323
613
  }),
1324
- onDone: withNeedsBossReplyTransition('commitCoderInitial', [
1325
- {
1326
- guard: guardAndOrigin('committedSpecs', 'bossIntent'),
1327
- target: '#reviewBossCommitSpecs',
1328
- actions: rememberCaptainOutput,
1329
- },
1330
- {
1331
- guard: guardAndOrigin('committedCode', 'bossIntent'),
1332
- target: '#reviewBossCommitCode',
1333
- actions: rememberCaptainOutput,
1334
- },
1335
- {
1336
- guard: guardAndOrigin('committedMixed', 'bossIntent'),
1337
- target: '#reviewBossCommitMixed',
1338
- actions: rememberCaptainOutput,
1339
- },
1340
- {
1341
- guard: guardAndOrigin('committedSpecs', 'irTask'),
1342
- target: '#reviewIrTaskCommitSpecs',
1343
- actions: rememberCaptainOutput,
1344
- },
614
+ onDone: [
1345
615
  {
1346
- guard: guardAndOrigin('committedCode', 'irTask'),
1347
- target: '#reviewIrTaskCommitCode',
1348
- actions: rememberCaptainOutput,
616
+ guard: 'reviewApprovedMoreTasks',
617
+ target: 'runIrTask',
618
+ actions: 'advanceToNextIrTask',
1349
619
  },
1350
620
  {
1351
- guard: guardAndOrigin('committedMixed', 'irTask'),
1352
- target: '#reviewIrTaskCommitMixed',
1353
- actions: rememberCaptainOutput,
621
+ guard: 'reviewApprovedFinalTask',
622
+ target: 'done',
623
+ actions: 'completeSuccessfully',
1354
624
  },
1355
625
  {
1356
- guard: guardIs('noRelevantChanges'),
1357
- target: '#ready',
1358
- actions: rememberCaptainOutput,
626
+ target: 'done',
627
+ actions: 'completeWithInvalidReviewOutput',
1359
628
  },
629
+ ],
630
+ onError: [
1360
631
  {
1361
- guard: guardIs('needsBossInput'),
1362
- target: '#ready',
1363
- actions: rememberCaptainOutput,
632
+ guard: 'authoredReviewFailure',
633
+ target: 'done',
634
+ actions: 'completeWithReviewFailure',
1364
635
  },
1365
- ]),
1366
- onError: captainError,
636
+ { target: 'failed', actions: 'rememberActorError' },
637
+ ],
1367
638
  },
1368
639
  },
1369
- commitJoint: {
1370
- id: 'commitJoint',
1371
- description: stateDescriptions.commitJoint,
1372
- meta: playbookMeta('commitJoint'),
1373
- tags: ['playbook.busy'],
1374
- invoke: {
1375
- src: 'player',
1376
- input: ({ context }) => ({
1377
- stateId: 'commitJoint',
1378
- player: 'Committer',
1379
- sourceItem: 'CODE-19',
1380
- ...bossReplyInputFields(context),
1381
- coderPlayer: context.coderPlayer,
1382
- reviewerPlayer: context.reviewerPlayer,
1383
- committerPlayer: context.committerPlayer,
1384
- prompt: [
1385
- 'Make a commit of the changes that belong in the repo, following @specs/packages/git.md (reread if necessary).',
1386
- "If that spec is absent, follow the legacy @specs/dev/git.md; if neither exists, follow the repository's existing commit conventions and do not search elsewhere.",
1387
- 'Write the commit message concisely.',
1388
- 'Coder is <coder-llm>; Reviewer is <reviewer-llm>.',
1389
- 'Format the `Co-authored-by` `<model>` token as the conventional human form of the substituted id (e.g., `claude-opus-4-7` → `Claude-Opus-4.7`, `gpt-5.5` → `GPT-5.5`).',
1390
- ].join('\n'),
1391
- result: {
1392
- committed: 'Relevant changes were committed.',
1393
- noRelevantChanges: 'There are no relevant changes to commit.',
1394
- needsBossInput: 'Committing requires additional Boss input.',
1395
- needsBossReply: needsBossReplyDescription,
1396
- },
1397
- }),
1398
- onDone: withNeedsBossReplyTransition('commitJoint', [
1399
- {
1400
- guard: ({ context, event }) => outputOf(event)?.guard === 'committed' &&
1401
- context.afterReview === 'continueIr',
1402
- target: '#continueIr',
1403
- actions: rememberCaptainOutput,
1404
- },
1405
- {
1406
- guard: ({ context, event }) => outputOf(event)?.guard === 'committed' &&
1407
- context.afterReview === 'summarizeSpecs',
1408
- target: '#summarizeSpecs',
1409
- actions: rememberCaptainOutput,
1410
- },
640
+ awaitBossReply: {
641
+ id: 'awaitBossReply',
642
+ description: STATE_DESCRIPTIONS.awaitBossReply,
643
+ meta: playbookMeta('awaitBossReply'),
644
+ tags: ['playbook.parked'],
645
+ on: {
646
+ BOSS_REPLY: [
1411
647
  {
1412
- guard: ({ context, event }) => outputOf(event)?.guard === 'committed' &&
1413
- context.afterReview === 'done',
1414
- target: '#done',
1415
- actions: rememberCaptainOutput,
648
+ guard: 'emptyBossReply',
649
+ target: '#failed',
650
+ actions: 'rememberEmptyBossReplyError',
1416
651
  },
1417
652
  {
1418
- guard: guardIs('noRelevantChanges'),
1419
- target: '#done',
1420
- actions: rememberCaptainOutput,
653
+ guard: 'resumesFirstPhase',
654
+ target: '#runFirstPhase',
655
+ reenter: true,
656
+ actions: 'rememberBossReply',
1421
657
  },
1422
658
  {
1423
- guard: guardIs('needsBossInput'),
1424
- target: '#ready',
1425
- actions: rememberCaptainOutput,
659
+ guard: 'resumesIrTask',
660
+ target: '#runIrTask',
661
+ reenter: true,
662
+ actions: 'rememberBossReply',
1426
663
  },
1427
- ]),
1428
- onError: captainError,
664
+ ],
665
+ START_CODE: { target: 'runFirstPhase', actions: 'startCoding' },
1429
666
  },
1430
667
  },
1431
668
  failed: {
1432
669
  id: 'failed',
1433
- description: stateDescriptions.failed,
670
+ description: STATE_DESCRIPTIONS.failed,
1434
671
  meta: playbookMeta('failed'),
1435
672
  tags: ['playbook.parked'],
1436
- on: readyEvents,
673
+ on: {
674
+ START_CODE: { target: 'runFirstPhase', actions: 'startCoding' },
675
+ },
1437
676
  },
1438
677
  done: {
1439
678
  id: 'done',
1440
- description: stateDescriptions.done,
679
+ description: STATE_DESCRIPTIONS.done,
1441
680
  meta: playbookMeta('done'),
1442
681
  type: 'final',
1443
682
  },
1444
683
  },
1445
684
  });
685
+ export default codingMachine;