@sublang/playbook 13.1.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 (38) 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 +40 -2
  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.playbook/playbook.config.template.yaml +16 -2
  18. package/reference/sdlc/dev.md +24 -3
  19. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +100 -9
  20. package/reference/sdlc/dev.playbook/dev.fsm.js +235 -18
  21. package/reference/sdlc/dev.playbook/dev.fsm.ts +368 -24
  22. package/reference/sdlc/dev.playbook/dev.gears.md +45 -7
  23. package/reference/sdlc/dev.playbook/dev.playbook.js +13 -1
  24. package/reference/sdlc/dev.playbook/dev.playbook.ts +13 -1
  25. package/reference/sdlc/dev.playbook/dev.registry.d.ts +1 -1
  26. package/reference/sdlc/dev.playbook/dev.registry.js +7 -2
  27. package/reference/sdlc/dev.playbook/dev.registry.ts +7 -2
  28. package/reference/sdlc/pr.md +108 -0
  29. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +352 -0
  30. package/reference/sdlc/pr.playbook/pr.fsm.js +902 -0
  31. package/reference/sdlc/pr.playbook/pr.fsm.ts +1251 -0
  32. package/reference/sdlc/pr.playbook/pr.gears.md +155 -0
  33. package/reference/sdlc/pr.playbook/pr.playbook.d.ts +27 -0
  34. package/reference/sdlc/pr.playbook/pr.playbook.js +133 -0
  35. package/reference/sdlc/pr.playbook/pr.playbook.ts +235 -0
  36. package/reference/sdlc/pr.playbook/pr.registry.d.ts +40 -0
  37. package/reference/sdlc/pr.playbook/pr.registry.js +68 -0
  38. package/reference/sdlc/pr.playbook/pr.registry.ts +124 -0
@@ -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;
@@ -78,8 +78,8 @@ players:
78
78
  # `roles` are launcher-owned; every other key is that playbook's option
79
79
  # slice.
80
80
  #
81
- # Every manifest role is bound explicitly. CODE, REVIEW, and DECIDE
82
- # intentionally reuse the same two stable players, while DEV keeps its own
81
+ # Every manifest role is bound explicitly. CODE, REVIEW, DECIDE, BRANCH, and
82
+ # PR intentionally reuse the same two stable players, while DEV keeps its own
83
83
  # Analyst; change an id to isolate its conversation.
84
84
  playbooks:
85
85
  code:
@@ -103,3 +103,17 @@ playbooks:
103
103
  from: "@sublang/playbook/dev/registry"
104
104
  roles:
105
105
  analyst: dev.analyst
106
+
107
+ # Pull-request delivery for DEV (DR-050). BRANCH and PR bind Coder to the
108
+ # same dev.coder player as CODE and REVIEW on purpose: the Coder that read
109
+ # the issue and named the branch makes the commits and then describes
110
+ # them in the pull request. Change an id to isolate its conversation.
111
+ branch:
112
+ from: "@sublang/playbook/branch/registry"
113
+ roles:
114
+ coder: dev.coder
115
+
116
+ pr:
117
+ from: "@sublang/playbook/pr/registry"
118
+ roles:
119
+ coder: dev.coder
@@ -27,13 +27,14 @@ Do not change files or commit while planning or discussing the request.
27
27
  - If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.
28
28
  - If implementation can proceed under the existing decisions, choose `code`.
29
29
  - If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.
30
+ - If the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery, read the issue and its comments as part of the analysis (`gh issue view --comments` with the issue number) and choose `code via pull request` or `decide then code via pull request` in place of `code` or `decide then code`.
30
31
 
31
32
  A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.
32
33
  Do not choose `decide then code` merely because the work is large.
33
34
  Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.
34
35
  ```
35
36
 
36
- The planning result has four semantic outcomes: needs Boss reply, discussion complete, code, and decide then code.
37
+ The planning result has six semantic outcomes: needs Boss reply, discussion complete, code, decide then code, code via pull request, and decide then code via pull request.
37
38
  Each outcome requires affirmative support in Analyst's result; absence of a reason to choose another outcome is not support.
38
39
  No outcome depends on a fixed presentation format of Analyst's reply.
39
40
  `dev` shall act on the accepted outcome itself and shall not return to the session Captain for another routing decision.
@@ -66,7 +67,27 @@ Only after `decide` succeeds shall `dev` call playbook `code` with the developme
66
67
 
67
68
  `dev` shall not separately call `review` for the design scope already reviewed by `decide`.
68
69
 
70
+ For code via pull request and decide then code via pull request, `dev` shall first call playbook `branch` with the development request, relevant discussion context, and planning result in quotes (`>`).
71
+
72
+ > Original request: <development-request>
73
+ > Prior discussion: <discussion-context>
74
+ > Planning result: <planning-result>
75
+
76
+ Only after `branch` succeeds shall `dev` continue with the `code` call for code via pull request, or the `decide` call and then the `code` call for decide then code via pull request, each with the same input as its plain path above.
77
+
78
+ On a plain path, `code` success completes `dev`.
79
+ On a pull-request path, only after `code` succeeds shall `dev` call playbook `pr` with the development request, issue summary, branch, base revision, last `code`-owned commit, and exact final evaluated repository revision in quotes (`>`).
80
+
81
+ > Original request: <development-request>
82
+ > Issue summary: <issue-summary>
83
+ > Branch: <branch>
84
+ > Base revision: <base-revision>
85
+ > CODE commit: <last-code-commit>
86
+ > Evaluated revision: <final-evaluated-revision>
87
+
88
+ A plain request calls neither `branch` nor `pr`.
89
+
69
90
  `dev` completes with the successful result of its final child call.
70
- If a child returns an authored abort or failure, or a terminal result that does not prove the success required for the selected path, `dev` shall start no later child and shall relay that canonical result.
91
+ If a child returns an authored abort or failure, or a terminal result that does not prove the success required for the selected path, `dev` shall start no later child and shall relay that canonical result; a `branch` or `pr` failure ends `dev` under this same rule.
71
92
  If a child call fails outside its authored result contract, `dev` shall park as failed and retain the control-plane error.
72
- `dev` shall consume commit identities only from each child's canonical structured result, never from player prose.
93
+ `dev` shall consume commit, revision, branch, and pull-request identities only from each child's canonical structured result, never from player prose.