@sublang/playbook 13.2.0 → 14.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.
- package/README.md +5 -5
- package/docs/configuration.md +14 -9
- package/docs/embedding.md +20 -1
- package/package.json +6 -1
- package/reference/sdlc/code.playbook/bin/launch-config.js +88 -5
- package/reference/sdlc/code.playbook/bin/playbook.js +4 -0
- package/reference/sdlc/code.playbook/bin/repository-effects.js +11 -3
- package/reference/sdlc/code.playbook/bin/run.js +4 -0
- package/reference/sdlc/code.playbook/bin/session-host.js +13 -3
- package/reference/sdlc/code.playbook/bin/session-store.js +2 -2
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +26 -1
- package/reference/sdlc/code.playbook/playbook-captain.js +233 -8
- package/reference/sdlc/code.playbook/playbook-captain.ts +298 -8
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +5 -6
- package/reference/sdlc/code.playbook/session-host.d.ts +26 -0
- package/reference/sdlc/dev.playbook/dev.playbook.d.ts +3 -10
- package/reference/sdlc/dev.playbook/dev.playbook.js +5 -5
- package/reference/sdlc/dev.playbook/dev.playbook.ts +7 -7
- package/reference/sdlc/pr.md +17 -4
- package/reference/sdlc/pr.playbook/pr.fsm.d.ts +17 -3
- package/reference/sdlc/pr.playbook/pr.fsm.js +49 -18
- package/reference/sdlc/pr.playbook/pr.fsm.ts +63 -20
- package/reference/sdlc/pr.playbook/pr.gears.md +20 -7
- package/reference/sdlc/pr.playbook/pr.playbook.js +2 -1
- package/reference/sdlc/pr.playbook/pr.playbook.ts +2 -1
- package/slc/experiments/fsm-scaffold-guidance.md +31 -0
- package/slc/gears2fsm.md +145 -58
- package/slc/link.md +158 -7
- package/slc/materialize-link.mjs +460 -0
- package/slc/optimize.md +46 -6
- package/slc/scaffold-fsm.mjs +225 -0
- package/slc/slc.pin-inputs.json +13 -0
- package/slc/text2gears.md +38 -3
- package/slc/workflow-contracts.json +371 -0
- package/src/xstate-playbook-runtime.js +35 -1
- package/src/xstate-playbook-runtime.ts +54 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { link, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const HEADING = /^### ([A-Za-z][\w-]*)$/;
|
|
11
|
+
const QUOTE = /^>[ \t]?(.*)$/;
|
|
12
|
+
const RESULT = /^- `([A-Za-z_$][A-Za-z0-9_$]*)`: (\S(?:.*\S)?)$/;
|
|
13
|
+
const HELP = 'Usage: node scaffold-fsm.mjs --source <GEARS.md> --out <new.fsm.ts>\nCreates an incomplete authoring scaffold; ordinary compiler checks remain required.\n';
|
|
14
|
+
|
|
15
|
+
function refuse(message) {
|
|
16
|
+
throw new Error(message);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A closed copying profile, not a GEARS semantic compiler. */
|
|
20
|
+
function sourceItems(source) {
|
|
21
|
+
if (/[\r\u2028\u2029\uFEFF]/.test(source)) refuse('unsupported line encoding; the scaffold profile requires LF text');
|
|
22
|
+
if (source.split('\n').some(line => /^\s*(```|~~~)/.test(line))) refuse('fenced source bodies are outside this profile');
|
|
23
|
+
const items = [];
|
|
24
|
+
let current;
|
|
25
|
+
for (const line of source.split('\n')) {
|
|
26
|
+
if (line.startsWith('###')) {
|
|
27
|
+
const heading = HEADING.exec(line);
|
|
28
|
+
if (!heading) refuse('unsupported item heading');
|
|
29
|
+
if (items.some(item => item.id === heading[1])) refuse('duplicate item id');
|
|
30
|
+
current = { id: heading[1], lines: [] };
|
|
31
|
+
items.push(current);
|
|
32
|
+
} else if (/^#{1,2} /.test(line)) {
|
|
33
|
+
current = undefined;
|
|
34
|
+
} else if (current) {
|
|
35
|
+
current.lines.push(line);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (items.length === 0) refuse('no supported item headings');
|
|
39
|
+
const prompted = [];
|
|
40
|
+
for (const { id, lines } of items) {
|
|
41
|
+
const first = lines.findIndex(line => QUOTE.test(line));
|
|
42
|
+
const preamble = lines.slice(0, first === -1 ? lines.length : first);
|
|
43
|
+
if (preamble.some(line => /Parallel group:|Captain shall run\s*:|Captain shall call playbook selected by/.test(line))) {
|
|
44
|
+
refuse(`${id}: script, dynamic or parallel item is outside this profile`);
|
|
45
|
+
}
|
|
46
|
+
if (first === -1) {
|
|
47
|
+
if (lines.some(line => /Captain shall|^Results\b/.test(line))) refuse(`${id}: acting item has no blockquote`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const before = preamble.join(' ');
|
|
51
|
+
const child = /\bCaptain shall call playbook (?:`([A-Za-z0-9][\w.-]*)`|([A-Za-z0-9][\w.-]*)):\s*$/.exec(before.trim());
|
|
52
|
+
const player = /\bCaptain shall (?:prompt ([A-Z][\w]*)|relay\b[^.]*?\bto ([A-Z][\w]*)):\s*$/.exec(before.trim());
|
|
53
|
+
if (!child && /\bCaptain shall call playbook\b/.test(before)) refuse(`${id}: unsupported nested-call form`);
|
|
54
|
+
if (!player && /\bCaptain shall (?:prompt|relay)\b/.test(before)) refuse(`${id}: unsupported delegated-role form`);
|
|
55
|
+
if (!child && !player && !/\bCaptain shall\b.*:\s*$/.test(before.trim())) refuse(`${id}: unsupported acting clause`);
|
|
56
|
+
if (/Results\s*:/.test(before)) refuse(`${id}: Results precedes its prompt`);
|
|
57
|
+
const prompt = [];
|
|
58
|
+
let cursor = first;
|
|
59
|
+
while (cursor < lines.length) {
|
|
60
|
+
const quote = QUOTE.exec(lines[cursor]);
|
|
61
|
+
if (!quote) break;
|
|
62
|
+
prompt.push(quote[1]);
|
|
63
|
+
cursor++;
|
|
64
|
+
}
|
|
65
|
+
const after = lines.slice(cursor);
|
|
66
|
+
if (after.some(line => QUOTE.test(line))) refuse(`${id}: noncontiguous prompt`);
|
|
67
|
+
const nonblank = after.filter(line => line.trim() !== '');
|
|
68
|
+
let results;
|
|
69
|
+
if (nonblank.some(line => /^Results\b/.test(line))) {
|
|
70
|
+
if (child) refuse(`${id}: nested-call Results are forbidden`);
|
|
71
|
+
if (nonblank[0] !== 'Results:') refuse(`${id}: Results must immediately follow its prompt`);
|
|
72
|
+
results = [];
|
|
73
|
+
for (const line of nonblank.slice(1)) {
|
|
74
|
+
const result = RESULT.exec(line);
|
|
75
|
+
if (!result) refuse(`${id}: malformed Results entry`);
|
|
76
|
+
if (results.some(([guard]) => guard === result[1])) refuse(`${id}: duplicate Results guard`);
|
|
77
|
+
results.push([result[1], result[2]]);
|
|
78
|
+
}
|
|
79
|
+
if (results.length === 0) refuse(`${id}: empty Results block`);
|
|
80
|
+
} else if (!child && nonblank.length > 0) {
|
|
81
|
+
refuse(`${id}: trailing acting-item prose is outside this profile`);
|
|
82
|
+
}
|
|
83
|
+
prompted.push({ id, actor: child ? 'playbook' : player ? 'player' : 'captain', prompt: prompt.join('\n'), results });
|
|
84
|
+
}
|
|
85
|
+
if (prompted.length === 0) refuse('no supported prompted items');
|
|
86
|
+
return prompted;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function sourceLicense(source) {
|
|
90
|
+
const header = /^(?:<!--[^]*?-->\s*)+/.exec(source)?.[0] ?? '';
|
|
91
|
+
return [...header.matchAll(/<!--\s*(SPDX-[^]*?)\s*-->/g)]
|
|
92
|
+
.flatMap(match => match[1].split('\n').map(line => `// ${line.trim()}`))
|
|
93
|
+
.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const CHILD_VALIDATION_IMPORTS = `import { validatePlaybookCallResult } from '@sublang/playbook/xstate-runtime';
|
|
97
|
+
import type { PlaybookCallResult } from '@sublang/playbook/runtime';`;
|
|
98
|
+
|
|
99
|
+
const CHILD_VALIDATION_HELPER = `export function authoredChildResult(error: unknown, expectedPlaybookId: string): PlaybookCallResult | undefined {
|
|
100
|
+
if (!(error instanceof Error)) return undefined;
|
|
101
|
+
try {
|
|
102
|
+
const result = validatePlaybookCallResult(
|
|
103
|
+
(error as Error & { result?: unknown }).result,
|
|
104
|
+
expectedPlaybookId,
|
|
105
|
+
);
|
|
106
|
+
return result.status !== 'ok' || result.terminal?.kind === 'failure' ? result : undefined;
|
|
107
|
+
} catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}`;
|
|
111
|
+
|
|
112
|
+
function render(source, items) {
|
|
113
|
+
const kinds = [...new Set(items.map(item => item.actor))];
|
|
114
|
+
const constants = items.map(item => {
|
|
115
|
+
const result = item.results === undefined ? 'undefined' : `{ ${item.results.map(([guard, description]) => `[${JSON.stringify(guard)}]: ${JSON.stringify(description)}`).join(', ')} }`;
|
|
116
|
+
return ` [${JSON.stringify(item.id)}]: { prompt: ${JSON.stringify(item.prompt)}, result: ${result} }`;
|
|
117
|
+
}).join(',\n');
|
|
118
|
+
const types = kinds.map(kind => {
|
|
119
|
+
const title = kind[0].toUpperCase() + kind.slice(1);
|
|
120
|
+
if (kind === 'playbook') return `export type PlaybookInput = { stateId: string; sourceItem: string; playbookId: string; text: string };\nexport type PlaybookOutput = JsonValue | undefined;`;
|
|
121
|
+
return `export type ${title}Input = { stateId: string; sourceItem: string; ${kind === 'player' ? 'role: string; ' : ''}prompt: string; result: Readonly<Record<string, string>> } & __AUTHOR_${kind.toUpperCase()}_FIELDS__;\nexport type ${title}Output = __AUTHOR_${kind.toUpperCase()}_OUTPUT__;`;
|
|
122
|
+
}).join('\n');
|
|
123
|
+
const actors = kinds.map(kind => {
|
|
124
|
+
const title = kind[0].toUpperCase() + kind.slice(1);
|
|
125
|
+
return ` ${kind}: fromPromise<${title}Output, ${title}Input>(async () => { throw new Error('${kind} actor must be provided by the runner'); })`;
|
|
126
|
+
}).join(',\n');
|
|
127
|
+
const json = kinds.includes('playbook') ? `export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n` : '';
|
|
128
|
+
const license = sourceLicense(source);
|
|
129
|
+
return `${license ? `${license}\n\n` : ''}// INCOMPLETE AUTHORING SCAFFOLD: replace every __AUTHOR_* marker and finish the workflow.
|
|
130
|
+
// Read the complete GEARS Source and phase definition; constants are not its routing semantics.
|
|
131
|
+
import { assign, fromPromise, setup } from 'xstate';
|
|
132
|
+
${kinds.includes('playbook') ? `${CHILD_VALIDATION_IMPORTS}\n` : ''}
|
|
133
|
+
// Acting prompts stay literal; carry runtime values beside invoke.input.prompt.
|
|
134
|
+
// Nested prompts are templates: compose child text under the phase's actual rules.
|
|
135
|
+
// Results are only authored declarations. Apply the phase's default/question/controller rules.
|
|
136
|
+
export const GEARS_ITEMS = {
|
|
137
|
+
${constants}
|
|
138
|
+
} as const;
|
|
139
|
+
|
|
140
|
+
type Context = __AUTHOR_CONTEXT__;
|
|
141
|
+
type Event = __AUTHOR_EVENTS__;
|
|
142
|
+
export type MachineInput = __AUTHOR_MACHINE_INPUT__;
|
|
143
|
+
export type MachineOutput = __AUTHOR_MACHINE_OUTPUT__;
|
|
144
|
+
${json}${types}
|
|
145
|
+
${kinds.includes('playbook') ? `\n${CHILD_VALIDATION_HELPER}\n` : ''}
|
|
146
|
+
function invocationOutput(event: unknown): unknown {
|
|
147
|
+
return typeof event === 'object' && event !== null && 'output' in event ? event.output : undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const actorSetup = setup({
|
|
151
|
+
types: {} as { context: Context; events: Event; input: MachineInput; output: MachineOutput },
|
|
152
|
+
actors: {
|
|
153
|
+
${actors}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Keep reusable completion assignments registered and reference their names.
|
|
158
|
+
// A standalone typed action reused directly on onDone can mismatch external event types.
|
|
159
|
+
const machineSetup = actorSetup.extend({
|
|
160
|
+
actions: {
|
|
161
|
+
rememberResult: assign(({ event }) => {
|
|
162
|
+
const output = invocationOutput(event);
|
|
163
|
+
// Narrow unknown output to the declared result arm before reading payload fields.
|
|
164
|
+
return __AUTHOR_CONTEXT_UPDATE_FROM_OUTPUT__;
|
|
165
|
+
}),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Use machineSetup.createStateConfig(...) for shared transition/config fragments.
|
|
170
|
+
// Agent-owned work includes routes, guards, public metadata, failures, Boss continuation,
|
|
171
|
+
// actor value relays and any child-result predicates. No default topology is supplied.
|
|
172
|
+
export const machine = machineSetup.createMachine({
|
|
173
|
+
...__AUTHOR_MACHINE_CONFIG__,
|
|
174
|
+
});
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function scaffoldFsm({ sourcePath, outputPath }) {
|
|
179
|
+
const source = await readFile(sourcePath, 'utf8');
|
|
180
|
+
if (!outputPath.endsWith('.fsm.ts')) refuse('output must end in .fsm.ts');
|
|
181
|
+
if (resolve(sourcePath) === resolve(outputPath)) refuse('source and output must differ');
|
|
182
|
+
const items = sourceItems(source);
|
|
183
|
+
try {
|
|
184
|
+
const xstate = createRequire(resolve(outputPath))('xstate');
|
|
185
|
+
const typed = xstate.setup({});
|
|
186
|
+
if (typeof typed.extend !== 'function' || typeof typed.createStateConfig !== 'function') {
|
|
187
|
+
refuse('unsupported XState setup API');
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
refuse('target dependencies must supply XState setup.extend and createStateConfig');
|
|
191
|
+
}
|
|
192
|
+
const target = render(source, items);
|
|
193
|
+
const staging = join(dirname(outputPath), `.fsm-scaffold-${randomUUID()}.tmp`);
|
|
194
|
+
try {
|
|
195
|
+
await writeFile(staging, target, { flag: 'wx' });
|
|
196
|
+
// Exclusive publication prevents replacing a target created after our read.
|
|
197
|
+
await link(staging, outputPath);
|
|
198
|
+
} finally {
|
|
199
|
+
await unlink(staging).catch(error => { if (error.code !== 'ENOENT') throw error; });
|
|
200
|
+
}
|
|
201
|
+
return { status: 'scaffold', bytes: Buffer.byteLength(target), items: items.length };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function main(args) {
|
|
205
|
+
if (args.length === 1 && args[0] === '--help') {
|
|
206
|
+
process.stdout.write(HELP);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const options = {};
|
|
210
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
211
|
+
const key = args[index];
|
|
212
|
+
if (!['--source', '--out'].includes(key) || typeof args[index + 1] !== 'string' || args[index + 1].startsWith('--') || Object.hasOwn(options, key)) refuse(HELP.trim());
|
|
213
|
+
options[key] = args[index + 1];
|
|
214
|
+
}
|
|
215
|
+
if (!options['--source'] || !options['--out']) refuse(HELP.trim());
|
|
216
|
+
const result = await scaffoldFsm({ sourcePath: resolve(options['--source']), outputPath: resolve(options['--out']) });
|
|
217
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
221
|
+
main(process.argv.slice(2)).catch(error => {
|
|
222
|
+
process.stderr.write(`FSM scaffold: ${error instanceof Error ? error.message : 'initialization failed'}\n`);
|
|
223
|
+
process.exitCode = 1;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "sublang.slc.pin-inputs.v1",
|
|
3
|
+
"closures": {
|
|
4
|
+
"gears2fsm": ["text2gears.md", "link.md", "workflow-contracts.json"],
|
|
5
|
+
"link": [
|
|
6
|
+
"text2gears.md",
|
|
7
|
+
"gears2fsm.md",
|
|
8
|
+
"optimize.md",
|
|
9
|
+
"materialize-link.mjs",
|
|
10
|
+
"workflow-contracts.json"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
}
|
package/slc/text2gears.md
CHANGED
|
@@ -76,9 +76,12 @@ text2gears shall not move a shared instruction ahead of behavior-specific contex
|
|
|
76
76
|
|
|
77
77
|
Where Source says that a runtime value is relayed in quotes, the leading `>` is prompt content rather than Source-only blockquote syntax.
|
|
78
78
|
If Source supplies a blockquoted template for that relay, text2gears shall keep one literal leading `>` on every quoted line; the target GEARS line therefore uses its outer blockquote marker followed by the literal marker, such as `> > Coder output: <coder-output>`.
|
|
79
|
-
If Source names the relayed value but supplies no template, text2gears shall emit
|
|
79
|
+
If Source names the relayed value but supplies no template, text2gears shall emit a bare quoted prompt-content line, exactly `> <token>`, written as `> > <token>` in the GEARS file because the first marker encloses the prompt and the second is literal content, without an added label or surrounding prose, and shall not summarize, paraphrase, or invent the relayed value.
|
|
80
80
|
An ordinary Source blockquote that specifies a complete acting prompt without requiring quoted relay retains the existing rule above: its one leading marker is Source syntax and is not prompt content.
|
|
81
81
|
|
|
82
|
+
Apply each Source-authored relay to every acting behavior it governs, including relays described only in prose.
|
|
83
|
+
Mentioning a value in a condition, result contract, or machine context does not deliver it to the acting role; its complete prompt blockquote shall carry the required quoted placeholder.
|
|
84
|
+
|
|
82
85
|
An acting prompt whose instructions refer to a runtime value the acting role cannot otherwise observe — for example the Boss input task that triggered the workflow — shall relay that value as a quoted `<placeholder>` line appended to the prompt even when Source states no explicit relay.
|
|
83
86
|
A prompt that references an undelivered value asks its player to act on data it never received; omitting the relay is a compilation defect, not a faithful rendering of Source.
|
|
84
87
|
|
|
@@ -137,6 +140,10 @@ Results:
|
|
|
137
140
|
- `delegation`: Captain selected a call. Output shall include `remainingPlan: <JSON-safe array>`, `nextPlaybookId: <stable id>`, and `nextPlaybookInput: <complete request>`.
|
|
138
141
|
```
|
|
139
142
|
|
|
143
|
+
A `Results:` block continues until the next item or section heading; after the label, emit only result bullets and blank lines.
|
|
144
|
+
Put other acting-item conditions and invariants before the acting blockquote, never between the blockquote and `Results:` or after its bullets.
|
|
145
|
+
Nested-call items remain without `Results:` and keep their child-continuation prose after the blockquote.
|
|
146
|
+
|
|
140
147
|
`Results:` shall be a plain label rather than a heading.
|
|
141
148
|
Every result shall occupy one bullet with exactly a backtick-delimited guard
|
|
142
149
|
name, a colon, and a non-empty description.
|
|
@@ -145,6 +152,13 @@ The guard name shall match the ASCII identifier pattern
|
|
|
145
152
|
The bullet order is authoritative, guard names are unique within the item, and
|
|
146
153
|
the description shall name every required output property with its exact
|
|
147
154
|
case-sensitive identifier.
|
|
155
|
+
After `Output shall include`, reserve backticks for output-field declarations.
|
|
156
|
+
Keep each declaration outside plain-text parentheses; explanatory symbols in
|
|
157
|
+
parenthetical guidance use plain text, never separate backticks, because those
|
|
158
|
+
backticks would declare extra required fields. Guidance may instead occur
|
|
159
|
+
inside a field's complete annotation, including any parentheses there.
|
|
160
|
+
For example, use `` `codeCommit` (new code-owned commit) `` or
|
|
161
|
+
`` `codeCommit: <new code-owned commit>` ``, without backticks around code.
|
|
148
162
|
An output property name shall match the same ASCII identifier pattern as a
|
|
149
163
|
guard name: a kebab-case Source placeholder such as `<coder-output>` names the
|
|
150
164
|
property `coderOutput` through the canonical kebab-token-to-camel-field mapping
|
|
@@ -187,6 +201,10 @@ contract, so text2gears shall not invent a one-bullet `Results:` block for it.
|
|
|
187
201
|
When a later item does consume its output, the produced-value rule above
|
|
188
202
|
applies instead.
|
|
189
203
|
|
|
204
|
+
When Source requires a terminal return to the caller, preserve every returned value or fact and its return condition as an explicit workflow output obligation in GEARS.
|
|
205
|
+
Merely naming a value in a completion predicate or an acting result does not state that the workflow returns it.
|
|
206
|
+
Keep this non-acting requirement outside prompt blockquotes, in the item's pre-prompt prose, an explicit terminal-return clause in the relevant Results description, or existing nested-call continuation; do not create a Captain action solely to restate the return.
|
|
207
|
+
|
|
190
208
|
### Boss-reply continuation
|
|
191
209
|
|
|
192
210
|
Where a direct-Captain or delegated-player behavior may ask Boss a question
|
|
@@ -197,6 +215,10 @@ when the answer changes its complete runtime prompt. It shall not emit a
|
|
|
197
215
|
second item solely for "Boss answers," "after the question," or clearing the
|
|
198
216
|
consumed question/reply. The FSM and linker own the same-leaf suspension,
|
|
199
217
|
continuation blocks, and consumed-context cleanup.
|
|
218
|
+
When such an authored result asks Boss and waits, its `Results:` description
|
|
219
|
+
shall declare `question: <verbatim final text>` as an output property; the
|
|
220
|
+
result name or prose saying that a question is asked is not the field
|
|
221
|
+
declaration.
|
|
200
222
|
|
|
201
223
|
This rule is an exception to splitting by accumulated prompt content below.
|
|
202
224
|
Split only when Source requires a genuinely different acting behavior after
|
|
@@ -247,6 +269,11 @@ text2gears shall emit an item whose behavior uses
|
|
|
247
269
|
complete JSON-safe input-text template for that call.
|
|
248
270
|
The literal target id shall be a stable configured playbook id, not a slash
|
|
249
271
|
command or module specifier.
|
|
272
|
+
In both literal and dynamic nested-call forms, the behavior's verb phrase shall
|
|
273
|
+
be exact: `Captain shall call playbook ...:`.
|
|
274
|
+
Text2gears shall not insert sequencing words such as `first`, `then`, `next`,
|
|
275
|
+
or `finally` between `shall` and `call`; required sequencing belongs in the
|
|
276
|
+
`When` or `While` clause or in continuation prose around the item.
|
|
250
277
|
A nested-call item shall carry no `Results:` label: the child's terminal result
|
|
251
278
|
is its outcome, so Source's continuation after child success, abort, or failure
|
|
252
279
|
stays as prose after the blockquote for
|
|
@@ -299,8 +326,16 @@ compiled items.
|
|
|
299
326
|
The kind is defined here so every consumer of the GEARS format shares one
|
|
300
327
|
item-syntax contract.
|
|
301
328
|
|
|
302
|
-
A script item's blockquote is static shell text
|
|
303
|
-
`<placeholder
|
|
329
|
+
A script item's blockquote is static shell text apart from the
|
|
330
|
+
`<placeholder>` forms an acting prompt may carry, which relay a runtime value
|
|
331
|
+
the machine already retains; Markdown escapes resolve exactly as in acting
|
|
332
|
+
prompts.
|
|
333
|
+
A script reads no conversation and produces no prose, so a placeholder is
|
|
334
|
+
sound there only to bind the command to a target the script cannot otherwise
|
|
335
|
+
name — never to carry a value the script would have to interpret.
|
|
336
|
+
It shall occupy a single-quoted shell word, so the authored command is valid
|
|
337
|
+
shell as written and the compiled one binds a literal
|
|
338
|
+
([gears2fsm "Setup"](gears2fsm.md#setup)).
|
|
304
339
|
A script item shall carry a `Results:` label with exactly two bullets in this
|
|
305
340
|
fixed interpretation: the first guard reports the script exiting with status
|
|
306
341
|
zero, the second reports a nonzero exit status.
|