@sublang/playbook 13.0.0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/package.json +2 -2
  2. package/reference/sdlc/code.md +7 -0
  3. package/reference/sdlc/code.playbook/code.fsm.js +5 -5
  4. package/reference/sdlc/code.playbook/code.fsm.ts +5 -5
  5. package/reference/sdlc/code.playbook/code.gears.md +5 -5
  6. package/reference/sdlc/code.playbook/code.playbook.d.ts +1 -1
  7. package/reference/sdlc/code.playbook/code.playbook.js +6 -16
  8. package/reference/sdlc/code.playbook/code.playbook.ts +7 -17
  9. package/reference/sdlc/code.playbook/playbook-captain.js +4 -1
  10. package/reference/sdlc/code.playbook/playbook-captain.ts +4 -1
  11. package/reference/sdlc/decide.md +2 -0
  12. package/reference/sdlc/decide.playbook/decide.fsm.js +1 -1
  13. package/reference/sdlc/decide.playbook/decide.fsm.ts +1 -1
  14. package/reference/sdlc/decide.playbook/decide.gears.md +2 -2
  15. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +1 -2
  16. package/reference/sdlc/decide.playbook/decide.playbook.js +8 -22
  17. package/reference/sdlc/decide.playbook/decide.playbook.ts +8 -25
  18. package/reference/sdlc/dev.md +21 -1
  19. package/reference/sdlc/dev.playbook/dev.fsm.js +10 -10
  20. package/reference/sdlc/dev.playbook/dev.fsm.ts +13 -13
  21. package/reference/sdlc/dev.playbook/dev.gears.md +15 -15
  22. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +1 -1
  23. package/reference/sdlc/dev.playbook/dev.playbook.js +7 -17
  24. package/reference/sdlc/dev.playbook/dev.playbook.ts +8 -20
  25. package/reference/sdlc/review.md +18 -0
  26. package/reference/sdlc/review.playbook/review.fsm.js +8 -8
  27. package/reference/sdlc/review.playbook/review.fsm.ts +8 -8
  28. package/reference/sdlc/review.playbook/review.gears.md +8 -8
  29. package/reference/sdlc/review.playbook/review.playbook.d.ts +1 -1
  30. package/reference/sdlc/review.playbook/review.playbook.js +5 -15
  31. package/reference/sdlc/review.playbook/review.playbook.ts +6 -16
  32. package/slc/link.md +12 -24
  33. package/src/runtime.d.ts +2 -0
  34. package/src/runtime.ts +2 -0
  35. package/src/xstate-playbook-runtime.d.ts +4 -2
  36. package/src/xstate-playbook-runtime.js +18 -13
  37. package/src/xstate-playbook-runtime.ts +26 -14
@@ -137,9 +137,9 @@ export type DevEvent =
137
137
  };
138
138
 
139
139
  const PLAN_ANALYSIS_PROMPT = [
140
- '> <development-request>',
141
- '> <discussion-context>',
142
- '> <run-results>',
140
+ '> Original request: <development-request>',
141
+ '> Prior discussion: <discussion-context>',
142
+ '> Run results: <run-results>',
143
143
  '',
144
144
  'Inspect the request and the relevant repository and specs only as needed to determine the smallest sound next step.',
145
145
  'Do not change files or commit while planning or discussing the request.',
@@ -590,11 +590,11 @@ function archivedExchanges(
590
590
  ];
591
591
  }
592
592
 
593
- function quotedRelay(values: readonly string[]): string {
593
+ function quotedRelay(values: readonly (readonly [string, string])[]): string {
594
594
  return values
595
- .filter((value) => value.length > 0)
596
- .map((value) =>
597
- value
595
+ .filter(([, value]) => value.length > 0)
596
+ .map(([label, value]) =>
597
+ `${label}: ${value}`
598
598
  .split('\n')
599
599
  .map((line) => `> ${line}`)
600
600
  .join('\n'),
@@ -602,11 +602,11 @@ function quotedRelay(values: readonly string[]): string {
602
602
  .join('\n');
603
603
  }
604
604
 
605
- function planningRelayValues(context: DevContext): readonly string[] {
605
+ function planningRelayValues(context: DevContext): readonly (readonly [string, string])[] {
606
606
  return [
607
- context.developmentRequest ?? '',
608
- renderDiscussionContext(context.discussionExchanges),
609
- context.planningResult ?? '',
607
+ ['Original request', context.developmentRequest ?? ''],
608
+ ['Prior discussion', renderDiscussionContext(context.discussionExchanges)],
609
+ ['Planning result', context.planningResult ?? ''],
610
610
  ];
611
611
  }
612
612
 
@@ -621,8 +621,8 @@ function decideCallText(context: DevContext): string {
621
621
  function codeAfterDecideCallText(context: DevContext): string {
622
622
  return quotedRelay([
623
623
  ...planningRelayValues(context),
624
- context.decideCommit ?? '',
625
- context.evaluatedRevision ?? '',
624
+ ['DECIDE commit', context.decideCommit ?? ''],
625
+ ['Evaluated revision', context.evaluatedRevision ?? ''],
626
626
  ]);
627
627
  }
628
628
 
@@ -16,9 +16,9 @@ It coordinates existing playbooks and owns no repository commit itself.
16
16
 
17
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
18
 
19
- > > <development-request>
20
- > > <discussion-context>
21
- > > <run-results>
19
+ > > Original request: <development-request>
20
+ > > Prior discussion: <discussion-context>
21
+ > > Run results: <run-results>
22
22
  >
23
23
  > Inspect the request and the relevant repository and specs only as needed to determine the smallest sound next step.
24
24
  > Do not change files or commit while planning or discussing the request.
@@ -40,7 +40,7 @@ Results:
40
40
  Workflow outcomes:
41
41
  - The planning result has four semantic outcomes: needs Boss reply, discussion complete, code, and decide then code.
42
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.
43
+ - Needs Boss reply uses the standard Boss-question suspension with Analyst's complete response; after Boss replies, `dev` resumes with the answer in the same Analyst conversation; the previous question is included only when that conversation must start fresh.
44
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
45
  - `dev` acts on the accepted outcome itself and does not return to the session Captain for another routing decision.
46
46
 
@@ -50,9 +50,9 @@ Workflow outcomes:
50
50
 
51
51
  When the accepted planning result selects `code`, Captain shall call playbook `code`:
52
52
 
53
- > > <development-request>
54
- > > <discussion-context>
55
- > > <planning-result>
53
+ > > Original request: <development-request>
54
+ > > Prior discussion: <discussion-context>
55
+ > > Planning result: <planning-result>
56
56
 
57
57
  Workflow outcomes:
58
58
  - `code` success completes `dev` with the successful `code` result.
@@ -64,9 +64,9 @@ Workflow outcomes:
64
64
 
65
65
  When the accepted planning result selects `decide then code`, Captain shall call playbook `decide`:
66
66
 
67
- > > <development-request>
68
- > > <discussion-context>
69
- > > <planning-result>
67
+ > > Original request: <development-request>
68
+ > > Prior discussion: <discussion-context>
69
+ > > Planning result: <planning-result>
70
70
 
71
71
  Workflow outcomes:
72
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.
@@ -78,11 +78,11 @@ Workflow outcomes:
78
78
 
79
79
  When `decide` succeeds, Captain shall call playbook `code`:
80
80
 
81
- > > <development-request>
82
- > > <discussion-context>
83
- > > <planning-result>
84
- > > <decide-commit>
85
- > > <evaluated-revision>
81
+ > > Original request: <development-request>
82
+ > > Prior discussion: <discussion-context>
83
+ > > Planning result: <planning-result>
84
+ > > DECIDE commit: <decide-commit>
85
+ > > Evaluated revision: <evaluated-revision>
86
86
 
87
87
  Workflow outcomes:
88
88
  - `code` success completes `dev` with the successful `code` result.
@@ -11,7 +11,7 @@ export type DevPlaybookHostCapabilities = PlaybookHostConstructionCapabilities &
11
11
  * override additionally keeps a multiline value inside that quote and drops
12
12
  * the optional relays that have no value yet.
13
13
  */
14
- declare function composePlayerPrompt(input: PlayerInput): string;
14
+ declare function composePlayerPrompt(input: PlayerInput, resuming?: boolean): string;
15
15
  export declare const _internal: {
16
16
  composePlayerPrompt: typeof composePlayerPrompt;
17
17
  VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string>;
@@ -11,11 +11,10 @@
11
11
  // carried verbatim
12
12
  // Nested calls: literal code and decide targets through the shared bridge
13
13
  // Compat: artifact schema 3 / runtime ABI 1
14
- import { createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
14
+ import { createXStatePlaybookRuntime, composePlayerContinuation, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
15
15
  import { devMachine, } from './dev.fsm.js';
16
16
  const OPTION_KEYS = new Set(['runResults']);
17
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
18
  const VERBATIM_PAYLOAD_FIELDS = new Set([
20
19
  'planningResult',
21
20
  ]);
@@ -54,13 +53,13 @@ function quotedContinuation(value) {
54
53
  * override additionally keeps a multiline value inside that quote and drops
55
54
  * the optional relays that have no value yet.
56
55
  */
57
- function composePlayerPrompt(input) {
56
+ function composePlayerPrompt(input, resuming = false) {
58
57
  const fields = input;
59
58
  const template = input.prompt
60
59
  .split('\n')
61
- .filter((line) => !(line === '> <discussion-context>' &&
60
+ .filter((line) => !(line === '> Prior discussion: <discussion-context>' &&
62
61
  input.discussionContext.length === 0) &&
63
- !(line === '> <run-results>' && input.runResults.length === 0))
62
+ !(line === '> Run results: <run-results>' && input.runResults.length === 0))
64
63
  .join('\n');
65
64
  const body = template.replace(PLACEHOLDER, (match, token, offset, source) => {
66
65
  const value = fields[placeholderField(token)];
@@ -68,18 +67,9 @@ function composePlayerPrompt(input) {
68
67
  return match;
69
68
  const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
70
69
  const literal = source.slice(lineStart, offset);
71
- return literal === '> ' ? quotedContinuation(value) : value;
70
+ return literal.startsWith('> ') ? quotedContinuation(value) : value;
72
71
  });
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');
72
+ return composePlayerContinuation(input, body, resuming);
83
73
  }
84
74
  export const _internal = {
85
75
  composePlayerPrompt,
@@ -133,7 +123,7 @@ const runtimeSpec = {
133
123
  },
134
124
  },
135
125
  },
136
- composePlayerPrompt: (input) => composePlayerPrompt(input),
126
+ composePlayerPrompt: (input, _identity, resuming) => composePlayerPrompt(input, resuming),
137
127
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
138
128
  controlContextFields: [],
139
129
  unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
@@ -14,6 +14,7 @@
14
14
 
15
15
  import {
16
16
  createXStatePlaybookRuntime,
17
+ composePlayerContinuation,
17
18
  snapshotJsonValue,
18
19
  type PlaybookPlayerInput,
19
20
  type XStatePlaybookRuntimeFactory,
@@ -85,8 +86,6 @@ export type DevPlaybookHostCapabilities =
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
  'planningResult',
@@ -133,17 +132,17 @@ function quotedContinuation(value: string): string {
133
132
  * override additionally keeps a multiline value inside that quote and drops
134
133
  * the optional relays that have no value yet.
135
134
  */
136
- function composePlayerPrompt(input: PlayerInput): string {
135
+ function composePlayerPrompt(input: PlayerInput, resuming = false): string {
137
136
  const fields = input as unknown as Record<string, unknown>;
138
137
  const template = input.prompt
139
138
  .split('\n')
140
139
  .filter(
141
140
  (line) =>
142
141
  !(
143
- line === '> <discussion-context>' &&
142
+ line === '> Prior discussion: <discussion-context>' &&
144
143
  input.discussionContext.length === 0
145
144
  ) &&
146
- !(line === '> <run-results>' && input.runResults.length === 0),
145
+ !(line === '> Run results: <run-results>' && input.runResults.length === 0),
147
146
  )
148
147
  .join('\n');
149
148
  const body = template.replace(
@@ -153,21 +152,10 @@ function composePlayerPrompt(input: PlayerInput): string {
153
152
  if (typeof value !== 'string') return match;
154
153
  const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
155
154
  const literal = source.slice(lineStart, offset);
156
- return literal === '> ' ? quotedContinuation(value) : value;
155
+ return literal.startsWith('> ') ? quotedContinuation(value) : value;
157
156
  },
158
157
  );
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');
158
+ return composePlayerContinuation(input, body, resuming);
171
159
  }
172
160
 
173
161
  export const _internal = {
@@ -224,8 +212,8 @@ const runtimeSpec = {
224
212
  },
225
213
  },
226
214
  },
227
- composePlayerPrompt: (input: PlaybookPlayerInput) =>
228
- composePlayerPrompt(input as PlayerInput),
215
+ composePlayerPrompt: (input: PlaybookPlayerInput, _identity, resuming) =>
216
+ composePlayerPrompt(input as PlayerInput, resuming),
229
217
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
230
218
  controlContextFields: [],
231
219
  unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
@@ -27,6 +27,10 @@ Keep to the original intent and follow what it asks.
27
27
  When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.
28
28
  ```
29
29
 
30
+ Captain shall append the caller’s input in quotes (`>`):
31
+
32
+ > Original request: <caller-input>
33
+
30
34
  After every review-fix commit, Captain shall give Reviewer the following instruction:
31
35
 
32
36
  ```markdown
@@ -35,6 +39,12 @@ Keep to the original intent and follow what it asks.
35
39
  Read the latest review-fix commit's message and see Coder's feedback below.
36
40
  ```
37
41
 
42
+ Captain shall append the round’s context in quotes (`>`):
43
+
44
+ > Original request: <caller-input>
45
+ > Latest commit: <latest-commit>
46
+ > Coder output: <coder-output>
47
+
38
48
  When Coder rejects every finding and makes no commit, Captain shall give Reviewer the following instruction:
39
49
 
40
50
  ```markdown
@@ -42,6 +52,11 @@ No new commit was made because Coder rejected every finding.
42
52
  See Coder's feedback below.
43
53
  ```
44
54
 
55
+ Captain shall append the round’s context in quotes (`>`):
56
+
57
+ > Original request: <caller-input>
58
+ > Coder output: <coder-output>
59
+
45
60
  At the start of *every* review round, Captain shall relay to Reviewer the original intent, the review scope and context, the exact repository revision being evaluated, any Coder feedback from the preceding round, and any relevant run results, in quotes (`>`) after the instruction.
46
61
 
47
62
  At the start of *every* review round, Captain shall append the following instruction to the end of the prompt:
@@ -70,6 +85,9 @@ No review transition shall depend on numbering or any fixed presentation format
70
85
 
71
86
  When Reviewer raises or keeps any finding, Captain shall relay the original intent, the review scope and context, the exact repository revision being evaluated, the Reviewer's findings, and any relevant run results to Coder, in quotes (`>`), along with the following prompt:
72
87
 
88
+ > Original request: <caller-input>
89
+ > Reviewer findings: <reviewer-output>
90
+
73
91
  ```markdown
74
92
  For each review item, accept or reject it.
75
93
  Before deciding, understand the full picture and think systematically about the underlying design.
@@ -27,7 +27,7 @@ const INITIAL_REVIEW_PROMPT = [
27
27
  'Keep to the original intent and follow what it asks.',
28
28
  'When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.',
29
29
  '',
30
- '> <caller-input>',
30
+ '> Original request: <caller-input>',
31
31
  '',
32
32
  SHARED_REVIEW_INSTRUCTION,
33
33
  ].join('\n');
@@ -36,9 +36,9 @@ const POST_COMMIT_REVIEW_PROMPT = [
36
36
  'Keep to the original intent and follow what it asks.',
37
37
  "Read the latest review-fix commit's message and see Coder's feedback below.",
38
38
  '',
39
- '> <caller-input>',
40
- '> <latest-commit>',
41
- '> <coder-output>',
39
+ '> Original request: <caller-input>',
40
+ '> Latest commit: <latest-commit>',
41
+ '> Coder output: <coder-output>',
42
42
  '',
43
43
  SHARED_REVIEW_INSTRUCTION,
44
44
  ].join('\n');
@@ -46,14 +46,14 @@ const REBUTTAL_REVIEW_PROMPT = [
46
46
  'No new commit was made because Coder rejected every finding.',
47
47
  "See Coder's feedback below.",
48
48
  '',
49
- '> <caller-input>',
50
- '> <coder-output>',
49
+ '> Original request: <caller-input>',
50
+ '> Coder output: <coder-output>',
51
51
  '',
52
52
  SHARED_REVIEW_INSTRUCTION,
53
53
  ].join('\n');
54
54
  const CODER_DISPOSITION_PROMPT = [
55
- '> <caller-input>',
56
- '> <reviewer-output>',
55
+ '> Original request: <caller-input>',
56
+ '> Reviewer findings: <reviewer-output>',
57
57
  '',
58
58
  'For each review item, accept or reject it.',
59
59
  'Before deciding, understand the full picture and think systematically about the underlying design.',
@@ -113,7 +113,7 @@ const INITIAL_REVIEW_PROMPT = [
113
113
  'Keep to the original intent and follow what it asks.',
114
114
  'When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.',
115
115
  '',
116
- '> <caller-input>',
116
+ '> Original request: <caller-input>',
117
117
  '',
118
118
  SHARED_REVIEW_INSTRUCTION,
119
119
  ].join('\n');
@@ -123,9 +123,9 @@ const POST_COMMIT_REVIEW_PROMPT = [
123
123
  'Keep to the original intent and follow what it asks.',
124
124
  "Read the latest review-fix commit's message and see Coder's feedback below.",
125
125
  '',
126
- '> <caller-input>',
127
- '> <latest-commit>',
128
- '> <coder-output>',
126
+ '> Original request: <caller-input>',
127
+ '> Latest commit: <latest-commit>',
128
+ '> Coder output: <coder-output>',
129
129
  '',
130
130
  SHARED_REVIEW_INSTRUCTION,
131
131
  ].join('\n');
@@ -134,15 +134,15 @@ const REBUTTAL_REVIEW_PROMPT = [
134
134
  'No new commit was made because Coder rejected every finding.',
135
135
  "See Coder's feedback below.",
136
136
  '',
137
- '> <caller-input>',
138
- '> <coder-output>',
137
+ '> Original request: <caller-input>',
138
+ '> Coder output: <coder-output>',
139
139
  '',
140
140
  SHARED_REVIEW_INSTRUCTION,
141
141
  ].join('\n');
142
142
 
143
143
  const CODER_DISPOSITION_PROMPT = [
144
- '> <caller-input>',
145
- '> <reviewer-output>',
144
+ '> Original request: <caller-input>',
145
+ '> Reviewer findings: <reviewer-output>',
146
146
  '',
147
147
  'For each review item, accept or reject it.',
148
148
  'Before deciding, understand the full picture and think systematically about the underlying design.',
@@ -23,7 +23,7 @@ When the caller starts a review, Captain shall relay the complete caller input t
23
23
  > Keep to the original intent and follow what it asks.
24
24
  > When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.
25
25
  >
26
- > > <caller-input>
26
+ > > Original request: <caller-input>
27
27
  >
28
28
  > Understand the full picture and think systematically about the underlying design.
29
29
  > Continue to identify issues or improvements, if any, without duplication.
@@ -50,8 +50,8 @@ Results:
50
50
 
51
51
  When Reviewer raises or keeps any finding, Captain shall relay the caller input and Reviewer's findings to Coder with the disposition prompt:
52
52
 
53
- > > <caller-input>
54
- > > <reviewer-output>
53
+ > > Original request: <caller-input>
54
+ > > Reviewer findings: <reviewer-output>
55
55
  >
56
56
  > For each review item, accept or reject it.
57
57
  > Before deciding, understand the full picture and think systematically about the underlying design.
@@ -82,9 +82,9 @@ When Coder makes a review-fix commit, Captain shall relay the caller input, the
82
82
  > Keep to the original intent and follow what it asks.
83
83
  > Read the latest review-fix commit's message and see Coder's feedback below.
84
84
  >
85
- > > <caller-input>
86
- > > <latest-commit>
87
- > > <coder-output>
85
+ > > Original request: <caller-input>
86
+ > > Latest commit: <latest-commit>
87
+ > > Coder output: <coder-output>
88
88
  >
89
89
  > Understand the full picture and think systematically about the underlying design.
90
90
  > Continue to identify issues or improvements, if any, without duplication.
@@ -114,8 +114,8 @@ When Coder rejects every finding and makes no commit, Captain shall relay the ca
114
114
  > No new commit was made because Coder rejected every finding.
115
115
  > See Coder's feedback below.
116
116
  >
117
- > > <caller-input>
118
- > > <coder-output>
117
+ > > Original request: <caller-input>
118
+ > > Coder output: <coder-output>
119
119
  >
120
120
  > Understand the full picture and think systematically about the underlying design.
121
121
  > Continue to identify issues or improvements, if any, without duplication.
@@ -6,7 +6,7 @@ export type { CaptainCallOptions, CaptainResult, JsonValue, NormalizedError, Pla
6
6
  export type ReviewPlaybookOptions = ReviewInput;
7
7
  export type ReviewPlaybookHostCapabilities = PlaybookHostConstructionCapabilities & XStatePlaybookRuntimeConstruction<ReviewPlaybookOptions, object>['hostCapabilities'];
8
8
  /** Keep every line of a relayed runtime value inside its authored quote. */
9
- declare function composePlayerPrompt(input: PlayerInput, promptIdentity: XStatePromptIdentity): string;
9
+ declare function composePlayerPrompt(input: PlayerInput, promptIdentity: XStatePromptIdentity, resuming?: boolean): string;
10
10
  export declare const _internal: {
11
11
  composePlayerPrompt: typeof composePlayerPrompt;
12
12
  VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string>;
@@ -10,10 +10,9 @@
10
10
  // carried verbatim; latestCommit is receipt-owned effect
11
11
  // evidence, never taken from either player's prose
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 { reviewMachine, } from './review.fsm.js';
15
15
  const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
16
- 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.';
17
16
  const VERBATIM_PAYLOAD_FIELDS = new Set([
18
17
  'reviewerOutput',
19
18
  'coderOutput',
@@ -41,7 +40,7 @@ function quotedContinuation(value) {
41
40
  return value.replaceAll('\n', '\n> ');
42
41
  }
43
42
  /** Keep every line of a relayed runtime value inside its authored quote. */
44
- function composePlayerPrompt(input, promptIdentity) {
43
+ function composePlayerPrompt(input, promptIdentity, resuming = false) {
45
44
  const fields = input;
46
45
  const body = input.prompt.replace(PLACEHOLDER, (match, token, offset, source) => {
47
46
  const value = token === 'coder-llm'
@@ -52,20 +51,11 @@ function composePlayerPrompt(input, promptIdentity) {
52
51
  if (typeof value !== 'string')
53
52
  return match;
54
53
  const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
55
- return source.slice(lineStart, offset) === '> '
54
+ return source.slice(lineStart, offset).startsWith('> ')
56
55
  ? quotedContinuation(value)
57
56
  : value;
58
57
  });
59
- if (input.pendingBossQuestion === undefined ||
60
- input.bossReply === undefined) {
61
- return body;
62
- }
63
- return [
64
- CONTINUATION_PREAMBLE,
65
- `Boss question:\n${input.pendingBossQuestion.question}`,
66
- `Boss reply:\n${input.bossReply}`,
67
- body,
68
- ].join('\n\n');
58
+ return composePlayerContinuation(input, body, resuming);
69
59
  }
70
60
  export const _internal = {
71
61
  composePlayerPrompt,
@@ -175,7 +165,7 @@ const runtimeSpec = {
175
165
  },
176
166
  },
177
167
  },
178
- composePlayerPrompt: (input, promptIdentity) => composePlayerPrompt(input, promptIdentity),
168
+ composePlayerPrompt: (input, promptIdentity, resuming) => composePlayerPrompt(input, promptIdentity, resuming),
179
169
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
180
170
  controlContextFields: [],
181
171
  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,
@@ -84,8 +85,6 @@ export type ReviewPlaybookHostCapabilities =
84
85
  XStatePlaybookRuntimeConstruction<ReviewPlaybookOptions, object>['hostCapabilities'];
85
86
 
86
87
  const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
87
- const CONTINUATION_PREAMBLE =
88
- 'You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.';
89
88
 
90
89
  const VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string> = new Set([
91
90
  'reviewerOutput',
@@ -124,6 +123,7 @@ function quotedContinuation(value: string): string {
124
123
  function composePlayerPrompt(
125
124
  input: PlayerInput,
126
125
  promptIdentity: XStatePromptIdentity,
126
+ resuming = false,
127
127
  ): string {
128
128
  const fields = input as unknown as Record<string, unknown>;
129
129
  const body = input.prompt.replace(
@@ -137,23 +137,12 @@ function composePlayerPrompt(
137
137
  : fields[placeholderField(token)];
138
138
  if (typeof value !== 'string') return match;
139
139
  const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
140
- return source.slice(lineStart, offset) === '> '
140
+ return source.slice(lineStart, offset).startsWith('> ')
141
141
  ? quotedContinuation(value)
142
142
  : value;
143
143
  },
144
144
  );
145
- if (
146
- input.pendingBossQuestion === undefined ||
147
- input.bossReply === undefined
148
- ) {
149
- return body;
150
- }
151
- return [
152
- CONTINUATION_PREAMBLE,
153
- `Boss question:\n${input.pendingBossQuestion.question}`,
154
- `Boss reply:\n${input.bossReply}`,
155
- body,
156
- ].join('\n\n');
145
+ return composePlayerContinuation(input, body, resuming);
157
146
  }
158
147
 
159
148
  export const _internal = {
@@ -271,7 +260,8 @@ const runtimeSpec = {
271
260
  composePlayerPrompt: (
272
261
  input: PlaybookPlayerInput,
273
262
  promptIdentity: XStatePromptIdentity,
274
- ) => composePlayerPrompt(input as PlayerInput, promptIdentity),
263
+ resuming?: boolean,
264
+ ) => composePlayerPrompt(input as PlayerInput, promptIdentity, resuming),
275
265
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
276
266
  controlContextFields: [],
277
267
  unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
package/slc/link.md CHANGED
@@ -489,6 +489,7 @@ interface PlaybookPorts {
489
489
 
490
490
  interface PlayerCallOptions {
491
491
  resume: string | false;
492
+ freshPrompt?: string;
492
493
  }
493
494
 
494
495
  interface CaptainCallOptions {
@@ -865,30 +866,17 @@ Those blocks are outside the domain prompt body.
865
866
  The composer shall not inject a player-visible Boss-question instruction.
866
867
  Boss-question detection is adjudicator-facing: it comes from the state's `needsBossReply` result description, not from extra prompt text.
867
868
 
868
- When `PlayerInput` carries both `pendingBossQuestion` and `bossReply`, the
869
- composer shall prepend the continuation preamble and labelled Q&A blocks before
870
- ordinary structured blocks and before the domain prompt body:
871
-
872
- ```text
873
- You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.
874
-
875
- Boss question:
876
- <pendingBossQuestion.question>
877
-
878
- Boss reply:
879
- <bossReply>
880
-
881
- ```
882
-
883
- The continuation preamble is framework text supplied by the runtime.
884
- It is not part of the GEARS blockquote and shall not appear in `invoke.input.prompt`.
885
- The composer shall retain the blank line after the Boss reply before the next
886
- structured block or domain prompt, producing exactly two newline characters at
887
- that boundary.
888
- When implementing the prefix as an array joined with `"\n"`, the array needs
889
- two trailing empty strings after `bossReply`; one trailing empty string emits
890
- only one newline and is nonconformant. Equivalently, append `"\n\n"` exactly
891
- once before the following block or domain body.
869
+ When `PlayerInput` carries both `pendingBossQuestion` and `bossReply`, the composer receives an optional third `resuming` boolean after `promptIdentity`; absent means fresh.
870
+ The shared `composePlayerContinuation(input, body, resuming)` helper prefixes the verbatim Boss reply and, only for a fresh conversation, the pending question labeled `Your previous question:`.
871
+ A resumed call omits that question; a fresh call includes it before `Boss reply:`.
872
+ Both retain the authored task body exactly once, with two newlines between blocks.
873
+ These framework blocks never enter `invoke.input.prompt` or persisted FSM context.
874
+
875
+ The runtime chooses the compact prompt only after selecting a conversation token and carries the complete prompt as optional `PlayerCallOptions.freshPrompt`.
876
+ A host starting fresh after a definite pre-execution token rejection uses `freshPrompt`, when supplied, instead of the compact prompt.
877
+ Without that option, the given prompt remains complete.
878
+ Traces and host observations record the respective prompts actually sent.
879
+ Old composers may ignore the optional argument and retain their full prompt.
892
880
 
893
881
  ## Captain prompt composition
894
882
 
package/src/runtime.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface PlayerResult {
6
6
  }
7
7
  export interface PlayerCallOptions {
8
8
  resume: string | false;
9
+ /** Complete task and clarification context if the host must start fresh. */
10
+ freshPrompt?: string;
9
11
  }
10
12
  export interface PlayerSessionStore {
11
13
  select(roleId: string): string | false;
package/src/runtime.ts CHANGED
@@ -17,6 +17,8 @@ export interface PlayerResult {
17
17
 
18
18
  export interface PlayerCallOptions {
19
19
  resume: string | false;
20
+ /** Complete task and clarification context if the host must start fresh. */
21
+ freshPrompt?: string;
20
22
  }
21
23
 
22
24
  // DR-032: a composing host may supply one frame-local role view of the