@sublang/playbook 13.0.0 → 13.2.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 (68) hide show
  1. package/README.md +7 -5
  2. package/docs/cli.md +28 -9
  3. package/docs/configuration.md +21 -5
  4. package/docs/embedding.md +1 -1
  5. package/package.json +41 -3
  6. package/reference/sdlc/branch.md +37 -0
  7. package/reference/sdlc/branch.playbook/branch.fsm.d.ts +169 -0
  8. package/reference/sdlc/branch.playbook/branch.fsm.js +325 -0
  9. package/reference/sdlc/branch.playbook/branch.fsm.ts +458 -0
  10. package/reference/sdlc/branch.playbook/branch.gears.md +44 -0
  11. package/reference/sdlc/branch.playbook/branch.playbook.d.ts +20 -0
  12. package/reference/sdlc/branch.playbook/branch.playbook.js +121 -0
  13. package/reference/sdlc/branch.playbook/branch.playbook.ts +217 -0
  14. package/reference/sdlc/branch.playbook/branch.registry.d.ts +40 -0
  15. package/reference/sdlc/branch.playbook/branch.registry.js +63 -0
  16. package/reference/sdlc/branch.playbook/branch.registry.ts +121 -0
  17. package/reference/sdlc/code.md +7 -0
  18. package/reference/sdlc/code.playbook/code.fsm.js +5 -5
  19. package/reference/sdlc/code.playbook/code.fsm.ts +5 -5
  20. package/reference/sdlc/code.playbook/code.gears.md +5 -5
  21. package/reference/sdlc/code.playbook/code.playbook.d.ts +1 -1
  22. package/reference/sdlc/code.playbook/code.playbook.js +6 -16
  23. package/reference/sdlc/code.playbook/code.playbook.ts +7 -17
  24. package/reference/sdlc/code.playbook/playbook-captain.js +4 -1
  25. package/reference/sdlc/code.playbook/playbook-captain.ts +4 -1
  26. package/reference/sdlc/code.playbook/playbook.config.template.yaml +16 -2
  27. package/reference/sdlc/decide.md +2 -0
  28. package/reference/sdlc/decide.playbook/decide.fsm.js +1 -1
  29. package/reference/sdlc/decide.playbook/decide.fsm.ts +1 -1
  30. package/reference/sdlc/decide.playbook/decide.gears.md +2 -2
  31. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +1 -2
  32. package/reference/sdlc/decide.playbook/decide.playbook.js +8 -22
  33. package/reference/sdlc/decide.playbook/decide.playbook.ts +8 -25
  34. package/reference/sdlc/dev.md +45 -4
  35. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +100 -9
  36. package/reference/sdlc/dev.playbook/dev.fsm.js +245 -28
  37. package/reference/sdlc/dev.playbook/dev.fsm.ts +381 -37
  38. package/reference/sdlc/dev.playbook/dev.gears.md +60 -22
  39. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +1 -1
  40. package/reference/sdlc/dev.playbook/dev.playbook.js +20 -18
  41. package/reference/sdlc/dev.playbook/dev.playbook.ts +21 -21
  42. package/reference/sdlc/dev.playbook/dev.registry.d.ts +1 -1
  43. package/reference/sdlc/dev.playbook/dev.registry.js +7 -2
  44. package/reference/sdlc/dev.playbook/dev.registry.ts +7 -2
  45. package/reference/sdlc/pr.md +108 -0
  46. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +352 -0
  47. package/reference/sdlc/pr.playbook/pr.fsm.js +902 -0
  48. package/reference/sdlc/pr.playbook/pr.fsm.ts +1251 -0
  49. package/reference/sdlc/pr.playbook/pr.gears.md +155 -0
  50. package/reference/sdlc/pr.playbook/pr.playbook.d.ts +27 -0
  51. package/reference/sdlc/pr.playbook/pr.playbook.js +133 -0
  52. package/reference/sdlc/pr.playbook/pr.playbook.ts +235 -0
  53. package/reference/sdlc/pr.playbook/pr.registry.d.ts +40 -0
  54. package/reference/sdlc/pr.playbook/pr.registry.js +68 -0
  55. package/reference/sdlc/pr.playbook/pr.registry.ts +124 -0
  56. package/reference/sdlc/review.md +18 -0
  57. package/reference/sdlc/review.playbook/review.fsm.js +8 -8
  58. package/reference/sdlc/review.playbook/review.fsm.ts +8 -8
  59. package/reference/sdlc/review.playbook/review.gears.md +8 -8
  60. package/reference/sdlc/review.playbook/review.playbook.d.ts +1 -1
  61. package/reference/sdlc/review.playbook/review.playbook.js +5 -15
  62. package/reference/sdlc/review.playbook/review.playbook.ts +6 -16
  63. package/slc/link.md +12 -24
  64. package/src/runtime.d.ts +2 -0
  65. package/src/runtime.ts +2 -0
  66. package/src/xstate-playbook-runtime.d.ts +4 -2
  67. package/src/xstate-playbook-runtime.js +18 -13
  68. package/src/xstate-playbook-runtime.ts +26 -14
@@ -0,0 +1,217 @@
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: ./branch.fsm.ts
6
+ // Role metadata: Coder→coder (canonical local role)
7
+ // Boss event: deterministic START_BRANCH entry; exact Boss text becomes
8
+ // callerInput; pending Coder questions retain BOSS_REPLY
9
+ // Adjudication: LLM judge for the one Coder state; coderOutput is carried
10
+ // verbatim; baseRevision is receipt-owned effect evidence —
11
+ // the unchanged receipt's observed HEAD — never taken from
12
+ // Coder's prose
13
+ // Compat: artifact schema 3 / runtime ABI 1
14
+
15
+ import {
16
+ createXStatePlaybookRuntime,
17
+ composePlayerContinuation,
18
+ snapshotJsonValue,
19
+ type PlaybookPlayerInput,
20
+ type XStatePlaybookRuntimeFactory,
21
+ type XStatePlaybookRuntimeConstruction,
22
+ type XStatePlaybookRuntimeSpecV3,
23
+ } from '@sublang/playbook/xstate-runtime';
24
+ import {
25
+ branchMachine,
26
+ type BranchInput,
27
+ type PlayerInput,
28
+ } from './branch.fsm.js';
29
+ import type { PlaybookHostConstructionCapabilities } from '../code.playbook/playbook-captain.js';
30
+ import type {
31
+ CaptainCallOptions,
32
+ CaptainResult,
33
+ JsonValue,
34
+ NormalizedError,
35
+ PlayerCallOptions,
36
+ PlaybookCallRequest,
37
+ PlaybookCallResult,
38
+ PlaybookCallStart,
39
+ PlaybookControlReceipt,
40
+ PlaybookControlView,
41
+ PlaybookPendingCall,
42
+ PlaybookPorts,
43
+ PlaybookRunResult,
44
+ PlaybookRuntime,
45
+ PlaybookRuntimeFactory,
46
+ PlaybookRuntimeSnapshot,
47
+ PlaybookSession,
48
+ PlaybookState,
49
+ PlaybookStateValue,
50
+ PlaybookTraceEvent,
51
+ PlaybookTraceType,
52
+ PlayerResult,
53
+ PlayerSessionStore,
54
+ } from '@sublang/playbook/runtime';
55
+
56
+ export type {
57
+ CaptainCallOptions,
58
+ CaptainResult,
59
+ JsonValue,
60
+ NormalizedError,
61
+ PlayerCallOptions,
62
+ PlaybookCallRequest,
63
+ PlaybookCallResult,
64
+ PlaybookCallStart,
65
+ PlaybookControlReceipt,
66
+ PlaybookControlView,
67
+ PlaybookPendingCall,
68
+ PlaybookPorts,
69
+ PlaybookRunResult,
70
+ PlaybookRuntime,
71
+ PlaybookRuntimeFactory,
72
+ PlaybookRuntimeSnapshot,
73
+ PlaybookSession,
74
+ PlaybookState,
75
+ PlaybookStateValue,
76
+ PlaybookTraceEvent,
77
+ PlaybookTraceType,
78
+ PlayerResult,
79
+ PlayerSessionStore,
80
+ };
81
+
82
+ export type BranchPlaybookOptions = BranchInput;
83
+ export type BranchPlaybookHostCapabilities =
84
+ PlaybookHostConstructionCapabilities &
85
+ XStatePlaybookRuntimeConstruction<BranchPlaybookOptions, object>['hostCapabilities'];
86
+
87
+ const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
88
+
89
+ const VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string> = new Set([
90
+ 'coderOutput',
91
+ ]);
92
+ const UNFINISHED_FINAL_STATE_IDS: ReadonlySet<string> = new Set(['refused']);
93
+
94
+ function snapshotBranchOptions(value: unknown): BranchPlaybookOptions {
95
+ const captured = snapshotJsonValue(value, 'BRANCH runtime options');
96
+ if (
97
+ captured === null ||
98
+ typeof captured !== 'object' ||
99
+ Array.isArray(captured)
100
+ ) {
101
+ throw new TypeError('BRANCH runtime options must be an object');
102
+ }
103
+ const record = captured as Readonly<Record<string, JsonValue>>;
104
+ for (const key of Object.keys(record)) {
105
+ throw new TypeError(`BRANCH runtime options.${key} is not declared`);
106
+ }
107
+ return captured as unknown as BranchPlaybookOptions;
108
+ }
109
+
110
+ function placeholderField(token: string): string {
111
+ if (token === '#') return 'irNumber';
112
+ return token.replace(/-([A-Za-z0-9_$])/g, (_match, next: string) =>
113
+ next.toUpperCase(),
114
+ );
115
+ }
116
+
117
+ function quotedContinuation(value: string): string {
118
+ return value.replaceAll('\n', '\n> ');
119
+ }
120
+
121
+ /**
122
+ * Preserve authored Markdown quote markers around every line of relayed
123
+ * runtime text. The generic composer preserves the marker itself; BRANCH's
124
+ * override additionally keeps a multiline caller input inside that quote.
125
+ */
126
+ function composePlayerPrompt(input: PlayerInput, resuming = false): string {
127
+ const fields = input as unknown as Record<string, unknown>;
128
+ const body = input.prompt.replace(
129
+ PLACEHOLDER,
130
+ (match: string, token: string, offset: number, source: string) => {
131
+ const value = fields[placeholderField(token)];
132
+ if (typeof value !== 'string') return match;
133
+ const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
134
+ const literal = source.slice(lineStart, offset);
135
+ return literal.startsWith('> ') ? quotedContinuation(value) : value;
136
+ },
137
+ );
138
+ return composePlayerContinuation(input, body, resuming);
139
+ }
140
+
141
+ export const _internal = {
142
+ composePlayerPrompt,
143
+ VERBATIM_PAYLOAD_FIELDS,
144
+ UNFINISHED_FINAL_STATE_IDS,
145
+ };
146
+
147
+ const runtimeSpec = {
148
+ label: 'BRANCH',
149
+ // DR-022 / slc/link.md: the declaration carries the value current at link
150
+ // time — a literal, never the loading engine's RUNTIME_ABI self-report,
151
+ // which would follow whatever engine loads the module and make the
152
+ // factory's skew check compare that engine with itself.
153
+ compat: { artifactSchema: 3, runtimeAbi: 1 },
154
+ snapshotOptions: snapshotBranchOptions,
155
+ entryEvent: {
156
+ type: 'START_BRANCH',
157
+ textField: 'callerInput',
158
+ // `startBranch` copies the entry text here, so the failure-state retry
159
+ // reads it back from the persisted machine snapshot and survives a
160
+ // continued session (DR-034).
161
+ contextField: 'callerInput',
162
+ },
163
+ roleStates: {
164
+ createBranch: {
165
+ role: 'coder',
166
+ label:
167
+ 'Coder is reading the named issue and creating and checking out a new branch at the current commit.',
168
+ },
169
+ },
170
+ // DR-050: BRANCH owns no repository commit — every Coder outcome, including
171
+ // the Boss question for an ambiguous request, is governed with the
172
+ // `unchanged` repository disposition, because a new branch at the current
173
+ // commit changes neither HEAD's OID nor the worktree projection.
174
+ outcomeAuthority: {
175
+ governedPlayerStates: {
176
+ createBranch: {
177
+ branched: {
178
+ fields: {
179
+ branch: 'semantic',
180
+ // DR-045: the unchanged receipt observes the commit the branch
181
+ // was created from as HEAD; the reconciler injects it here.
182
+ baseRevision: 'effect',
183
+ issueSummary: 'semantic',
184
+ },
185
+ repositoryDisposition: 'unchanged',
186
+ },
187
+ refused: {
188
+ fields: { coderOutput: 'presentation' },
189
+ repositoryDisposition: 'unchanged',
190
+ },
191
+ needsBossReply: {
192
+ fields: { question: 'presentation' },
193
+ repositoryDisposition: 'unchanged',
194
+ },
195
+ },
196
+ },
197
+ },
198
+ composePlayerPrompt: (input: PlaybookPlayerInput, _identity, resuming) =>
199
+ composePlayerPrompt(input as PlayerInput, resuming),
200
+ verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
201
+ controlContextFields: [],
202
+ unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
203
+ transitionEventFields: ['callerInput', 'answer', 'questionId'],
204
+ } satisfies XStatePlaybookRuntimeSpecV3<BranchPlaybookOptions>;
205
+
206
+ const createPlaybookRuntime: XStatePlaybookRuntimeFactory<
207
+ XStatePlaybookRuntimeConstruction<
208
+ BranchPlaybookOptions,
209
+ BranchPlaybookHostCapabilities
210
+ >,
211
+ 3
212
+ > = createXStatePlaybookRuntime<
213
+ BranchPlaybookOptions,
214
+ BranchPlaybookHostCapabilities
215
+ >(branchMachine, runtimeSpec);
216
+
217
+ export default createPlaybookRuntime;
@@ -0,0 +1,40 @@
1
+ import { type BranchPlaybookHostCapabilities, type PlaybookRuntime } from './branch.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 BranchOptions = Readonly<Record<string, never>>;
11
+ export interface BranchPlaybookRegistryEntry {
12
+ id: 'branch';
13
+ command: 'branch';
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 ['coder'];
24
+ concurrentRoleSets: readonly [];
25
+ summaryPolicy: PlaybookSummaryPolicy;
26
+ validateOptions(optionSlice: unknown): BranchOptions;
27
+ createRuntime(options: BranchOptions, hostCapabilities: BranchPlaybookHostCapabilities): PlaybookRuntime;
28
+ }
29
+ export declare const branchStateCountLabels: {
30
+ readonly createBranch: "branching round";
31
+ };
32
+ export declare const branchCopyPasteGuardNames: readonly ["branched"];
33
+ export declare function branchSavedCountsLine(counts: {
34
+ interruptions: number;
35
+ copyPastes: number;
36
+ }, rounds: number): string;
37
+ export declare const branchSummaryPolicy: PlaybookSummaryPolicy;
38
+ export declare function validateBranchOptions(optionSlice: unknown): BranchOptions;
39
+ export declare const branchPlaybookRegistryEntry: BranchPlaybookRegistryEntry;
40
+ export default branchPlaybookRegistryEntry;
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+ import createPlaybookRuntime from './branch.playbook.js';
4
+ // BRANCH's one Coder call is its only round: the branching call that reads
5
+ // the issue and creates and checks out the branch.
6
+ export const branchStateCountLabels = {
7
+ createBranch: 'branching round',
8
+ };
9
+ export const branchCopyPasteGuardNames = ['branched'];
10
+ function countNoun(count, singular, plural = `${singular}s`) {
11
+ return `${count} ${count === 1 ? singular : plural}`;
12
+ }
13
+ export function branchSavedCountsLine(counts, rounds) {
14
+ return [
15
+ 'Saved you',
16
+ countNoun(counts.interruptions, 'interruption'),
17
+ 'and',
18
+ countNoun(counts.copyPastes, 'copy-paste'),
19
+ 'across',
20
+ countNoun(rounds, 'round'),
21
+ 'of branching.',
22
+ ].join(' ');
23
+ }
24
+ export const branchSummaryPolicy = {
25
+ stateCountLabels: branchStateCountLabels,
26
+ copyPasteGuardNames: branchCopyPasteGuardNames,
27
+ savedCountsLine: branchSavedCountsLine,
28
+ };
29
+ export function validateBranchOptions(optionSlice) {
30
+ if (optionSlice === undefined)
31
+ return Object.freeze({});
32
+ if (optionSlice === null ||
33
+ typeof optionSlice !== 'object' ||
34
+ Array.isArray(optionSlice)) {
35
+ throw new Error('captain.options.playbooks.branch.options must be an object');
36
+ }
37
+ const keys = Object.keys(optionSlice);
38
+ if (keys.length > 0) {
39
+ throw new Error(`Unknown config field captain.options.playbooks.branch.options.${keys[0]}`);
40
+ }
41
+ return Object.freeze({});
42
+ }
43
+ export const branchPlaybookRegistryEntry = {
44
+ id: 'branch',
45
+ command: 'branch',
46
+ intent: 'create and check out a new branch for a GitHub issue or request before pull-request delivery',
47
+ artifactSchema: 3,
48
+ runtimeProfile: Object.freeze({
49
+ kind: 'shared-factory',
50
+ compat: createPlaybookRuntime.compat,
51
+ }),
52
+ requiredRoleIds: ['coder'],
53
+ concurrentRoleSets: [],
54
+ summaryPolicy: branchSummaryPolicy,
55
+ validateOptions: validateBranchOptions,
56
+ createRuntime(options, hostCapabilities) {
57
+ return createPlaybookRuntime({
58
+ configuredOptions: options,
59
+ hostCapabilities,
60
+ });
61
+ },
62
+ };
63
+ export default branchPlaybookRegistryEntry;
@@ -0,0 +1,121 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ import createPlaybookRuntime, {
5
+ type BranchPlaybookHostCapabilities,
6
+ type PlaybookRuntime,
7
+ } from './branch.playbook.js';
8
+
9
+ export interface PlaybookSummaryPolicy {
10
+ stateCountLabels: Readonly<Record<string, string>>;
11
+ copyPasteGuardNames: readonly string[];
12
+ savedCountsLine(
13
+ counts: { interruptions: number; copyPastes: number },
14
+ rounds: number,
15
+ ): string;
16
+ }
17
+
18
+ export type BranchOptions = Readonly<Record<string, never>>;
19
+
20
+ export interface BranchPlaybookRegistryEntry {
21
+ id: 'branch';
22
+ command: 'branch';
23
+ intent: string;
24
+ artifactSchema: 3;
25
+ runtimeProfile: {
26
+ readonly kind: 'shared-factory';
27
+ readonly compat: {
28
+ readonly artifactSchema: 3;
29
+ readonly runtimeAbi: number;
30
+ };
31
+ };
32
+ requiredRoleIds: readonly ['coder'];
33
+ concurrentRoleSets: readonly [];
34
+ summaryPolicy: PlaybookSummaryPolicy;
35
+ validateOptions(optionSlice: unknown): BranchOptions;
36
+ createRuntime(
37
+ options: BranchOptions,
38
+ hostCapabilities: BranchPlaybookHostCapabilities,
39
+ ): PlaybookRuntime;
40
+ }
41
+
42
+ // BRANCH's one Coder call is its only round: the branching call that reads
43
+ // the issue and creates and checks out the branch.
44
+ export const branchStateCountLabels = {
45
+ createBranch: 'branching round',
46
+ } as const;
47
+
48
+ export const branchCopyPasteGuardNames = ['branched'] as const;
49
+
50
+ function countNoun(
51
+ count: number,
52
+ singular: string,
53
+ plural = `${singular}s`,
54
+ ): string {
55
+ return `${count} ${count === 1 ? singular : plural}`;
56
+ }
57
+
58
+ export function branchSavedCountsLine(
59
+ counts: { interruptions: number; copyPastes: number },
60
+ rounds: number,
61
+ ): string {
62
+ return [
63
+ 'Saved you',
64
+ countNoun(counts.interruptions, 'interruption'),
65
+ 'and',
66
+ countNoun(counts.copyPastes, 'copy-paste'),
67
+ 'across',
68
+ countNoun(rounds, 'round'),
69
+ 'of branching.',
70
+ ].join(' ');
71
+ }
72
+
73
+ export const branchSummaryPolicy: PlaybookSummaryPolicy = {
74
+ stateCountLabels: branchStateCountLabels,
75
+ copyPasteGuardNames: branchCopyPasteGuardNames,
76
+ savedCountsLine: branchSavedCountsLine,
77
+ };
78
+
79
+ export function validateBranchOptions(optionSlice: unknown): BranchOptions {
80
+ if (optionSlice === undefined) return Object.freeze({});
81
+ if (
82
+ optionSlice === null ||
83
+ typeof optionSlice !== 'object' ||
84
+ Array.isArray(optionSlice)
85
+ ) {
86
+ throw new Error(
87
+ 'captain.options.playbooks.branch.options must be an object',
88
+ );
89
+ }
90
+ const keys = Object.keys(optionSlice);
91
+ if (keys.length > 0) {
92
+ throw new Error(
93
+ `Unknown config field captain.options.playbooks.branch.options.${keys[0]}`,
94
+ );
95
+ }
96
+ return Object.freeze({});
97
+ }
98
+
99
+ export const branchPlaybookRegistryEntry: BranchPlaybookRegistryEntry = {
100
+ id: 'branch',
101
+ command: 'branch',
102
+ intent:
103
+ 'create and check out a new branch for a GitHub issue or request before pull-request delivery',
104
+ artifactSchema: 3,
105
+ runtimeProfile: Object.freeze({
106
+ kind: 'shared-factory',
107
+ compat: createPlaybookRuntime.compat,
108
+ }),
109
+ requiredRoleIds: ['coder'],
110
+ concurrentRoleSets: [],
111
+ summaryPolicy: branchSummaryPolicy,
112
+ validateOptions: validateBranchOptions,
113
+ createRuntime(options, hostCapabilities) {
114
+ return createPlaybookRuntime({
115
+ configuredOptions: options,
116
+ hostCapabilities,
117
+ });
118
+ },
119
+ };
120
+
121
+ export default branchPlaybookRegistryEntry;
@@ -25,6 +25,9 @@ When the nested `review` call fails outside that authored result contract, `code
25
25
 
26
26
  At the start of the first phase, Captain shall relay to Coder the complete caller input and any relevant run results in quotes (`>`), along with the following instruction:
27
27
 
28
+ > Original request: <caller-input>
29
+ > Run results: <run-results>
30
+
28
31
  ```markdown
29
32
  First determine whether the coding request starts a new coding intent or continues an existing IR with unfinished work.
30
33
  If the request may continue an existing IR but does not identify it unambiguously, ask Boss before changing files.
@@ -44,6 +47,10 @@ Consult @specs/map.md for relevant context and @specs/meta.md for spec requireme
44
47
 
45
48
  At the start of every later IR-task phase, Captain shall relay to Coder the original caller input, the IR identity, and any relevant run results in quotes (`>`), along with the following instruction:
46
49
 
50
+ > Original request: <caller-input>
51
+ > IR number: <ir-number>
52
+ > Run results: <run-results>
53
+
47
54
  ```markdown
48
55
  Read the identified IR and implement exactly its next unfinished task, including corresponding tests or specs if any.
49
56
  Do not implement a later task in this phase.
@@ -12,8 +12,8 @@ const APPENDED_PHASE_PROMPT = [
12
12
  'Coder is <coder-llm>.',
13
13
  ];
14
14
  const FIRST_PHASE_PROMPT = [
15
- '> <caller-input>',
16
- '> <run-results>',
15
+ '> Original request: <caller-input>',
16
+ '> Run results: <run-results>',
17
17
  '',
18
18
  'First determine whether the coding request starts a new coding intent or continues an existing IR with unfinished work.',
19
19
  'If the request may continue an existing IR but does not identify it unambiguously, ask Boss before changing files.',
@@ -33,9 +33,9 @@ const FIRST_PHASE_PROMPT = [
33
33
  ...APPENDED_PHASE_PROMPT,
34
34
  ].join('\n');
35
35
  const IR_TASK_PROMPT = [
36
- '> <caller-input>',
37
- '> <ir-number>',
38
- '> <run-results>',
36
+ '> Original request: <caller-input>',
37
+ '> IR number: <ir-number>',
38
+ '> Run results: <run-results>',
39
39
  '',
40
40
  'Read the identified IR and implement exactly its next unfinished task, including corresponding tests or specs if any.',
41
41
  'Do not implement a later task in this phase.',
@@ -156,8 +156,8 @@ const APPENDED_PHASE_PROMPT = [
156
156
  ];
157
157
 
158
158
  const FIRST_PHASE_PROMPT = [
159
- '> <caller-input>',
160
- '> <run-results>',
159
+ '> Original request: <caller-input>',
160
+ '> Run results: <run-results>',
161
161
  '',
162
162
  'First determine whether the coding request starts a new coding intent or continues an existing IR with unfinished work.',
163
163
  'If the request may continue an existing IR but does not identify it unambiguously, ask Boss before changing files.',
@@ -178,9 +178,9 @@ const FIRST_PHASE_PROMPT = [
178
178
  ].join('\n');
179
179
 
180
180
  const IR_TASK_PROMPT = [
181
- '> <caller-input>',
182
- '> <ir-number>',
183
- '> <run-results>',
181
+ '> Original request: <caller-input>',
182
+ '> IR number: <ir-number>',
183
+ '> Run results: <run-results>',
184
184
  '',
185
185
  'Read the identified IR and implement exactly its next unfinished task, including corresponding tests or specs if any.',
186
186
  'Do not implement a later task in this phase.',
@@ -13,8 +13,8 @@ Roles:
13
13
 
14
14
  When the first coding phase begins, Captain shall prompt Coder:
15
15
 
16
- > > <caller-input>
17
- > > <run-results>
16
+ > > Original request: <caller-input>
17
+ > > Run results: <run-results>
18
18
  >
19
19
  > First determine whether the coding request starts a new coding intent or continues an existing IR with unfinished work.
20
20
  > If the request may continue an existing IR but does not identify it unambiguously, ask Boss before changing files.
@@ -63,9 +63,9 @@ Workflow outcomes:
63
63
 
64
64
  When a later IR-task phase begins, Captain shall prompt Coder:
65
65
 
66
- > > <caller-input>
67
- > > <ir-number>
68
- > > <run-results>
66
+ > > Original request: <caller-input>
67
+ > > IR number: <ir-number>
68
+ > > Run results: <run-results>
69
69
  >
70
70
  > Read the identified IR and implement exactly its next unfinished task, including corresponding tests or specs if any.
71
71
  > Do not implement a later task in this phase.
@@ -10,7 +10,7 @@ export type CodePlaybookHostCapabilities = PlaybookHostConstructionCapabilities
10
10
  * runtime text. The generic composer preserves the marker itself; CODE's
11
11
  * override additionally keeps a multiline value inside that quote.
12
12
  */
13
- declare function composePlayerPrompt(input: PlayerInput, promptIdentity: XStatePromptIdentity): string;
13
+ declare function composePlayerPrompt(input: PlayerInput, promptIdentity: XStatePromptIdentity, resuming?: boolean): string;
14
14
  export declare const _internal: {
15
15
  composePlayerPrompt: typeof composePlayerPrompt;
16
16
  VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string>;
@@ -10,11 +10,10 @@
10
10
  // the first phase decides among four semantic outcomes
11
11
  // Nested call: literal review target through the shared bridge
12
12
  // Compat: artifact schema 3 / runtime ABI 1
13
- import { createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
13
+ import { createXStatePlaybookRuntime, composePlayerContinuation, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
14
14
  import { codingMachine, } from './code.fsm.js';
15
15
  const OPTION_KEYS = new Set(['runResults']);
16
16
  const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
17
- 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.';
18
17
  const VERBATIM_PAYLOAD_FIELDS = new Set([
19
18
  'coderOutput',
20
19
  ]);
@@ -52,11 +51,11 @@ function quoteContinuation(value) {
52
51
  * runtime text. The generic composer preserves the marker itself; CODE's
53
52
  * override additionally keeps a multiline value inside that quote.
54
53
  */
55
- function composePlayerPrompt(input, promptIdentity) {
54
+ function composePlayerPrompt(input, promptIdentity, resuming = false) {
56
55
  const fields = input;
57
56
  const template = input.prompt
58
57
  .split('\n')
59
- .filter((line) => !(line === '> <run-results>' && input.runResults.length === 0))
58
+ .filter((line) => !(line === '> Run results: <run-results>' && input.runResults.length === 0))
60
59
  .join('\n');
61
60
  const body = template.replace(PLACEHOLDER, (match, token, offset, source) => {
62
61
  const value = token === 'coder-llm'
@@ -66,18 +65,9 @@ function composePlayerPrompt(input, promptIdentity) {
66
65
  return match;
67
66
  const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
68
67
  const literal = source.slice(lineStart, offset);
69
- return literal === '> ' ? quoteContinuation(value) : value;
68
+ return literal.startsWith('> ') ? quoteContinuation(value) : value;
70
69
  });
71
- if (input.pendingBossQuestion === undefined ||
72
- input.bossReply === undefined) {
73
- return body;
74
- }
75
- return [
76
- CONTINUATION_PREAMBLE,
77
- `Boss question:\n${input.pendingBossQuestion.question}`,
78
- `Boss reply:\n${input.bossReply}`,
79
- body,
80
- ].join('\n\n');
70
+ return composePlayerContinuation(input, body, resuming);
81
71
  }
82
72
  export const _internal = {
83
73
  composePlayerPrompt,
@@ -177,7 +167,7 @@ const runtimeSpec = {
177
167
  },
178
168
  },
179
169
  },
180
- composePlayerPrompt: (input, promptIdentity) => composePlayerPrompt(input, promptIdentity),
170
+ composePlayerPrompt: (input, promptIdentity, resuming) => composePlayerPrompt(input, promptIdentity, resuming),
181
171
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
182
172
  controlContextFields: ['phase'],
183
173
  unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
@@ -13,6 +13,7 @@
13
13
 
14
14
  import {
15
15
  createXStatePlaybookRuntime,
16
+ composePlayerContinuation,
16
17
  snapshotJsonValue,
17
18
  type PlaybookPlayerInput,
18
19
  type XStatePlaybookRuntimeFactory,
@@ -85,8 +86,6 @@ export type CodePlaybookHostCapabilities =
85
86
 
86
87
  const OPTION_KEYS = new Set(['runResults']);
87
88
  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
89
 
91
90
  const VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string> = new Set([
92
91
  'coderOutput',
@@ -135,12 +134,13 @@ function quoteContinuation(value: string): string {
135
134
  function composePlayerPrompt(
136
135
  input: PlayerInput,
137
136
  promptIdentity: XStatePromptIdentity,
137
+ resuming = false,
138
138
  ): string {
139
139
  const fields = input as unknown as Record<string, unknown>;
140
140
  const template = input.prompt
141
141
  .split('\n')
142
142
  .filter(
143
- (line) => !(line === '> <run-results>' && input.runResults.length === 0),
143
+ (line) => !(line === '> Run results: <run-results>' && input.runResults.length === 0),
144
144
  )
145
145
  .join('\n');
146
146
  const body = template.replace(
@@ -153,21 +153,10 @@ function composePlayerPrompt(
153
153
  if (typeof value !== 'string') return match;
154
154
  const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
155
155
  const literal = source.slice(lineStart, offset);
156
- return literal === '> ' ? quoteContinuation(value) : value;
156
+ return literal.startsWith('> ') ? quoteContinuation(value) : value;
157
157
  },
158
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');
159
+ return composePlayerContinuation(input, body, resuming);
171
160
  }
172
161
 
173
162
  export const _internal = {
@@ -273,7 +262,8 @@ const runtimeSpec = {
273
262
  composePlayerPrompt: (
274
263
  input: PlaybookPlayerInput,
275
264
  promptIdentity: XStatePromptIdentity,
276
- ) => composePlayerPrompt(input as PlayerInput, promptIdentity),
265
+ resuming?: boolean,
266
+ ) => composePlayerPrompt(input as PlayerInput, promptIdentity, resuming),
277
267
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
278
268
  controlContextFields: ['phase'],
279
269
  unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,