@sublang/playbook 11.0.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/docs/cli.md +9 -6
  2. package/docs/configuration.md +15 -1
  3. package/package.json +22 -3
  4. package/reference/sdlc/code.playbook/code.fsm.d.ts +22 -19
  5. package/reference/sdlc/code.playbook/code.fsm.js +116 -52
  6. package/reference/sdlc/code.playbook/code.fsm.ts +149 -64
  7. package/reference/sdlc/code.playbook/code.gears.md +40 -20
  8. package/reference/sdlc/code.playbook/code.playbook.js +23 -2
  9. package/reference/sdlc/code.playbook/code.playbook.ts +23 -2
  10. package/reference/sdlc/code.playbook/playbook.config.template.yaml +18 -2
  11. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +13 -6
  12. package/reference/sdlc/decide.playbook/decide.fsm.js +54 -27
  13. package/reference/sdlc/decide.playbook/decide.fsm.ts +68 -29
  14. package/reference/sdlc/decide.playbook/decide.gears.md +25 -19
  15. package/reference/sdlc/decide.playbook/decide.playbook.js +11 -3
  16. package/reference/sdlc/decide.playbook/decide.playbook.ts +11 -3
  17. package/reference/sdlc/decide.playbook/decide.registry.js +1 -1
  18. package/reference/sdlc/decide.playbook/decide.registry.ts +1 -1
  19. package/reference/sdlc/dev.md +52 -0
  20. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +261 -0
  21. package/reference/sdlc/dev.playbook/dev.fsm.js +723 -0
  22. package/reference/sdlc/dev.playbook/dev.fsm.ts +988 -0
  23. package/reference/sdlc/dev.playbook/dev.gears.md +91 -0
  24. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +21 -0
  25. package/reference/sdlc/dev.playbook/dev.playbook.js +143 -0
  26. package/reference/sdlc/dev.playbook/dev.playbook.ts +246 -0
  27. package/reference/sdlc/dev.playbook/dev.registry.d.ts +40 -0
  28. package/reference/sdlc/dev.playbook/dev.registry.js +64 -0
  29. package/reference/sdlc/dev.playbook/dev.registry.ts +120 -0
  30. package/reference/sdlc/review.playbook/review.fsm.d.ts +15 -2
  31. package/reference/sdlc/review.playbook/review.fsm.js +77 -27
  32. package/reference/sdlc/review.playbook/review.fsm.ts +96 -30
  33. package/reference/sdlc/review.playbook/review.gears.md +52 -26
  34. package/reference/sdlc/review.playbook/review.playbook.js +17 -7
  35. package/reference/sdlc/review.playbook/review.playbook.ts +17 -7
  36. package/reference/sdlc/review.playbook/review.registry.js +1 -1
  37. package/reference/sdlc/review.playbook/review.registry.ts +1 -1
  38. package/slc/link.md +12 -5
  39. package/slc/text2gears.md +3 -0
  40. package/src/xstate-playbook-runtime.js +5 -2
  41. package/src/xstate-playbook-runtime.ts +5 -2
  42. package/src/xstate-runtime.js +13 -1
  43. package/src/xstate-runtime.ts +13 -1
@@ -0,0 +1,91 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai> -->
3
+
4
+ # DEV: Development Planning Workflow
5
+
6
+ Roles:
7
+
8
+ - Analyst
9
+
10
+ `dev` is an optional repository-aware planner for a development request that needs more analysis before choosing a development path.
11
+ It coordinates existing playbooks and owns no repository commit itself.
12
+
13
+ ## Analyst
14
+
15
+ ### DEV-1
16
+
17
+ At the start of `dev` and after each Boss reply, Captain shall relay the development request, relevant discussion context, and any relevant run results to Analyst in quotes (`>`), along with the planning instruction:
18
+
19
+ > > <development-request>
20
+ > > <discussion-context>
21
+ > > <run-results>
22
+ >
23
+ > Inspect the request and the relevant repository and specs only as needed to determine the smallest sound next step.
24
+ > Do not change files or commit while planning or discussing the request.
25
+ >
26
+ > - If useful analysis or clarification should be discussed before any repository work, give Boss the useful response and ask one material question that advances the decision.
27
+ > - If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.
28
+ > - If implementation can proceed under the existing decisions, choose `code`.
29
+ > - If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.
30
+ >
31
+ > A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.
32
+ > Do not choose `decide then code` merely because the work is large.
33
+ > Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.
34
+
35
+ Results:
36
+ - `discussionComplete`: Analyst concluded the discussion after a Boss reply, with no repository work to follow.
37
+ - `code`: Analyst determined implementation can proceed under the existing decisions. Output shall include `planningResult: <verbatim final text>`.
38
+ - `decideThenCode`: Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle. Output shall include `planningResult: <verbatim final text>`.
39
+
40
+ Workflow outcomes:
41
+ - The planning result has four semantic outcomes: needs Boss reply, discussion complete, code, and decide then code.
42
+ - Each outcome requires affirmative support in Analyst's result; absence of a reason to choose another outcome is not support, and no outcome depends on a fixed presentation format of Analyst's reply.
43
+ - Needs Boss reply uses the standard Boss-question suspension with Analyst's complete response; after Boss replies, `dev` resumes with the question and answer in the same Analyst conversation.
44
+ - Discussion complete is available only after a Boss reply, when any useful analysis has already been presented through needs Boss reply; it completes `dev` without a child call or repository change.
45
+ - `dev` acts on the accepted outcome itself and does not return to the session Captain for another routing decision.
46
+
47
+ ## Nested development paths
48
+
49
+ ### DEV-2
50
+
51
+ When the accepted planning result selects `code`, Captain shall call playbook `code`:
52
+
53
+ > > <development-request>
54
+ > > <discussion-context>
55
+ > > <planning-result>
56
+
57
+ Workflow outcomes:
58
+ - `code` success completes `dev` with the successful `code` result.
59
+ - An authored `code` abort or failure, or a terminal `code` result that does not prove the success required for the selected path, terminates `dev` with that canonical result relayed and no later child call.
60
+ - Any other nested-call error parks `dev` as failed and retains the control-plane error.
61
+ - `dev` consumes commit identities only from each child's canonical structured result, never from player prose.
62
+
63
+ ### DEV-3
64
+
65
+ When the accepted planning result selects `decide then code`, Captain shall call playbook `decide`:
66
+
67
+ > > <development-request>
68
+ > > <discussion-context>
69
+ > > <planning-result>
70
+
71
+ Workflow outcomes:
72
+ - `decide` success provides the `decide`-owned commit and the exact evaluated repository revision from `decide`'s canonical structured result and continues with the `code` call.
73
+ - An authored `decide` abort or failure, or a terminal `decide` result that does not prove that success, terminates `dev` with that canonical result relayed and no later child call.
74
+ - Any other nested-call error parks `dev` as failed and retains the control-plane error.
75
+ - `dev` does not separately call `review` for the design scope already reviewed by `decide`.
76
+
77
+ ### DEV-4
78
+
79
+ When `decide` succeeds, Captain shall call playbook `code`:
80
+
81
+ > > <development-request>
82
+ > > <discussion-context>
83
+ > > <planning-result>
84
+ > > <decide-commit>
85
+ > > <evaluated-revision>
86
+
87
+ Workflow outcomes:
88
+ - `code` success completes `dev` with the successful `code` result.
89
+ - An authored `code` abort or failure, or a terminal `code` result that does not prove the success required for the selected path, terminates `dev` with that canonical result relayed and no later child call.
90
+ - Any other nested-call error parks `dev` as failed and retains the control-plane error.
91
+ - `dev` consumes commit identities only from each child's canonical structured result, never from player prose.
@@ -0,0 +1,21 @@
1
+ import { type XStatePlaybookRuntimeFactory, type XStatePlaybookRuntimeConstruction } from '@sublang/playbook/xstate-runtime';
2
+ import { type DevInput, type PlayerInput } from './dev.fsm.js';
3
+ import type { PlaybookHostConstructionCapabilities } from '../code.playbook/playbook-captain.js';
4
+ import type { CaptainCallOptions, CaptainResult, JsonValue, NormalizedError, PlayerCallOptions, PlaybookCallRequest, PlaybookCallResult, PlaybookCallStart, PlaybookControlReceipt, PlaybookControlView, PlaybookPendingCall, PlaybookPorts, PlaybookRunResult, PlaybookRuntime, PlaybookRuntimeFactory, PlaybookRuntimeSnapshot, PlaybookSession, PlaybookState, PlaybookStateValue, PlaybookTraceEvent, PlaybookTraceType, PlayerResult, PlayerSessionStore } from '@sublang/playbook/runtime';
5
+ export type { CaptainCallOptions, CaptainResult, JsonValue, NormalizedError, PlayerCallOptions, PlaybookCallRequest, PlaybookCallResult, PlaybookCallStart, PlaybookControlReceipt, PlaybookControlView, PlaybookPendingCall, PlaybookPorts, PlaybookRunResult, PlaybookRuntime, PlaybookRuntimeFactory, PlaybookRuntimeSnapshot, PlaybookSession, PlaybookState, PlaybookStateValue, PlaybookTraceEvent, PlaybookTraceType, PlayerResult, PlayerSessionStore, };
6
+ export type DevPlaybookOptions = DevInput;
7
+ export type DevPlaybookHostCapabilities = PlaybookHostConstructionCapabilities & XStatePlaybookRuntimeConstruction<DevPlaybookOptions, object>['hostCapabilities'];
8
+ /**
9
+ * Preserve authored Markdown quote markers around every line of relayed
10
+ * runtime text. The generic composer preserves the marker itself; DEV's
11
+ * override additionally keeps a multiline value inside that quote and drops
12
+ * the optional relays that have no value yet.
13
+ */
14
+ declare function composePlayerPrompt(input: PlayerInput): string;
15
+ export declare const _internal: {
16
+ composePlayerPrompt: typeof composePlayerPrompt;
17
+ VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string>;
18
+ UNFINISHED_FINAL_STATE_IDS: ReadonlySet<string>;
19
+ };
20
+ declare const createPlaybookRuntime: XStatePlaybookRuntimeFactory<XStatePlaybookRuntimeConstruction<DevPlaybookOptions, DevPlaybookHostCapabilities>, 3>;
21
+ export default createPlaybookRuntime;
@@ -0,0 +1,143 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+ //
4
+ // Generated by slc/link.md (FSM-to-Runtime linker).
5
+ // Source FSM: ./dev.fsm.ts
6
+ // Role metadata: Analyst→analyst (canonical local role)
7
+ // Boss event: deterministic START_DEV entry; exact Boss text becomes
8
+ // developmentRequest; pending Analyst questions retain
9
+ // BOSS_REPLY
10
+ // Adjudication: LLM judge for the one Analyst state; planningResult is
11
+ // carried verbatim
12
+ // Nested calls: literal code and decide targets through the shared bridge
13
+ // Compat: artifact schema 3 / runtime ABI 1
14
+ import { createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
15
+ import { devMachine, } from './dev.fsm.js';
16
+ const OPTION_KEYS = new Set(['runResults']);
17
+ const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
18
+ const CONTINUATION_PREAMBLE = 'You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.';
19
+ const VERBATIM_PAYLOAD_FIELDS = new Set([
20
+ 'planningResult',
21
+ ]);
22
+ const UNFINISHED_FINAL_STATE_IDS = new Set([
23
+ 'reportedChildFailure',
24
+ ]);
25
+ function snapshotDevOptions(value) {
26
+ const captured = snapshotJsonValue(value, 'DEV runtime options');
27
+ if (captured === null ||
28
+ typeof captured !== 'object' ||
29
+ Array.isArray(captured)) {
30
+ throw new TypeError('DEV runtime options must be an object');
31
+ }
32
+ const record = captured;
33
+ for (const [key, option] of Object.entries(record)) {
34
+ if (!OPTION_KEYS.has(key)) {
35
+ throw new TypeError(`DEV runtime options.${key} is not declared`);
36
+ }
37
+ if (typeof option !== 'string') {
38
+ throw new TypeError(`DEV runtime options.${key} must be a string`);
39
+ }
40
+ }
41
+ return captured;
42
+ }
43
+ function placeholderField(token) {
44
+ if (token === '#')
45
+ return 'irNumber';
46
+ return token.replace(/-([A-Za-z0-9_$])/g, (_match, next) => next.toUpperCase());
47
+ }
48
+ function quotedContinuation(value) {
49
+ return value.replaceAll('\n', '\n> ');
50
+ }
51
+ /**
52
+ * Preserve authored Markdown quote markers around every line of relayed
53
+ * runtime text. The generic composer preserves the marker itself; DEV's
54
+ * override additionally keeps a multiline value inside that quote and drops
55
+ * the optional relays that have no value yet.
56
+ */
57
+ function composePlayerPrompt(input) {
58
+ const fields = input;
59
+ const template = input.prompt
60
+ .split('\n')
61
+ .filter((line) => !(line === '> <discussion-context>' &&
62
+ input.discussionContext.length === 0) &&
63
+ !(line === '> <run-results>' && input.runResults.length === 0))
64
+ .join('\n');
65
+ const body = template.replace(PLACEHOLDER, (match, token, offset, source) => {
66
+ const value = fields[placeholderField(token)];
67
+ if (typeof value !== 'string')
68
+ return match;
69
+ const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
70
+ const literal = source.slice(lineStart, offset);
71
+ return literal === '> ' ? quotedContinuation(value) : value;
72
+ });
73
+ if (input.pendingBossQuestion === undefined ||
74
+ input.bossReply === undefined) {
75
+ return body;
76
+ }
77
+ return [
78
+ CONTINUATION_PREAMBLE,
79
+ `Boss question:\n${input.pendingBossQuestion.question}`,
80
+ `Boss reply:\n${input.bossReply}`,
81
+ body,
82
+ ].join('\n\n');
83
+ }
84
+ export const _internal = {
85
+ composePlayerPrompt,
86
+ VERBATIM_PAYLOAD_FIELDS,
87
+ UNFINISHED_FINAL_STATE_IDS,
88
+ };
89
+ const runtimeSpec = {
90
+ label: 'DEV',
91
+ // DR-022 / slc/link.md: the declaration carries the value current at link
92
+ // time — a literal, never the loading engine's RUNTIME_ABI self-report,
93
+ // which would follow whatever engine loads the module and make the
94
+ // factory's skew check compare that engine with itself.
95
+ compat: { artifactSchema: 3, runtimeAbi: 1 },
96
+ snapshotOptions: snapshotDevOptions,
97
+ entryEvent: {
98
+ type: 'START_DEV',
99
+ textField: 'developmentRequest',
100
+ // `startDev` copies the entry text here, so the failure-state retry
101
+ // reads it back from the persisted machine snapshot and survives a
102
+ // continued session (DR-034).
103
+ contextField: 'developmentRequest',
104
+ },
105
+ roleStates: {
106
+ planAnalysis: {
107
+ role: 'analyst',
108
+ label: 'Analyst is analyzing the development request to choose the smallest sound next step.',
109
+ },
110
+ },
111
+ // DR-044: DEV owns no repository commit — every Analyst outcome is
112
+ // governed with the `unchanged` repository disposition, so planning that
113
+ // mutates the repository fails authority instead of being adopted.
114
+ outcomeAuthority: {
115
+ governedPlayerStates: {
116
+ planAnalysis: {
117
+ discussionComplete: {
118
+ fields: {},
119
+ repositoryDisposition: 'unchanged',
120
+ },
121
+ code: {
122
+ fields: { planningResult: 'presentation' },
123
+ repositoryDisposition: 'unchanged',
124
+ },
125
+ decideThenCode: {
126
+ fields: { planningResult: 'presentation' },
127
+ repositoryDisposition: 'unchanged',
128
+ },
129
+ needsBossReply: {
130
+ fields: { question: 'presentation' },
131
+ repositoryDisposition: 'unchanged',
132
+ },
133
+ },
134
+ },
135
+ },
136
+ composePlayerPrompt: (input) => composePlayerPrompt(input),
137
+ verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
138
+ controlContextFields: [],
139
+ unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
140
+ transitionEventFields: ['developmentRequest', 'answer', 'questionId'],
141
+ };
142
+ const createPlaybookRuntime = createXStatePlaybookRuntime(devMachine, runtimeSpec);
143
+ export default createPlaybookRuntime;
@@ -0,0 +1,246 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+ //
4
+ // Generated by slc/link.md (FSM-to-Runtime linker).
5
+ // Source FSM: ./dev.fsm.ts
6
+ // Role metadata: Analyst→analyst (canonical local role)
7
+ // Boss event: deterministic START_DEV entry; exact Boss text becomes
8
+ // developmentRequest; pending Analyst questions retain
9
+ // BOSS_REPLY
10
+ // Adjudication: LLM judge for the one Analyst state; planningResult is
11
+ // carried verbatim
12
+ // Nested calls: literal code and decide targets through the shared bridge
13
+ // Compat: artifact schema 3 / runtime ABI 1
14
+
15
+ import {
16
+ createXStatePlaybookRuntime,
17
+ snapshotJsonValue,
18
+ type PlaybookPlayerInput,
19
+ type XStatePlaybookRuntimeFactory,
20
+ type XStatePlaybookRuntimeConstruction,
21
+ type XStatePlaybookRuntimeSpecV3,
22
+ } from '@sublang/playbook/xstate-runtime';
23
+ import {
24
+ devMachine,
25
+ type DevInput,
26
+ type PlayerInput,
27
+ } from './dev.fsm.js';
28
+ import type { PlaybookHostConstructionCapabilities } from '../code.playbook/playbook-captain.js';
29
+ import type {
30
+ CaptainCallOptions,
31
+ CaptainResult,
32
+ JsonValue,
33
+ NormalizedError,
34
+ PlayerCallOptions,
35
+ PlaybookCallRequest,
36
+ PlaybookCallResult,
37
+ PlaybookCallStart,
38
+ PlaybookControlReceipt,
39
+ PlaybookControlView,
40
+ PlaybookPendingCall,
41
+ PlaybookPorts,
42
+ PlaybookRunResult,
43
+ PlaybookRuntime,
44
+ PlaybookRuntimeFactory,
45
+ PlaybookRuntimeSnapshot,
46
+ PlaybookSession,
47
+ PlaybookState,
48
+ PlaybookStateValue,
49
+ PlaybookTraceEvent,
50
+ PlaybookTraceType,
51
+ PlayerResult,
52
+ PlayerSessionStore,
53
+ } from '@sublang/playbook/runtime';
54
+
55
+ export type {
56
+ CaptainCallOptions,
57
+ CaptainResult,
58
+ JsonValue,
59
+ NormalizedError,
60
+ PlayerCallOptions,
61
+ PlaybookCallRequest,
62
+ PlaybookCallResult,
63
+ PlaybookCallStart,
64
+ PlaybookControlReceipt,
65
+ PlaybookControlView,
66
+ PlaybookPendingCall,
67
+ PlaybookPorts,
68
+ PlaybookRunResult,
69
+ PlaybookRuntime,
70
+ PlaybookRuntimeFactory,
71
+ PlaybookRuntimeSnapshot,
72
+ PlaybookSession,
73
+ PlaybookState,
74
+ PlaybookStateValue,
75
+ PlaybookTraceEvent,
76
+ PlaybookTraceType,
77
+ PlayerResult,
78
+ PlayerSessionStore,
79
+ };
80
+
81
+ export type DevPlaybookOptions = DevInput;
82
+ export type DevPlaybookHostCapabilities =
83
+ PlaybookHostConstructionCapabilities &
84
+ XStatePlaybookRuntimeConstruction<DevPlaybookOptions, object>['hostCapabilities'];
85
+
86
+ const OPTION_KEYS = new Set(['runResults']);
87
+ const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
88
+ const CONTINUATION_PREAMBLE =
89
+ 'You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.';
90
+
91
+ const VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string> = new Set([
92
+ 'planningResult',
93
+ ]);
94
+ const UNFINISHED_FINAL_STATE_IDS: ReadonlySet<string> = new Set([
95
+ 'reportedChildFailure',
96
+ ]);
97
+
98
+ function snapshotDevOptions(value: unknown): DevPlaybookOptions {
99
+ const captured = snapshotJsonValue(value, 'DEV runtime options');
100
+ if (
101
+ captured === null ||
102
+ typeof captured !== 'object' ||
103
+ Array.isArray(captured)
104
+ ) {
105
+ throw new TypeError('DEV runtime options must be an object');
106
+ }
107
+ const record = captured as Readonly<Record<string, JsonValue>>;
108
+ for (const [key, option] of Object.entries(record)) {
109
+ if (!OPTION_KEYS.has(key)) {
110
+ throw new TypeError(`DEV runtime options.${key} is not declared`);
111
+ }
112
+ if (typeof option !== 'string') {
113
+ throw new TypeError(`DEV runtime options.${key} must be a string`);
114
+ }
115
+ }
116
+ return captured as unknown as DevPlaybookOptions;
117
+ }
118
+
119
+ function placeholderField(token: string): string {
120
+ if (token === '#') return 'irNumber';
121
+ return token.replace(/-([A-Za-z0-9_$])/g, (_match, next: string) =>
122
+ next.toUpperCase(),
123
+ );
124
+ }
125
+
126
+ function quotedContinuation(value: string): string {
127
+ return value.replaceAll('\n', '\n> ');
128
+ }
129
+
130
+ /**
131
+ * Preserve authored Markdown quote markers around every line of relayed
132
+ * runtime text. The generic composer preserves the marker itself; DEV's
133
+ * override additionally keeps a multiline value inside that quote and drops
134
+ * the optional relays that have no value yet.
135
+ */
136
+ function composePlayerPrompt(input: PlayerInput): string {
137
+ const fields = input as unknown as Record<string, unknown>;
138
+ const template = input.prompt
139
+ .split('\n')
140
+ .filter(
141
+ (line) =>
142
+ !(
143
+ line === '> <discussion-context>' &&
144
+ input.discussionContext.length === 0
145
+ ) &&
146
+ !(line === '> <run-results>' && input.runResults.length === 0),
147
+ )
148
+ .join('\n');
149
+ const body = template.replace(
150
+ PLACEHOLDER,
151
+ (match: string, token: string, offset: number, source: string) => {
152
+ const value = fields[placeholderField(token)];
153
+ if (typeof value !== 'string') return match;
154
+ const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
155
+ const literal = source.slice(lineStart, offset);
156
+ return literal === '> ' ? quotedContinuation(value) : value;
157
+ },
158
+ );
159
+ if (
160
+ input.pendingBossQuestion === undefined ||
161
+ input.bossReply === undefined
162
+ ) {
163
+ return body;
164
+ }
165
+ return [
166
+ CONTINUATION_PREAMBLE,
167
+ `Boss question:\n${input.pendingBossQuestion.question}`,
168
+ `Boss reply:\n${input.bossReply}`,
169
+ body,
170
+ ].join('\n\n');
171
+ }
172
+
173
+ export const _internal = {
174
+ composePlayerPrompt,
175
+ VERBATIM_PAYLOAD_FIELDS,
176
+ UNFINISHED_FINAL_STATE_IDS,
177
+ };
178
+
179
+ const runtimeSpec = {
180
+ label: 'DEV',
181
+ // DR-022 / slc/link.md: the declaration carries the value current at link
182
+ // time — a literal, never the loading engine's RUNTIME_ABI self-report,
183
+ // which would follow whatever engine loads the module and make the
184
+ // factory's skew check compare that engine with itself.
185
+ compat: { artifactSchema: 3, runtimeAbi: 1 },
186
+ snapshotOptions: snapshotDevOptions,
187
+ entryEvent: {
188
+ type: 'START_DEV',
189
+ textField: 'developmentRequest',
190
+ // `startDev` copies the entry text here, so the failure-state retry
191
+ // reads it back from the persisted machine snapshot and survives a
192
+ // continued session (DR-034).
193
+ contextField: 'developmentRequest',
194
+ },
195
+ roleStates: {
196
+ planAnalysis: {
197
+ role: 'analyst',
198
+ label:
199
+ 'Analyst is analyzing the development request to choose the smallest sound next step.',
200
+ },
201
+ },
202
+ // DR-044: DEV owns no repository commit — every Analyst outcome is
203
+ // governed with the `unchanged` repository disposition, so planning that
204
+ // mutates the repository fails authority instead of being adopted.
205
+ outcomeAuthority: {
206
+ governedPlayerStates: {
207
+ planAnalysis: {
208
+ discussionComplete: {
209
+ fields: {},
210
+ repositoryDisposition: 'unchanged',
211
+ },
212
+ code: {
213
+ fields: { planningResult: 'presentation' },
214
+ repositoryDisposition: 'unchanged',
215
+ },
216
+ decideThenCode: {
217
+ fields: { planningResult: 'presentation' },
218
+ repositoryDisposition: 'unchanged',
219
+ },
220
+ needsBossReply: {
221
+ fields: { question: 'presentation' },
222
+ repositoryDisposition: 'unchanged',
223
+ },
224
+ },
225
+ },
226
+ },
227
+ composePlayerPrompt: (input: PlaybookPlayerInput) =>
228
+ composePlayerPrompt(input as PlayerInput),
229
+ verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
230
+ controlContextFields: [],
231
+ unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
232
+ transitionEventFields: ['developmentRequest', 'answer', 'questionId'],
233
+ } satisfies XStatePlaybookRuntimeSpecV3<DevPlaybookOptions>;
234
+
235
+ const createPlaybookRuntime: XStatePlaybookRuntimeFactory<
236
+ XStatePlaybookRuntimeConstruction<
237
+ DevPlaybookOptions,
238
+ DevPlaybookHostCapabilities
239
+ >,
240
+ 3
241
+ > = createXStatePlaybookRuntime<
242
+ DevPlaybookOptions,
243
+ DevPlaybookHostCapabilities
244
+ >(devMachine, runtimeSpec);
245
+
246
+ export default createPlaybookRuntime;
@@ -0,0 +1,40 @@
1
+ import { type DevPlaybookHostCapabilities, type PlaybookRuntime } from './dev.playbook.js';
2
+ export interface PlaybookSummaryPolicy {
3
+ stateCountLabels: Readonly<Record<string, string>>;
4
+ copyPasteGuardNames: readonly string[];
5
+ savedCountsLine(counts: {
6
+ interruptions: number;
7
+ copyPastes: number;
8
+ }, rounds: number): string;
9
+ }
10
+ export type DevOptions = Readonly<Record<string, never>>;
11
+ export interface DevPlaybookRegistryEntry {
12
+ id: 'dev';
13
+ command: 'dev';
14
+ intent: string;
15
+ artifactSchema: 3;
16
+ runtimeProfile: {
17
+ readonly kind: 'shared-factory';
18
+ readonly compat: {
19
+ readonly artifactSchema: 3;
20
+ readonly runtimeAbi: number;
21
+ };
22
+ };
23
+ requiredRoleIds: readonly ['analyst'];
24
+ concurrentRoleSets: readonly [];
25
+ summaryPolicy: PlaybookSummaryPolicy;
26
+ validateOptions(optionSlice: unknown): DevOptions;
27
+ createRuntime(options: DevOptions, hostCapabilities: DevPlaybookHostCapabilities): PlaybookRuntime;
28
+ }
29
+ export declare const devStateCountLabels: {
30
+ readonly planAnalysis: "planning round";
31
+ };
32
+ export declare const devCopyPasteGuardNames: readonly ["code", "decideThenCode"];
33
+ export declare function devSavedCountsLine(counts: {
34
+ interruptions: number;
35
+ copyPastes: number;
36
+ }, rounds: number): string;
37
+ export declare const devSummaryPolicy: PlaybookSummaryPolicy;
38
+ export declare function validateDevOptions(optionSlice: unknown): DevOptions;
39
+ export declare const devPlaybookRegistryEntry: DevPlaybookRegistryEntry;
40
+ export default devPlaybookRegistryEntry;
@@ -0,0 +1,64 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+ import createPlaybookRuntime from './dev.playbook.js';
4
+ // CODE and DECIDE own and label the rounds of the paths DEV starts. DEV's
5
+ // suspended call states only delegate to those children and must not
6
+ // double-count their rounds; only the Analyst planning rounds are DEV's own.
7
+ export const devStateCountLabels = {
8
+ planAnalysis: 'planning round',
9
+ };
10
+ export const devCopyPasteGuardNames = ['code', 'decideThenCode'];
11
+ function countNoun(count, singular, plural = `${singular}s`) {
12
+ return `${count} ${count === 1 ? singular : plural}`;
13
+ }
14
+ export function devSavedCountsLine(counts, rounds) {
15
+ return [
16
+ 'Saved you',
17
+ countNoun(counts.interruptions, 'interruption'),
18
+ 'and',
19
+ countNoun(counts.copyPastes, 'copy-paste'),
20
+ 'across',
21
+ countNoun(rounds, 'round'),
22
+ 'of planning.',
23
+ ].join(' ');
24
+ }
25
+ export const devSummaryPolicy = {
26
+ stateCountLabels: devStateCountLabels,
27
+ copyPasteGuardNames: devCopyPasteGuardNames,
28
+ savedCountsLine: devSavedCountsLine,
29
+ };
30
+ export function validateDevOptions(optionSlice) {
31
+ if (optionSlice === undefined)
32
+ return Object.freeze({});
33
+ if (optionSlice === null ||
34
+ typeof optionSlice !== 'object' ||
35
+ Array.isArray(optionSlice)) {
36
+ throw new Error('captain.options.playbooks.dev.options must be an object');
37
+ }
38
+ const keys = Object.keys(optionSlice);
39
+ if (keys.length > 0) {
40
+ throw new Error(`Unknown config field captain.options.playbooks.dev.options.${keys[0]}`);
41
+ }
42
+ return Object.freeze({});
43
+ }
44
+ export const devPlaybookRegistryEntry = {
45
+ id: 'dev',
46
+ command: 'dev',
47
+ intent: 'analyze a development request that needs planning before choosing direct implementation or a durable decision first',
48
+ artifactSchema: 3,
49
+ runtimeProfile: Object.freeze({
50
+ kind: 'shared-factory',
51
+ compat: createPlaybookRuntime.compat,
52
+ }),
53
+ requiredRoleIds: ['analyst'],
54
+ concurrentRoleSets: [],
55
+ summaryPolicy: devSummaryPolicy,
56
+ validateOptions: validateDevOptions,
57
+ createRuntime(options, hostCapabilities) {
58
+ return createPlaybookRuntime({
59
+ configuredOptions: options,
60
+ hostCapabilities,
61
+ });
62
+ },
63
+ };
64
+ export default devPlaybookRegistryEntry;