@sublang/playbook 11.0.0 → 12.1.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 (49) hide show
  1. package/docs/cli.md +9 -6
  2. package/docs/configuration.md +15 -1
  3. package/docs/embedding.md +83 -1
  4. package/package.json +28 -3
  5. package/reference/sdlc/code.playbook/bin/repository-effects.js +501 -170
  6. package/reference/sdlc/code.playbook/bin/session-store.js +4 -1
  7. package/reference/sdlc/code.playbook/code.fsm.d.ts +22 -19
  8. package/reference/sdlc/code.playbook/code.fsm.js +116 -52
  9. package/reference/sdlc/code.playbook/code.fsm.ts +149 -64
  10. package/reference/sdlc/code.playbook/code.gears.md +40 -20
  11. package/reference/sdlc/code.playbook/code.playbook.js +23 -2
  12. package/reference/sdlc/code.playbook/code.playbook.ts +23 -2
  13. package/reference/sdlc/code.playbook/host-capabilities.d.ts +291 -0
  14. package/reference/sdlc/code.playbook/host-capabilities.js +40 -0
  15. package/reference/sdlc/code.playbook/playbook.config.template.yaml +18 -2
  16. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +13 -6
  17. package/reference/sdlc/decide.playbook/decide.fsm.js +54 -27
  18. package/reference/sdlc/decide.playbook/decide.fsm.ts +68 -29
  19. package/reference/sdlc/decide.playbook/decide.gears.md +25 -19
  20. package/reference/sdlc/decide.playbook/decide.playbook.js +11 -3
  21. package/reference/sdlc/decide.playbook/decide.playbook.ts +11 -3
  22. package/reference/sdlc/decide.playbook/decide.registry.js +1 -1
  23. package/reference/sdlc/decide.playbook/decide.registry.ts +1 -1
  24. package/reference/sdlc/dev.md +52 -0
  25. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +261 -0
  26. package/reference/sdlc/dev.playbook/dev.fsm.js +723 -0
  27. package/reference/sdlc/dev.playbook/dev.fsm.ts +988 -0
  28. package/reference/sdlc/dev.playbook/dev.gears.md +91 -0
  29. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +21 -0
  30. package/reference/sdlc/dev.playbook/dev.playbook.js +143 -0
  31. package/reference/sdlc/dev.playbook/dev.playbook.ts +246 -0
  32. package/reference/sdlc/dev.playbook/dev.registry.d.ts +40 -0
  33. package/reference/sdlc/dev.playbook/dev.registry.js +64 -0
  34. package/reference/sdlc/dev.playbook/dev.registry.ts +120 -0
  35. package/reference/sdlc/review.playbook/review.fsm.d.ts +15 -2
  36. package/reference/sdlc/review.playbook/review.fsm.js +77 -27
  37. package/reference/sdlc/review.playbook/review.fsm.ts +96 -30
  38. package/reference/sdlc/review.playbook/review.gears.md +52 -26
  39. package/reference/sdlc/review.playbook/review.playbook.js +17 -7
  40. package/reference/sdlc/review.playbook/review.playbook.ts +17 -7
  41. package/reference/sdlc/review.playbook/review.registry.js +1 -1
  42. package/reference/sdlc/review.playbook/review.registry.ts +1 -1
  43. package/slc/gears2fsm.md +20 -0
  44. package/slc/link.md +28 -5
  45. package/slc/text2gears.md +21 -1
  46. package/src/xstate-playbook-runtime.js +5 -2
  47. package/src/xstate-playbook-runtime.ts +5 -2
  48. package/src/xstate-runtime.js +13 -1
  49. package/src/xstate-runtime.ts +13 -1
@@ -6,7 +6,8 @@
6
6
  // Role metadata: Coder→coder (canonical local role)
7
7
  // Boss event: deterministic START_CODE entry; exact Boss text becomes
8
8
  // callerInput; pending Coder questions retain BOSS_REPLY
9
- // Adjudication: LLM judge per player state; coderOutput is carried verbatim
9
+ // Adjudication: LLM judge per player state; coderOutput is carried verbatim;
10
+ // the first phase decides among four semantic outcomes
10
11
  // Nested call: literal review target through the shared bridge
11
12
  // Compat: artifact schema 3 / runtime ABI 1
12
13
 
@@ -195,7 +196,7 @@ const runtimeSpec = {
195
196
  runFirstPhase: {
196
197
  role: 'coder',
197
198
  label:
198
- 'Coder is implementing one direct phase or committing a new intent record.',
199
+ 'Coder is running the first coding phase: a direct implementation, a new intent record, or an existing intent-record task.',
199
200
  },
200
201
  runIrTask: {
201
202
  role: 'coder',
@@ -213,6 +214,23 @@ const runtimeSpec = {
213
214
  repositoryDisposition: 'one-descendant-commit',
214
215
  },
215
216
  irCommit: {
217
+ fields: {
218
+ coderOutput: 'presentation',
219
+ latestCommit: 'effect',
220
+ irNumber: 'semantic',
221
+ },
222
+ repositoryDisposition: 'one-descendant-commit',
223
+ },
224
+ moreTasks: {
225
+ fields: {
226
+ coderOutput: 'presentation',
227
+ latestCommit: 'effect',
228
+ irNumber: 'semantic',
229
+ irTask: 'semantic',
230
+ },
231
+ repositoryDisposition: 'one-descendant-commit',
232
+ },
233
+ finalTask: {
216
234
  fields: {
217
235
  coderOutput: 'presentation',
218
236
  latestCommit: 'effect',
@@ -231,6 +249,7 @@ const runtimeSpec = {
231
249
  fields: {
232
250
  coderOutput: 'presentation',
233
251
  latestCommit: 'effect',
252
+ irNumber: 'semantic',
234
253
  irTask: 'semantic',
235
254
  },
236
255
  repositoryDisposition: 'one-descendant-commit',
@@ -239,6 +258,8 @@ const runtimeSpec = {
239
258
  fields: {
240
259
  coderOutput: 'presentation',
241
260
  latestCommit: 'effect',
261
+ irNumber: 'semantic',
262
+ irTask: 'semantic',
242
263
  },
243
264
  repositoryDisposition: 'one-descendant-commit',
244
265
  },
@@ -0,0 +1,291 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ export type JsonValue =
5
+ | null
6
+ | boolean
7
+ | number
8
+ | string
9
+ | readonly JsonValue[]
10
+ | { readonly [key: string]: JsonValue };
11
+
12
+ export type PlaybookRepositoryDisposition =
13
+ | 'unchanged'
14
+ | 'one-descendant-commit'
15
+ | 'deferred';
16
+ export type RepositoryReceiptClassification =
17
+ | 'unchanged'
18
+ | 'one-descendant-commit'
19
+ | 'multiple-commits'
20
+ | 'rewritten-or-non-descendant'
21
+ | 'worktree-only-change'
22
+ | 'concurrent-or-foreign-change'
23
+ | 'observation-ambiguous';
24
+ export declare const REPOSITORY_RECEIPT_CLASSIFICATIONS: readonly RepositoryReceiptClassification[];
25
+
26
+ export interface RepositoryIdentity {
27
+ readonly worktree: string;
28
+ readonly gitDir: string;
29
+ }
30
+ export interface PlaybookRepositoryObservation extends RepositoryIdentity {
31
+ readonly head: string;
32
+ readonly projection: Readonly<Record<string, JsonValue>>;
33
+ readonly projectionDigest: string;
34
+ }
35
+ export interface PlaybookRepositoryReceipt {
36
+ readonly classification: RepositoryReceiptClassification;
37
+ readonly baseline: PlaybookRepositoryObservation;
38
+ readonly after?: PlaybookRepositoryObservation;
39
+ readonly commitOid?: string;
40
+ }
41
+ export interface RepositoryReceiptOptions {
42
+ readonly allowedDispositions: readonly PlaybookRepositoryDisposition[];
43
+ }
44
+
45
+ export declare function observeGitRepository(
46
+ cwd: string,
47
+ ): Promise<PlaybookRepositoryObservation>;
48
+ export declare function classifyRepositoryReceipt(
49
+ baseline: PlaybookRepositoryObservation,
50
+ after: PlaybookRepositoryObservation,
51
+ options: RepositoryReceiptOptions,
52
+ ): Promise<PlaybookRepositoryReceipt>;
53
+ export declare function captureRepositoryReceipt(
54
+ baseline: PlaybookRepositoryObservation,
55
+ options: RepositoryReceiptOptions,
56
+ ): Promise<PlaybookRepositoryReceipt>;
57
+
58
+ export interface PlaybookPendingBossQuestion {
59
+ readonly questionId: string;
60
+ readonly asker:
61
+ | { readonly kind: 'captain' }
62
+ | { readonly kind: 'role'; readonly roleId: string };
63
+ readonly question: string;
64
+ readonly sourceItem?: string;
65
+ }
66
+ export interface PlaybookEffectBoundary {
67
+ readonly sequence: number;
68
+ readonly boundaryId: string;
69
+ readonly attemptId: string;
70
+ readonly attemptNumber: number;
71
+ readonly playbookId: string;
72
+ readonly runtimeSessionId: string;
73
+ readonly turnId: number;
74
+ readonly callId: string;
75
+ readonly roleId: string;
76
+ readonly sourceStateId: string;
77
+ readonly sourceOutcomeSchema: JsonValue;
78
+ readonly dispositions: readonly PlaybookRepositoryDisposition[];
79
+ readonly canonicalWorktree: RepositoryIdentity;
80
+ readonly baseline: PlaybookRepositoryObservation;
81
+ readonly after?: PlaybookRepositoryObservation;
82
+ readonly physicalReceipt?: PlaybookRepositoryReceipt;
83
+ readonly finalText?: string;
84
+ readonly semanticCandidate?: JsonValue;
85
+ readonly initialSemanticCandidate?: JsonValue;
86
+ readonly correctionBudget: {
87
+ readonly limit: 1;
88
+ readonly spent: boolean;
89
+ };
90
+ readonly cohortId?: string;
91
+ readonly logicalOperationId?: string;
92
+ }
93
+ export type PlaybookEffectBoundaryStart = Omit<
94
+ PlaybookEffectBoundary,
95
+ | 'sequence'
96
+ | 'attemptId'
97
+ | 'attemptNumber'
98
+ | 'after'
99
+ | 'physicalReceipt'
100
+ | 'finalText'
101
+ | 'semanticCandidate'
102
+ | 'initialSemanticCandidate'
103
+ >;
104
+ export type EffectBoundarySeed = Omit<
105
+ PlaybookEffectBoundaryStart,
106
+ 'playbookId' | 'canonicalWorktree' | 'baseline' | 'cohortId'
107
+ >;
108
+ export interface PlaybookEffectLogicalOperation {
109
+ readonly sequence: number;
110
+ readonly operationId: string;
111
+ readonly playbookId: string;
112
+ readonly runtimeSessionId: string;
113
+ readonly boundaryIds: readonly string[];
114
+ readonly originalBaseline: PlaybookRepositoryObservation;
115
+ readonly checkpoint?: PlaybookRepositoryObservation;
116
+ readonly pendingQuestion?: PlaybookPendingBossQuestion;
117
+ readonly playerContinuation?: JsonValue;
118
+ readonly checkpointRestorationEligible: boolean;
119
+ readonly logicalReceipt?: PlaybookRepositoryReceipt;
120
+ }
121
+ export interface PlaybookEffectLedger {
122
+ readonly schemaVersion: 1;
123
+ readonly revision: number;
124
+ readonly boundaries: readonly PlaybookEffectBoundary[];
125
+ readonly logicalOperations: readonly PlaybookEffectLogicalOperation[];
126
+ }
127
+ export type PlaybookEffectLedgerCommand =
128
+ | {
129
+ readonly kind: 'start-boundaries';
130
+ readonly boundaries: readonly [
131
+ PlaybookEffectBoundaryStart,
132
+ ...PlaybookEffectBoundaryStart[],
133
+ ];
134
+ }
135
+ | {
136
+ readonly kind: 'replace-boundaries';
137
+ readonly replacements: readonly [
138
+ {
139
+ readonly expected: PlaybookEffectBoundary;
140
+ readonly next: PlaybookEffectBoundary;
141
+ },
142
+ ...{
143
+ readonly expected: PlaybookEffectBoundary;
144
+ readonly next: PlaybookEffectBoundary;
145
+ }[],
146
+ ];
147
+ }
148
+ | {
149
+ readonly kind: 'append-logical-operations';
150
+ readonly operations: readonly [
151
+ Omit<PlaybookEffectLogicalOperation, 'sequence'>,
152
+ ...Omit<PlaybookEffectLogicalOperation, 'sequence'>[],
153
+ ];
154
+ }
155
+ | {
156
+ readonly kind: 'replace-logical-operations';
157
+ readonly replacements: readonly [
158
+ {
159
+ readonly expected: PlaybookEffectLogicalOperation;
160
+ readonly next: PlaybookEffectLogicalOperation;
161
+ },
162
+ ...{
163
+ readonly expected: PlaybookEffectLogicalOperation;
164
+ readonly next: PlaybookEffectLogicalOperation;
165
+ }[],
166
+ ];
167
+ };
168
+ export type PlaybookEffectLedgerCommandBatch = readonly [
169
+ PlaybookEffectLedgerCommand,
170
+ ...PlaybookEffectLedgerCommand[],
171
+ ];
172
+ export interface PlaybookEffectLedgerCapability {
173
+ snapshot(): PlaybookEffectLedger;
174
+ writeAhead(
175
+ commands: PlaybookEffectLedgerCommandBatch,
176
+ ): Promise<PlaybookEffectLedger>;
177
+ }
178
+
179
+ interface RepositoryOperationSettlement<T> {
180
+ readonly status: 'fulfilled';
181
+ readonly value: T;
182
+ }
183
+ interface RepositoryOperationRejection {
184
+ readonly status: 'rejected';
185
+ readonly reason: unknown;
186
+ }
187
+ export interface RepositoryExclusiveCompletion<T> {
188
+ readonly boundary: PlaybookEffectBoundary;
189
+ readonly operation:
190
+ | RepositoryOperationSettlement<T>
191
+ | RepositoryOperationRejection;
192
+ readonly receipt: PlaybookRepositoryReceipt;
193
+ readonly outcomeReceipt: PlaybookRepositoryReceipt;
194
+ }
195
+ interface RepositoryDeferredBinding {
196
+ readonly operationId: string;
197
+ readonly pendingQuestion: PlaybookPendingBossQuestion;
198
+ readonly playerContinuation: JsonValue;
199
+ }
200
+ export interface RepositoryCompletionEvidence {
201
+ readonly finalText?: string;
202
+ readonly semanticCandidate?: JsonValue;
203
+ readonly deferred?: RepositoryDeferredBinding;
204
+ readonly unresolved?: true;
205
+ }
206
+ export interface RepositoryExclusiveResult<T> {
207
+ readonly operation:
208
+ | RepositoryOperationSettlement<T>
209
+ | RepositoryOperationRejection;
210
+ readonly receipt: PlaybookRepositoryReceipt;
211
+ readonly effectLedger: PlaybookEffectLedger;
212
+ readonly deferredStatus?: 'bound' | 'unresolved';
213
+ }
214
+ interface RepositoryDeferredContinuationResult<T> {
215
+ readonly status: 'continued';
216
+ readonly operation:
217
+ | RepositoryOperationSettlement<T>
218
+ | RepositoryOperationRejection;
219
+ readonly receipt: PlaybookRepositoryReceipt;
220
+ readonly logicalReceipt?: PlaybookRepositoryReceipt;
221
+ readonly effectLedger: PlaybookEffectLedger;
222
+ readonly deferredStatus?: 'bound' | 'unresolved';
223
+ }
224
+ interface RepositoryDeferredCheckpointMismatch {
225
+ readonly status: 'checkpoint-mismatch' | 'ineligible';
226
+ readonly effectLedger: PlaybookEffectLedger;
227
+ }
228
+ interface RepositoryDeferredParked {
229
+ readonly status: 'parked';
230
+ readonly effectLedger: PlaybookEffectLedger;
231
+ }
232
+ interface RepositoryDeferredRestoreResult {
233
+ readonly status: 'restored' | 'checkpoint-mismatch' | 'ineligible';
234
+ readonly effectLedger: PlaybookEffectLedger;
235
+ }
236
+ export interface RepositoryCapability {
237
+ runExclusive<T>(options: {
238
+ readonly signal?: AbortSignal;
239
+ readonly effectBoundary: EffectBoundarySeed;
240
+ readonly operation: (context: {
241
+ readonly baseline: PlaybookRepositoryObservation;
242
+ readonly identity: RepositoryIdentity;
243
+ }) => Promise<T>;
244
+ readonly completeEffectBoundary: (
245
+ completion: RepositoryExclusiveCompletion<T>,
246
+ ) => RepositoryCompletionEvidence | Promise<RepositoryCompletionEvidence>;
247
+ }): Promise<RepositoryExclusiveResult<T>>;
248
+ runDeferred<T>(options: {
249
+ readonly mode: 'continue';
250
+ readonly signal?: AbortSignal;
251
+ readonly operationId: string;
252
+ readonly effectBoundary: EffectBoundarySeed;
253
+ readonly operation: (context: {
254
+ readonly baseline: PlaybookRepositoryObservation;
255
+ readonly identity: RepositoryIdentity;
256
+ readonly playerContinuation: JsonValue;
257
+ }) => Promise<T>;
258
+ readonly completeEffectBoundary: (
259
+ completion: RepositoryExclusiveCompletion<T>,
260
+ ) => RepositoryCompletionEvidence | Promise<RepositoryCompletionEvidence>;
261
+ }): Promise<
262
+ RepositoryDeferredContinuationResult<T> | RepositoryDeferredCheckpointMismatch
263
+ >;
264
+ runDeferred(options: {
265
+ readonly mode: 'park' | 'restore';
266
+ readonly signal?: AbortSignal;
267
+ readonly operationId: string;
268
+ }): Promise<RepositoryDeferredParked | RepositoryDeferredRestoreResult>;
269
+ }
270
+ export interface HostCapabilities {
271
+ readonly repository: RepositoryCapability;
272
+ readonly effectLedger: PlaybookEffectLedgerCapability;
273
+ }
274
+ export interface WorktreeRepositoryCapability extends RepositoryCapability {
275
+ readonly identity: RepositoryIdentity;
276
+ observe(): Promise<PlaybookRepositoryObservation>;
277
+ }
278
+ export interface WorktreeHostCapabilities extends HostCapabilities {
279
+ readonly repository: WorktreeRepositoryCapability;
280
+ }
281
+ export interface WorktreeHostCapabilitiesOptions {
282
+ readonly cwd: string;
283
+ readonly playbookId: string;
284
+ readonly requiredRoleIds: readonly string[];
285
+ readonly concurrentRoleSets?: readonly (readonly string[])[];
286
+ readonly effectLedger?: PlaybookEffectLedger;
287
+ }
288
+ export declare function createWorktreeHostCapabilities(
289
+ options: WorktreeHostCapabilitiesOptions,
290
+ ): Promise<WorktreeHostCapabilities>;
291
+ export declare function createFailClosedHostCapabilities(): HostCapabilities;
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ // DR-046: the public `@sublang/playbook/host-capabilities` facade. Every
5
+ // member is the CLI host's own repository-effect implementation; this module
6
+ // only narrows the accepted arguments to the declared surface.
7
+
8
+ import {
9
+ REPOSITORY_RECEIPT_CLASSIFICATIONS,
10
+ captureRepositoryReceipt as capture,
11
+ classifyRepositoryReceipt as classify,
12
+ createFailClosedHostCapabilities,
13
+ createWorktreeHostCapabilities,
14
+ observeGitRepository as observe,
15
+ } from './bin/repository-effects.js';
16
+
17
+ export {
18
+ REPOSITORY_RECEIPT_CLASSIFICATIONS,
19
+ createFailClosedHostCapabilities,
20
+ createWorktreeHostCapabilities,
21
+ };
22
+
23
+ export async function observeGitRepository(cwd) {
24
+ return observe(cwd);
25
+ }
26
+
27
+ export async function classifyRepositoryReceipt(baseline, after, options) {
28
+ return classify(baseline, after, receiptOptions(options));
29
+ }
30
+
31
+ export async function captureRepositoryReceipt(baseline, options) {
32
+ return capture(baseline, receiptOptions(options));
33
+ }
34
+
35
+ function receiptOptions(options) {
36
+ if (options === null || typeof options !== 'object' || Array.isArray(options)) {
37
+ throw new TypeError('repository receipt options must be an object');
38
+ }
39
+ return { allowedDispositions: options.allowedDispositions };
40
+ }
@@ -63,13 +63,24 @@ players:
63
63
  permissions:
64
64
  mode: auto
65
65
 
66
+ # DEV's planner stays a distinct player: equal ids deliberately share one
67
+ # provider conversation, and planning context must not bleed into review
68
+ # conversations (DR-044).
69
+ dev.analyst:
70
+ adapter: claude
71
+ model: claude-opus-5
72
+ effort: xhigh
73
+ permissions:
74
+ mode: auto
75
+
66
76
  # Enabled playbooks. Each is loaded from its explicit `from` module; the
67
77
  # `<id>` key must equal that module's manifest id. `from`, `command`, and
68
78
  # `roles` are launcher-owned; every other key is that playbook's option
69
79
  # slice.
70
80
  #
71
- # Every manifest role is bound explicitly. These three playbooks intentionally
72
- # reuse the same two stable players; change an id to isolate its conversation.
81
+ # Every manifest role is bound explicitly. CODE, REVIEW, and DECIDE
82
+ # intentionally reuse the same two stable players, while DEV keeps its own
83
+ # Analyst; change an id to isolate its conversation.
73
84
  playbooks:
74
85
  code:
75
86
  from: "@sublang/playbook/code/registry"
@@ -87,3 +98,8 @@ playbooks:
87
98
  roles:
88
99
  coder: dev.coder
89
100
  reviewer: dev.reviewer
101
+
102
+ dev:
103
+ from: "@sublang/playbook/dev/registry"
104
+ roles:
105
+ analyst: dev.analyst
@@ -19,7 +19,12 @@ type PendingBossQuestions = Partial<Record<ResumableStateId, PendingBossQuestion
19
19
  type BossReplies = Partial<Record<ResumableStateId, string>>;
20
20
  type PendingBossQuestionParams = Omit<PendingBossQuestion, 'questionId'>;
21
21
  export interface ReviewSuccessOutput {
22
- approvedCommit: 'latest';
22
+ evaluatedRevision: string;
23
+ noUnsettledFindings: true;
24
+ }
25
+ export interface DecideSuccessOutput {
26
+ decideCommit: string;
27
+ evaluatedRevision: string;
23
28
  noUnsettledFindings: true;
24
29
  }
25
30
  export interface DecideFailureOutput {
@@ -31,7 +36,7 @@ export interface DecideFailureOutput {
31
36
  message: string;
32
37
  };
33
38
  }
34
- export type DecideOutput = ReviewSuccessOutput | DecideFailureOutput;
39
+ export type DecideOutput = DecideSuccessOutput | DecideFailureOutput;
35
40
  interface ChildFailure {
36
41
  status: 'aborted' | 'error';
37
42
  playbookId: 'review';
@@ -45,6 +50,7 @@ export interface DecideContext {
45
50
  coderProposal?: string;
46
51
  reviewerProposal?: string;
47
52
  latestCommit?: string;
53
+ coderOutput?: string;
48
54
  reviewResult?: ReviewSuccessOutput;
49
55
  reviewFailure?: ChildFailure;
50
56
  lastResult?: PlayerOutput;
@@ -74,6 +80,7 @@ export interface PlayerInput {
74
80
  result: Record<string, string>;
75
81
  role: Role;
76
82
  callerTopic?: string;
83
+ reviewerProposal?: string;
77
84
  pendingBossQuestion?: PendingBossQuestion;
78
85
  bossReply?: string;
79
86
  }
@@ -139,9 +146,6 @@ export declare const decideMachine: import("xstate").StateMachine<DecideContext,
139
146
  } | {
140
147
  type: "rememberBossReply";
141
148
  params: import("xstate").NonReducibleUnknown;
142
- } | {
143
- type: "clearBossReplyContext";
144
- params: import("xstate").NonReducibleUnknown;
145
149
  } | {
146
150
  type: "copyInterruptedTopic";
147
151
  params: import("xstate").NonReducibleUnknown;
@@ -186,6 +190,9 @@ export declare const decideMachine: import("xstate").StateMachine<DecideContext,
186
190
  } | {
187
191
  type: "clearProposalRoundContext";
188
192
  params: import("xstate").NonReducibleUnknown;
193
+ } | {
194
+ type: "clearBossReplyContext";
195
+ params: import("xstate").NonReducibleUnknown;
189
196
  }, {
190
197
  type: "authoredReviewFailure";
191
198
  params: unknown;
@@ -212,7 +219,7 @@ export declare const decideMachine: import("xstate").StateMachine<DecideContext,
212
219
  coder: "complete" | "working" | "waiting";
213
220
  reviewer: "complete" | "working" | "waiting";
214
221
  };
215
- }, string, Readonly<Record<string, never>>, ReviewSuccessOutput | DecideFailureOutput, import("xstate").EventObject, import("xstate").MetaObject, {
222
+ }, string, Readonly<Record<string, never>>, DecideSuccessOutput | DecideFailureOutput, import("xstate").EventObject, import("xstate").MetaObject, {
216
223
  id: "decide";
217
224
  states: {
218
225
  readonly ready: {
@@ -15,30 +15,40 @@ const INDEPENDENT_PROPOSAL_PROMPT = [
15
15
  'Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.',
16
16
  'Do not change any files.',
17
17
  ].join('\n');
18
- const COMMIT_CODER_PROMPT = [
19
- 'Turn your proposal into the necessary spec items or DRs.',
18
+ const SYNTHESIZE_COMMIT_PROMPT = [
19
+ "Synthesize your independent proposal with Reviewer's proposal below.",
20
+ 'Keep to the original topic below and follow what it asks.',
21
+ 'Keep the best, essential parts of either proposal and reject any point that is unsound, unnecessary, or outside the topic.',
22
+ 'Turn the resulting design into the necessary DRs and/or spec items.',
20
23
  'Follow @specs/meta.md and update @specs/map.md when needed.',
21
- "Do not inspect or incorporate Reviewer's proposal before this commit.",
22
- 'Do not change code or implement the proposal.',
24
+ 'Do not change code or implement the design.',
23
25
  '',
24
26
  'Commit the result as one new commit, following @specs/packages/git.md.',
25
27
  'Make the commit message explain concisely what changed and why.',
26
- 'Coder is <coder-llm>; format the model token in conventional human form.',
28
+ 'Identify every new commit you make.',
29
+ 'Coder is <coder-llm> and Reviewer is <reviewer-llm>.',
30
+ '',
31
+ '> Original topic: <caller-topic>',
32
+ "> Reviewer's independent proposal: <reviewer-proposal>",
27
33
  ].join('\n');
28
34
  const REVIEW_INPUT_TEMPLATE = [
29
- 'Review the latest commit as a spec-design change against the initial intent.',
30
- 'Compare it with your independent proposal and take the best of both.',
31
- 'Make your suggestions.',
32
- '',
33
- 'Initial intent: <caller-topic>.',
34
- "Coder's independent proposal: <coder-proposal>.",
35
+ '> Original intent: <caller-topic>',
36
+ '> Review scope: the `decide`-owned commit <decide-commit> and its resulting repository state.',
37
+ '> Coder output: <coder-output>',
35
38
  ].join('\n');
36
- function composeReviewInput(callerTopic, coderProposal) {
39
+ function composeReviewInput(callerTopic, decideCommit, coderOutput) {
37
40
  const fields = {
38
41
  '<caller-topic>': callerTopic ?? '',
39
- '<coder-proposal>': coderProposal ?? '',
42
+ '<decide-commit>': decideCommit ?? '',
43
+ '<coder-output>': coderOutput ?? '',
40
44
  };
41
- return REVIEW_INPUT_TEMPLATE.replace(/<caller-topic>|<coder-proposal>/g, (placeholder) => fields[placeholder]);
45
+ return REVIEW_INPUT_TEMPLATE.replace(/<caller-topic>|<decide-commit>|<coder-output>/g, (placeholder, offset, source) => {
46
+ const value = fields[placeholder];
47
+ const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
48
+ return source.slice(lineStart, offset).startsWith('> ')
49
+ ? value.replaceAll('\n', '\n> ')
50
+ : value;
51
+ });
42
52
  }
43
53
  const outputOf = (event) => event.output;
44
54
  const playbookOutputOf = (event) => event.output;
@@ -170,19 +180,23 @@ function isPlaybookState(value) {
170
180
  value.activeStateIds.length === 1 &&
171
181
  value.activeStateIds[0] === value.stateId)));
172
182
  }
183
+ // DECIDE supplied `review` a scope rooted at the `decide`-owned commit, so a
184
+ // result applies to that scope only when it is the exact declared terminal
185
+ // contract: the evaluated repository revision plus the affirmative
186
+ // no-unsettled-findings fact. Any other shape does not establish those facts.
173
187
  function reviewSuccessFrom(event) {
174
188
  const output = playbookOutputOf(event);
175
189
  if (!isPlainRecord(output))
176
190
  return undefined;
177
- if (!isExactKeys(output, ['approvedCommit', 'noUnsettledFindings'])) {
191
+ if (!isExactKeys(output, ['evaluatedRevision', 'noUnsettledFindings'])) {
178
192
  return undefined;
179
193
  }
180
- if (output.approvedCommit !== 'latest' ||
194
+ if (!isNonEmptyString(output.evaluatedRevision) ||
181
195
  output.noUnsettledFindings !== true) {
182
196
  return undefined;
183
197
  }
184
198
  return {
185
- approvedCommit: 'latest',
199
+ evaluatedRevision: output.evaluatedRevision,
186
200
  noUnsettledFindings: true,
187
201
  };
188
202
  }
@@ -303,6 +317,7 @@ export const decideMachine = setup({
303
317
  coderProposal: undefined,
304
318
  reviewerProposal: undefined,
305
319
  latestCommit: undefined,
320
+ coderOutput: undefined,
306
321
  reviewResult: undefined,
307
322
  reviewFailure: undefined,
308
323
  lastResult: undefined,
@@ -317,6 +332,7 @@ export const decideMachine = setup({
317
332
  coderProposal: undefined,
318
333
  reviewerProposal: undefined,
319
334
  latestCommit: undefined,
335
+ coderOutput: undefined,
320
336
  reviewResult: undefined,
321
337
  reviewFailure: undefined,
322
338
  lastResult: undefined,
@@ -346,6 +362,7 @@ export const decideMachine = setup({
346
362
  }),
347
363
  rememberCommit: assign({
348
364
  latestCommit: ({ event }) => outputOf(event).latestCommit,
365
+ coderOutput: ({ event }) => outputOf(event).coderOutput,
349
366
  lastResult: ({ event }) => outputOf(event),
350
367
  lastError: undefined,
351
368
  }),
@@ -364,7 +381,7 @@ export const decideMachine = setup({
364
381
  playbookId: 'review',
365
382
  error: {
366
383
  name: 'ReviewProtocolError',
367
- message: 'REVIEW returned without an approved latest commit and a no-findings result.',
384
+ message: 'REVIEW returned without the evaluated repository revision and a no-unsettled-findings result for the supplied scope.',
368
385
  },
369
386
  },
370
387
  lastError: undefined,
@@ -429,8 +446,16 @@ export const decideMachine = setup({
429
446
  initial: 'ready',
430
447
  context: () => ({}),
431
448
  output: ({ context }) => {
432
- if (context.reviewResult)
433
- return context.reviewResult;
449
+ if (context.reviewResult) {
450
+ if (!context.latestCommit) {
451
+ throw new Error('DECIDE completed without its decide-owned commit');
452
+ }
453
+ return {
454
+ decideCommit: context.latestCommit,
455
+ evaluatedRevision: context.reviewResult.evaluatedRevision,
456
+ noUnsettledFindings: true,
457
+ };
458
+ }
434
459
  if (!context.latestCommit || !context.reviewFailure) {
435
460
  throw new Error('DECIDE reached a final state without a result');
436
461
  }
@@ -498,7 +523,7 @@ export const decideMachine = setup({
498
523
  role: 'coder',
499
524
  prompt: INDEPENDENT_PROPOSAL_PROMPT,
500
525
  result: withNeedsBossReply({
501
- proposed: 'Coder completed an independent proposal. Output shall include `coderProposal: <verbatim final text>`.',
526
+ proposed: 'Coder affirmatively provided a complete design proposal; a progress report, status update, or promise of a later proposal supports no proposal outcome. Output shall include `coderProposal: <verbatim final text>`.',
502
527
  }),
503
528
  callerTopic: context.callerTopic,
504
529
  ...bossReplyFields(context, 'askCoderProposal'),
@@ -634,7 +659,7 @@ export const decideMachine = setup({
634
659
  role: 'reviewer',
635
660
  prompt: INDEPENDENT_PROPOSAL_PROMPT,
636
661
  result: withNeedsBossReply({
637
- proposed: 'Reviewer completed an independent proposal. Output shall include `reviewerProposal: <verbatim final text>`.',
662
+ proposed: 'Reviewer affirmatively provided a complete design proposal; a progress report, status update, or promise of a later proposal supports no proposal outcome. Output shall include `reviewerProposal: <verbatim final text>`.',
638
663
  }),
639
664
  callerTopic: context.callerTopic,
640
665
  ...bossReplyFields(context, 'askReviewerProposal'),
@@ -757,11 +782,11 @@ export const decideMachine = setup({
757
782
  commitCoderProposal: {
758
783
  id: 'commitCoderProposal',
759
784
  tags: 'playbook.busy',
760
- description: 'Coder writes and commits Coder’s independent proposal.',
785
+ description: 'Coder synthesizes both proposals and commits the design.',
761
786
  meta: {
762
787
  playbook: {
763
788
  stateId: 'commitCoderProposal',
764
- description: 'Coder writes and commits Coder’s independent proposal.',
789
+ description: 'Coder synthesizes both proposals and commits the design.',
765
790
  role: 'coder',
766
791
  },
767
792
  },
@@ -771,10 +796,12 @@ export const decideMachine = setup({
771
796
  stateId: 'commitCoderProposal',
772
797
  sourceItem: 'DECIDE-3',
773
798
  role: 'coder',
774
- prompt: COMMIT_CODER_PROMPT,
799
+ prompt: SYNTHESIZE_COMMIT_PROMPT,
775
800
  result: withNeedsBossReply({
776
- committed: "Coder committed Coder's proposal. Output shall include `coderOutput: <verbatim final text>` and `latestCommit: <commit identity>`.",
801
+ committed: 'Coder synthesized both proposals and committed the resulting design as one new commit. Output shall include `coderOutput: <verbatim final text>` and `latestCommit: <commit identity>`.',
777
802
  }),
803
+ callerTopic: context.callerTopic,
804
+ reviewerProposal: context.reviewerProposal,
778
805
  ...bossReplyFields(context, 'commitCoderProposal'),
779
806
  }),
780
807
  onDone: [
@@ -881,7 +908,7 @@ export const decideMachine = setup({
881
908
  stateId: 'reviewCommit',
882
909
  sourceItem: 'DECIDE-4',
883
910
  playbookId: 'review',
884
- text: composeReviewInput(context.callerTopic, context.coderProposal),
911
+ text: composeReviewInput(context.callerTopic, context.latestCommit, context.coderOutput),
885
912
  }),
886
913
  onDone: [
887
914
  {