@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.
- package/docs/cli.md +9 -6
- package/docs/configuration.md +15 -1
- package/docs/embedding.md +83 -1
- package/package.json +28 -3
- package/reference/sdlc/code.playbook/bin/repository-effects.js +501 -170
- package/reference/sdlc/code.playbook/bin/session-store.js +4 -1
- package/reference/sdlc/code.playbook/code.fsm.d.ts +22 -19
- package/reference/sdlc/code.playbook/code.fsm.js +116 -52
- package/reference/sdlc/code.playbook/code.fsm.ts +149 -64
- package/reference/sdlc/code.playbook/code.gears.md +40 -20
- package/reference/sdlc/code.playbook/code.playbook.js +23 -2
- package/reference/sdlc/code.playbook/code.playbook.ts +23 -2
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +291 -0
- package/reference/sdlc/code.playbook/host-capabilities.js +40 -0
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +18 -2
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +13 -6
- package/reference/sdlc/decide.playbook/decide.fsm.js +54 -27
- package/reference/sdlc/decide.playbook/decide.fsm.ts +68 -29
- package/reference/sdlc/decide.playbook/decide.gears.md +25 -19
- package/reference/sdlc/decide.playbook/decide.playbook.js +11 -3
- package/reference/sdlc/decide.playbook/decide.playbook.ts +11 -3
- package/reference/sdlc/decide.playbook/decide.registry.js +1 -1
- package/reference/sdlc/decide.playbook/decide.registry.ts +1 -1
- package/reference/sdlc/dev.md +52 -0
- package/reference/sdlc/dev.playbook/dev.fsm.d.ts +261 -0
- package/reference/sdlc/dev.playbook/dev.fsm.js +723 -0
- package/reference/sdlc/dev.playbook/dev.fsm.ts +988 -0
- package/reference/sdlc/dev.playbook/dev.gears.md +91 -0
- package/reference/sdlc/dev.playbook/dev.playbook.d.ts +21 -0
- package/reference/sdlc/dev.playbook/dev.playbook.js +143 -0
- package/reference/sdlc/dev.playbook/dev.playbook.ts +246 -0
- package/reference/sdlc/dev.playbook/dev.registry.d.ts +40 -0
- package/reference/sdlc/dev.playbook/dev.registry.js +64 -0
- package/reference/sdlc/dev.playbook/dev.registry.ts +120 -0
- package/reference/sdlc/review.playbook/review.fsm.d.ts +15 -2
- package/reference/sdlc/review.playbook/review.fsm.js +77 -27
- package/reference/sdlc/review.playbook/review.fsm.ts +96 -30
- package/reference/sdlc/review.playbook/review.gears.md +52 -26
- package/reference/sdlc/review.playbook/review.playbook.js +17 -7
- package/reference/sdlc/review.playbook/review.playbook.ts +17 -7
- package/reference/sdlc/review.playbook/review.registry.js +1 -1
- package/reference/sdlc/review.playbook/review.registry.ts +1 -1
- package/slc/gears2fsm.md +20 -0
- package/slc/link.md +28 -5
- package/slc/text2gears.md +21 -1
- package/src/xstate-playbook-runtime.js +5 -2
- package/src/xstate-playbook-runtime.ts +5 -2
- package/src/xstate-runtime.js +13 -1
- 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
|
|
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.
|
|
72
|
-
# reuse the same two stable players
|
|
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
|
-
|
|
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 =
|
|
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>>,
|
|
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
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
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,
|
|
39
|
+
function composeReviewInput(callerTopic, decideCommit, coderOutput) {
|
|
37
40
|
const fields = {
|
|
38
41
|
'<caller-topic>': callerTopic ?? '',
|
|
39
|
-
'<
|
|
42
|
+
'<decide-commit>': decideCommit ?? '',
|
|
43
|
+
'<coder-output>': coderOutput ?? '',
|
|
40
44
|
};
|
|
41
|
-
return REVIEW_INPUT_TEMPLATE.replace(/<caller-topic>|<coder-
|
|
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, ['
|
|
191
|
+
if (!isExactKeys(output, ['evaluatedRevision', 'noUnsettledFindings'])) {
|
|
178
192
|
return undefined;
|
|
179
193
|
}
|
|
180
|
-
if (output.
|
|
194
|
+
if (!isNonEmptyString(output.evaluatedRevision) ||
|
|
181
195
|
output.noUnsettledFindings !== true) {
|
|
182
196
|
return undefined;
|
|
183
197
|
}
|
|
184
198
|
return {
|
|
185
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
785
|
+
description: 'Coder synthesizes both proposals and commits the design.',
|
|
761
786
|
meta: {
|
|
762
787
|
playbook: {
|
|
763
788
|
stateId: 'commitCoderProposal',
|
|
764
|
-
description: 'Coder
|
|
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:
|
|
799
|
+
prompt: SYNTHESIZE_COMMIT_PROMPT,
|
|
775
800
|
result: withNeedsBossReply({
|
|
776
|
-
committed:
|
|
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.
|
|
911
|
+
text: composeReviewInput(context.callerTopic, context.latestCommit, context.coderOutput),
|
|
885
912
|
}),
|
|
886
913
|
onDone: [
|
|
887
914
|
{
|