@sublang/playbook 0.7.0 → 0.9.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 +93 -74
- package/package.json +10 -15
- package/reference/sdlc/code.playbook/bin/playbook.js +438 -0
- package/reference/sdlc/code.playbook/code.fsm.ts +3 -2
- package/reference/sdlc/code.playbook/code.playbook.js +3 -2
- package/reference/sdlc/code.playbook/code.playbook.ts +3 -2
- package/reference/sdlc/code.playbook/code.registry.d.ts +18 -3
- package/reference/sdlc/code.playbook/code.registry.js +50 -32
- package/reference/sdlc/code.playbook/code.registry.ts +77 -37
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +8 -5
- package/reference/sdlc/code.playbook/playbook-captain.js +141 -55
- package/reference/sdlc/code.playbook/playbook-captain.ts +203 -73
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +58 -0
- package/reference/sdlc/code.playbook/bin/playbook-code.js +0 -453
- package/reference/sdlc/code.playbook/code.tmux-play.d.ts +0 -4
- package/reference/sdlc/code.playbook/code.tmux-play.js +0 -11
- package/reference/sdlc/code.playbook/code.tmux-play.ts +0 -29
- package/reference/sdlc/code.playbook/playbook-code.config.template.yaml +0 -72
- package/reference/sdlc/code.playbook/tmux-play.config.yaml +0 -50
- package/reference/sdlc/code.playbook/tmux-play.production.config.yaml +0 -33
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
4
|
-
|
|
5
|
-
import { spawn } from 'node:child_process';
|
|
6
|
-
import {
|
|
7
|
-
constants,
|
|
8
|
-
copyFileSync,
|
|
9
|
-
existsSync,
|
|
10
|
-
mkdirSync,
|
|
11
|
-
mkdtempSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
realpathSync,
|
|
14
|
-
rmSync,
|
|
15
|
-
writeFileSync,
|
|
16
|
-
} from 'node:fs';
|
|
17
|
-
import { homedir, tmpdir } from 'node:os';
|
|
18
|
-
import { dirname, join, resolve } from 'node:path';
|
|
19
|
-
import { fileURLToPath } from 'node:url';
|
|
20
|
-
import {
|
|
21
|
-
findTmuxPlayConfig,
|
|
22
|
-
loadTmuxPlayConfig,
|
|
23
|
-
} from '@sublang/cligent/tmux-play';
|
|
24
|
-
import {
|
|
25
|
-
parse as parseYaml,
|
|
26
|
-
parseDocument as parseYamlDocument,
|
|
27
|
-
stringify as stringifyYaml,
|
|
28
|
-
} from 'yaml';
|
|
29
|
-
|
|
30
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
31
|
-
const templatePath = resolve(here, '..', 'playbook-code.config.template.yaml');
|
|
32
|
-
const READINESS_FAILURE_EXIT_CODE = 2;
|
|
33
|
-
const COMPOSITION_FAILURE_EXIT_CODE = 1;
|
|
34
|
-
const DEFAULT_NOTIFICATION_BLOCK = [
|
|
35
|
-
'',
|
|
36
|
-
'# tmux-play host notifications. Omitted turn_aborted resolves to off.',
|
|
37
|
-
'notifications:',
|
|
38
|
-
' player_finished: bell',
|
|
39
|
-
' turn_finished: desktop',
|
|
40
|
-
'',
|
|
41
|
-
].join('\n');
|
|
42
|
-
|
|
43
|
-
// PBCODE-16: the composer injects `captain.from` (the Playbook
|
|
44
|
-
// Captain shell adapter module) and the `coder` / `reviewer` player
|
|
45
|
-
// ids, so the user-edited overlay carries neither.
|
|
46
|
-
export const PLAYBOOK_CAPTAIN_MODULE = '@sublang/playbook/playbook-captain';
|
|
47
|
-
export const CODE_ADAPTER_MODULE = PLAYBOOK_CAPTAIN_MODULE;
|
|
48
|
-
const CODE_ROLES = ['coder', 'reviewer'];
|
|
49
|
-
// Accepted `players` keys: the two fixed CODE roles plus the optional
|
|
50
|
-
// `committer` alias (a string naming one of the roles); PBCODE-17.
|
|
51
|
-
const CODE_PLAYER_KEYS = [...CODE_ROLES, 'committer'];
|
|
52
|
-
// Captain-judge fields inherited from a base config when the overlay
|
|
53
|
-
// leaves them unset (PBCODE-16); `adapter` is handled separately
|
|
54
|
-
// because it is required in the composed config.
|
|
55
|
-
const CAPTAIN_INHERITED_FIELDS = ['model', 'reasoningEffort', 'permissions'];
|
|
56
|
-
const PLAYER_FIELDS = ['model', 'reasoningEffort', 'permissions'];
|
|
57
|
-
|
|
58
|
-
export async function runPlaybookCodeCli(options = {}) {
|
|
59
|
-
const argv = [...(options.argv ?? process.argv.slice(2))];
|
|
60
|
-
const env = options.env ?? process.env;
|
|
61
|
-
const stdout = options.stdout ?? process.stdout;
|
|
62
|
-
const stderr = options.stderr ?? process.stderr;
|
|
63
|
-
const spawnFn = options.spawn ?? spawn;
|
|
64
|
-
const tmuxPlayBin = options.tmuxPlayBin ?? resolveTmuxPlayBin();
|
|
65
|
-
const home = options.homeDir ?? env.HOME ?? homedir();
|
|
66
|
-
const cwd = options.cwd ?? process.cwd();
|
|
67
|
-
const configHome = resolveConfigHome(env, home);
|
|
68
|
-
const userConfigPath = resolveUserConfigPath(env, home);
|
|
69
|
-
|
|
70
|
-
if (argv.includes('--help') || argv.includes('-h')) {
|
|
71
|
-
stdout.write(helpText({ userConfigPath }));
|
|
72
|
-
return { code: 0 };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// PBCODE-1: explicit `--config <path>` bypasses seeding, readiness,
|
|
76
|
-
// and composition — the path is launched verbatim.
|
|
77
|
-
if (hasExplicitConfig(argv)) {
|
|
78
|
-
return await launchTmuxPlay(spawnFn, [tmuxPlayBin, ...argv], stderr);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
seedUserConfigIfMissing(userConfigPath, stderr);
|
|
82
|
-
migrateUserConfigNotificationsIfMissing(userConfigPath, stderr);
|
|
83
|
-
|
|
84
|
-
// PBCODE-16/17: compose the launched config from the overlay plus an
|
|
85
|
-
// optional base tmux-play config. Composition failures (missing role,
|
|
86
|
-
// non-CODE role id, missing `captain.adapter`) surface a path-named
|
|
87
|
-
// error and abort before launch.
|
|
88
|
-
let composed;
|
|
89
|
-
try {
|
|
90
|
-
composed = await composeLaunchConfig({
|
|
91
|
-
overlayPath: userConfigPath,
|
|
92
|
-
cwd,
|
|
93
|
-
configHome,
|
|
94
|
-
});
|
|
95
|
-
} catch (error) {
|
|
96
|
-
stderr.write(`playbook-code: ${errorMessage(error)}\n`);
|
|
97
|
-
return { code: COMPOSITION_FAILURE_EXIT_CODE };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// PBCODE-8: readiness reads the adapters of the composed config —
|
|
101
|
-
// including a captain adapter that may have been inherited from the
|
|
102
|
-
// base — not the raw overlay.
|
|
103
|
-
const readiness = checkReadiness(
|
|
104
|
-
adaptersFromComposedConfig(composed),
|
|
105
|
-
env,
|
|
106
|
-
home,
|
|
107
|
-
);
|
|
108
|
-
for (const adapter of readiness.unknownAdapters) {
|
|
109
|
-
stderr.write(
|
|
110
|
-
`playbook-code: warning: no readiness check for adapter "${adapter}"\n`,
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
if (readiness.failingAdapters.length > 0) {
|
|
114
|
-
stderr.write(
|
|
115
|
-
helpText({
|
|
116
|
-
userConfigPath,
|
|
117
|
-
failingAdapters: readiness.failingAdapters,
|
|
118
|
-
}),
|
|
119
|
-
);
|
|
120
|
-
return { code: READINESS_FAILURE_EXIT_CODE };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// PBCODE-16: materialize the composed config to a temp file, launch
|
|
124
|
-
// against it, and remove it before the shim exits — on normal exit,
|
|
125
|
-
// on non-zero child exit, and before re-raising a forwarded signal.
|
|
126
|
-
const { dir: tempDir, path: composedPath } = writeComposedConfig(composed);
|
|
127
|
-
try {
|
|
128
|
-
return await launchTmuxPlay(
|
|
129
|
-
spawnFn,
|
|
130
|
-
[tmuxPlayBin, '--config', composedPath, ...argv],
|
|
131
|
-
stderr,
|
|
132
|
-
);
|
|
133
|
-
} finally {
|
|
134
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function resolveConfigHome(env = process.env, home = homedir()) {
|
|
139
|
-
return env.XDG_CONFIG_HOME || join(home, '.config');
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function resolveUserConfigPath(env = process.env, home = homedir()) {
|
|
143
|
-
return join(resolveConfigHome(env, home), 'playbook', 'playbook-code.config.yaml');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// PBCODE-17: read the overlay, locate an optional base tmux-play config
|
|
147
|
-
// with cligent's exported `findTmuxPlayConfig` (never the bare
|
|
148
|
-
// `loadTmuxPlayConfig`, which writes a default config when none is
|
|
149
|
-
// found), load the base only when a path is located, and compose.
|
|
150
|
-
async function composeLaunchConfig({ overlayPath, cwd, configHome }) {
|
|
151
|
-
const overlay = parseYaml(readFileSync(overlayPath, 'utf8')) ?? {};
|
|
152
|
-
const basePath = findTmuxPlayConfig(cwd, configHome);
|
|
153
|
-
let base;
|
|
154
|
-
if (basePath) {
|
|
155
|
-
base = (await loadTmuxPlayConfig({ configPath: basePath })).config;
|
|
156
|
-
}
|
|
157
|
-
return composeRuntimeConfig(overlay, base);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Compose the launched tmux-play config (PBCODE-16/17) from the CODE
|
|
162
|
-
* overlay and an optional base tmux-play config.
|
|
163
|
-
*
|
|
164
|
-
* @returns {import('@sublang/cligent/tmux-play').TmuxPlayConfig}
|
|
165
|
-
*/
|
|
166
|
-
export function composeRuntimeConfig(
|
|
167
|
-
overlay,
|
|
168
|
-
base,
|
|
169
|
-
adapterModule = PLAYBOOK_CAPTAIN_MODULE,
|
|
170
|
-
) {
|
|
171
|
-
const overlayConfig = requireObject(overlay, 'config');
|
|
172
|
-
const overlayPlayers = requireObject(overlayConfig.players, 'players');
|
|
173
|
-
|
|
174
|
-
// Reject any players key other than the two fixed CODE roles and the
|
|
175
|
-
// optional `committer` alias.
|
|
176
|
-
for (const key of Object.keys(overlayPlayers)) {
|
|
177
|
-
if (!CODE_PLAYER_KEYS.includes(key)) {
|
|
178
|
-
throw new Error(`Unknown config field players.${key}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// PBCODE-17: `players.committer` is an optional string naming `coder`
|
|
183
|
-
// or `reviewer`. The composer resolves it into the composed
|
|
184
|
-
// `captain.options.code.committer` below and emits no extra
|
|
185
|
-
// `players[]` entry, so the roster stays coder + reviewer.
|
|
186
|
-
let committerAlias;
|
|
187
|
-
if (overlayPlayers.committer !== undefined) {
|
|
188
|
-
const value = overlayPlayers.committer;
|
|
189
|
-
if (!CODE_ROLES.includes(value)) {
|
|
190
|
-
throw new Error(
|
|
191
|
-
`players.committer must name one of: ${CODE_ROLES.join(', ')}`,
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
committerAlias = value;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// One composed `players[]` entry per role, with `id` = the role key
|
|
198
|
-
// and that role's required `adapter` plus optional fields.
|
|
199
|
-
const players = CODE_ROLES.map((role) => {
|
|
200
|
-
if (overlayPlayers[role] === undefined) {
|
|
201
|
-
throw new Error(`Missing required field players.${role}`);
|
|
202
|
-
}
|
|
203
|
-
const block = requireObject(overlayPlayers[role], `players.${role}`);
|
|
204
|
-
if (block.adapter === undefined) {
|
|
205
|
-
throw new Error(`Missing required field players.${role}.adapter`);
|
|
206
|
-
}
|
|
207
|
-
const entry = { id: role, adapter: block.adapter };
|
|
208
|
-
for (const field of PLAYER_FIELDS) {
|
|
209
|
-
if (block[field] !== undefined) entry[field] = block[field];
|
|
210
|
-
}
|
|
211
|
-
return entry;
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const overlayCaptain =
|
|
215
|
-
overlayConfig.captain === undefined
|
|
216
|
-
? {}
|
|
217
|
-
: requireObject(overlayConfig.captain, 'captain');
|
|
218
|
-
const baseCaptain = isObject(base?.captain) ? base.captain : {};
|
|
219
|
-
|
|
220
|
-
// `captain.adapter` comes from the overlay when present, else the
|
|
221
|
-
// base; composition fails with a path-named error when neither
|
|
222
|
-
// supplies it. Role adapters are required in the overlay and are not
|
|
223
|
-
// inherited.
|
|
224
|
-
const captainAdapter = overlayCaptain.adapter ?? baseCaptain.adapter;
|
|
225
|
-
if (captainAdapter === undefined) {
|
|
226
|
-
throw new Error('Missing required field captain.adapter');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const captain = { from: adapterModule, adapter: captainAdapter };
|
|
230
|
-
for (const field of CAPTAIN_INHERITED_FIELDS) {
|
|
231
|
-
const value = overlayCaptain[field] ?? baseCaptain[field];
|
|
232
|
-
if (value !== undefined) captain[field] = value;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// PBCODE-17: the shim is the sole writer of the composed
|
|
236
|
-
// `captain.options.code.committer`. Read the overlay's
|
|
237
|
-
// `captain.options.code` (carried through unchanged), reject a
|
|
238
|
-
// directly-set `committer` there, then layer the resolved
|
|
239
|
-
// `players.committer` alias on top.
|
|
240
|
-
let overlayCode;
|
|
241
|
-
if (overlayCaptain.options !== undefined) {
|
|
242
|
-
const captainOptions = requireObject(
|
|
243
|
-
overlayCaptain.options,
|
|
244
|
-
'captain.options',
|
|
245
|
-
);
|
|
246
|
-
if (captainOptions.code !== undefined) {
|
|
247
|
-
overlayCode = requireObject(captainOptions.code, 'captain.options.code');
|
|
248
|
-
if (overlayCode.committer !== undefined) {
|
|
249
|
-
throw new Error(
|
|
250
|
-
'captain.options.code.committer is composer-owned; ' +
|
|
251
|
-
'set players.committer instead',
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (overlayCode !== undefined || committerAlias !== undefined) {
|
|
257
|
-
const code = { ...(overlayCode ?? {}) };
|
|
258
|
-
if (committerAlias !== undefined) code.committer = committerAlias;
|
|
259
|
-
captain.options = { code };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Inherit host-owned top-level fields from the base when the overlay
|
|
263
|
-
// omits them; never the base `players[]` roster.
|
|
264
|
-
const composed = {};
|
|
265
|
-
const theme = overlayConfig.theme ?? base?.theme;
|
|
266
|
-
if (theme !== undefined) composed.theme = theme;
|
|
267
|
-
const layout = overlayConfig.layout ?? base?.layout;
|
|
268
|
-
if (layout !== undefined) composed.layout = layout;
|
|
269
|
-
const notifications = overlayConfig.notifications ?? base?.notifications;
|
|
270
|
-
if (notifications !== undefined) composed.notifications = notifications;
|
|
271
|
-
composed.captain = captain;
|
|
272
|
-
composed.players = players;
|
|
273
|
-
return composed;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export function adaptersFromComposedConfig(config) {
|
|
277
|
-
const adapters = new Set();
|
|
278
|
-
const captainAdapter = config?.captain?.adapter;
|
|
279
|
-
if (captainAdapter) adapters.add(captainAdapter);
|
|
280
|
-
for (const player of config?.players ?? []) {
|
|
281
|
-
if (player?.adapter) adapters.add(player.adapter);
|
|
282
|
-
}
|
|
283
|
-
return [...adapters];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export function checkReadiness(adapters, env = process.env, home = homedir()) {
|
|
287
|
-
const failingAdapters = [];
|
|
288
|
-
const unknownAdapters = [];
|
|
289
|
-
|
|
290
|
-
for (const adapter of adapters) {
|
|
291
|
-
if (adapter === 'claude') {
|
|
292
|
-
if (!env.ANTHROPIC_API_KEY && !existsSync(join(home, '.claude'))) {
|
|
293
|
-
failingAdapters.push(adapter);
|
|
294
|
-
}
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
if (adapter === 'codex') {
|
|
298
|
-
if (!env.OPENAI_API_KEY && !existsSync(join(home, '.codex'))) {
|
|
299
|
-
failingAdapters.push(adapter);
|
|
300
|
-
}
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
unknownAdapters.push(adapter);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return { failingAdapters, unknownAdapters };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function writeComposedConfig(composed) {
|
|
310
|
-
const dir = mkdtempSync(join(tmpdir(), 'playbook-code-'));
|
|
311
|
-
// cligent's loader only accepts a `.yaml` extension; name the temp
|
|
312
|
-
// file accordingly.
|
|
313
|
-
const path = join(dir, 'tmux-play.config.yaml');
|
|
314
|
-
writeFileSync(path, stringifyYaml(composed));
|
|
315
|
-
return { dir, path };
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function seedUserConfigIfMissing(userConfigPath, stderr) {
|
|
319
|
-
if (existsSync(userConfigPath)) return;
|
|
320
|
-
mkdirSync(dirname(userConfigPath), { recursive: true });
|
|
321
|
-
copyFileSync(templatePath, userConfigPath, constants.COPYFILE_EXCL);
|
|
322
|
-
stderr.write(`playbook-code: created config at ${userConfigPath}\n`);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function migrateUserConfigNotificationsIfMissing(userConfigPath, stderr) {
|
|
326
|
-
const source = readFileSync(userConfigPath, 'utf8');
|
|
327
|
-
let parsed;
|
|
328
|
-
try {
|
|
329
|
-
const document = parseYamlDocument(source);
|
|
330
|
-
if (document.errors.length > 0) return;
|
|
331
|
-
parsed = document.contents === null ? {} : document.toJS();
|
|
332
|
-
} catch {
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
if (!isObject(parsed) || hasOwn(parsed, 'notifications')) return;
|
|
336
|
-
const separator = source.endsWith('\n') ? '' : '\n';
|
|
337
|
-
writeFileSync(
|
|
338
|
-
userConfigPath,
|
|
339
|
-
`${source}${separator}${DEFAULT_NOTIFICATION_BLOCK}`,
|
|
340
|
-
);
|
|
341
|
-
stderr.write(
|
|
342
|
-
`playbook-code: added notifications defaults to config at ${userConfigPath}\n`,
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function hasExplicitConfig(argv) {
|
|
347
|
-
return argv.some((arg) => arg === '--config' || arg.startsWith('--config='));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function helpText({ userConfigPath, failingAdapters = [] }) {
|
|
351
|
-
const failures =
|
|
352
|
-
failingAdapters.length > 0
|
|
353
|
-
? [
|
|
354
|
-
`Adapters not ready: ${failingAdapters.join(', ')}`,
|
|
355
|
-
'',
|
|
356
|
-
]
|
|
357
|
-
: [];
|
|
358
|
-
return [
|
|
359
|
-
...failures,
|
|
360
|
-
'Usage:',
|
|
361
|
-
' playbook-code [--config <path>] [tmux-play options]',
|
|
362
|
-
' playbook-code --help',
|
|
363
|
-
'',
|
|
364
|
-
`Default config: ${userConfigPath}`,
|
|
365
|
-
'',
|
|
366
|
-
'Adapter setup:',
|
|
367
|
-
' claude: run Claude Code once or set ANTHROPIC_API_KEY.',
|
|
368
|
-
' codex: run Codex CLI once or set OPENAI_API_KEY.',
|
|
369
|
-
'',
|
|
370
|
-
'Agent swap recipe:',
|
|
371
|
-
' - change captain.adapter and captain.model for the Captain/Judge',
|
|
372
|
-
' - change adapter and model under players.coder and players.reviewer',
|
|
373
|
-
' for the Coder and Reviewer; model when pinned, else adapter, is',
|
|
374
|
-
' substituted into <coder-llm>/<reviewer-llm> player prompts (PBRT-4)',
|
|
375
|
-
' - the composer injects captain.from; the role keys coder and',
|
|
376
|
-
' reviewer are fixed',
|
|
377
|
-
'',
|
|
378
|
-
].join('\n');
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
async function launchTmuxPlay(spawnFn, childArgs, stderr) {
|
|
382
|
-
return await new Promise((resolveResult) => {
|
|
383
|
-
let child;
|
|
384
|
-
try {
|
|
385
|
-
child = spawnFn(process.execPath, childArgs, { stdio: 'inherit' });
|
|
386
|
-
} catch (error) {
|
|
387
|
-
stderr.write(`playbook-code: failed to launch tmux-play: ${errorMessage(error)}\n`);
|
|
388
|
-
resolveResult({ code: 127 });
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
let settled = false;
|
|
393
|
-
const settle = (result) => {
|
|
394
|
-
if (settled) return;
|
|
395
|
-
settled = true;
|
|
396
|
-
resolveResult(result);
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
child.on('error', (err) => {
|
|
400
|
-
stderr.write(
|
|
401
|
-
`playbook-code: failed to launch tmux-play: ${errorMessage(err)}\n`,
|
|
402
|
-
);
|
|
403
|
-
settle({ code: 127 });
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
child.on('exit', (code, signal) => {
|
|
407
|
-
if (signal) settle({ signal });
|
|
408
|
-
else settle({ code: code ?? 0 });
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function resolveTmuxPlayBin() {
|
|
414
|
-
const tmuxPlayIndexUrl = import.meta.resolve('@sublang/cligent/tmux-play');
|
|
415
|
-
return join(dirname(fileURLToPath(tmuxPlayIndexUrl)), 'cli.js');
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function isObject(value) {
|
|
419
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function hasOwn(value, key) {
|
|
423
|
-
return Object.prototype.hasOwnProperty.call(value, key);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function requireObject(value, path) {
|
|
427
|
-
if (!isObject(value)) {
|
|
428
|
-
throw new Error(`${path} must be an object`);
|
|
429
|
-
}
|
|
430
|
-
return value;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function errorMessage(error) {
|
|
434
|
-
return error instanceof Error ? error.message : String(error);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function isCliEntry(
|
|
438
|
-
argv1 = process.argv[1],
|
|
439
|
-
moduleUrl = import.meta.url,
|
|
440
|
-
) {
|
|
441
|
-
if (!argv1) return false;
|
|
442
|
-
try {
|
|
443
|
-
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
444
|
-
} catch {
|
|
445
|
-
return false;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (isCliEntry()) {
|
|
450
|
-
const result = await runPlaybookCodeCli();
|
|
451
|
-
if (result.signal) process.kill(process.pid, result.signal);
|
|
452
|
-
else process.exit(result.code ?? 0);
|
|
453
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { Captain } from '@sublang/cligent/tmux-play';
|
|
2
|
-
export { codeCopyPasteGuardNames, codeStateCountLabels, codePlaybookRegistryEntry, createCodeRuntimeOptions, validateCodeOptions, } from './code.registry.js';
|
|
3
|
-
export type { CodeOptions, CodePlaybookRegistryEntry, CreateCodeRuntimeOptions, RegistryPlayer, } from './code.registry.js';
|
|
4
|
-
export default function createCodeTmuxPlayCaptain(options: unknown): Captain;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
-
import createPlaybookCaptainShell from './playbook-captain.js';
|
|
4
|
-
export { codeCopyPasteGuardNames, codeStateCountLabels, codePlaybookRegistryEntry, createCodeRuntimeOptions, validateCodeOptions, } from './code.registry.js';
|
|
5
|
-
// Compatibility shim for the historic `./code/tmux-play` package
|
|
6
|
-
// export. Public launch paths now target the Playbook Captain shell
|
|
7
|
-
// directly; explicit configs that still import this module get the
|
|
8
|
-
// same shell with CODE registered.
|
|
9
|
-
export default function createCodeTmuxPlayCaptain(options) {
|
|
10
|
-
return createPlaybookCaptainShell(options);
|
|
11
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
-
|
|
4
|
-
import type { Captain } from '@sublang/cligent/tmux-play';
|
|
5
|
-
import createPlaybookCaptainShell from './playbook-captain.js';
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
codeCopyPasteGuardNames,
|
|
9
|
-
codeStateCountLabels,
|
|
10
|
-
codePlaybookRegistryEntry,
|
|
11
|
-
createCodeRuntimeOptions,
|
|
12
|
-
validateCodeOptions,
|
|
13
|
-
} from './code.registry.js';
|
|
14
|
-
export type {
|
|
15
|
-
CodeOptions,
|
|
16
|
-
CodePlaybookRegistryEntry,
|
|
17
|
-
CreateCodeRuntimeOptions,
|
|
18
|
-
RegistryPlayer,
|
|
19
|
-
} from './code.registry.js';
|
|
20
|
-
|
|
21
|
-
// Compatibility shim for the historic `./code/tmux-play` package
|
|
22
|
-
// export. Public launch paths now target the Playbook Captain shell
|
|
23
|
-
// directly; explicit configs that still import this module get the
|
|
24
|
-
// same shell with CODE registered.
|
|
25
|
-
export default function createCodeTmuxPlayCaptain(
|
|
26
|
-
options: unknown,
|
|
27
|
-
): Captain {
|
|
28
|
-
return createPlaybookCaptainShell(options);
|
|
29
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
# SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
-
|
|
4
|
-
# Seed template for the user-level playbook-code CODE overlay.
|
|
5
|
-
# First run copies this file to:
|
|
6
|
-
# ${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook-code.config.yaml
|
|
7
|
-
#
|
|
8
|
-
# This is a CODE *overlay*, not a full tmux-play config: playbook-code
|
|
9
|
-
# composes the launched tmux-play config from it at startup (PBCODE-16).
|
|
10
|
-
# The composer injects captain.from (the Playbook Captain shell adapter)
|
|
11
|
-
# and the coder / reviewer player ids, and can inherit theme plus
|
|
12
|
-
# captain-judge defaults from an existing tmux-play config — so you only
|
|
13
|
-
# tune the fields below.
|
|
14
|
-
#
|
|
15
|
-
# Safe tuning points:
|
|
16
|
-
# - captain.adapter / model / reasoningEffort pick the judge/Captain
|
|
17
|
-
# agent and its reasoning tier.
|
|
18
|
-
# - players.coder and players.reviewer pick the Coder and Reviewer
|
|
19
|
-
# agents; set each role's adapter (required) and optional model /
|
|
20
|
-
# reasoningEffort / permissions. Each role's model when pinned, else
|
|
21
|
-
# its adapter, doubles as the identity string the adapter substitutes
|
|
22
|
-
# into <coder-llm> / <reviewer-llm> in player prompts (PBRT-4), so pin
|
|
23
|
-
# a model if you want the Committer's commit-message trailers to name
|
|
24
|
-
# the concrete model.
|
|
25
|
-
# - Codex roles that run git under mode: auto need .git in
|
|
26
|
-
# permissions.writablePaths so git metadata writes stay profile-scoped
|
|
27
|
-
# instead of requiring bypass permissions.
|
|
28
|
-
# - players.committer optionally aliases the Committer to a role
|
|
29
|
-
# (coder or reviewer); that role's pane runs the commit turn. The
|
|
30
|
-
# seeded value is reviewer; omit it to fall back to the coder.
|
|
31
|
-
# - layout sizes the tmux window (layout.window: columns × rows) and
|
|
32
|
-
# sets the relative column widths (layout.columnWeights) for the
|
|
33
|
-
# Boss/Captain, Coder, and Reviewer columns.
|
|
34
|
-
# - notifications configures tmux-play's host notifications. Supported
|
|
35
|
-
# events are player_finished, turn_finished, and turn_aborted; each
|
|
36
|
-
# event accepts off, bell, or desktop. The seeded defaults match
|
|
37
|
-
# cligent's generated home config, with omitted turn_aborted resolving
|
|
38
|
-
# to off.
|
|
39
|
-
|
|
40
|
-
layout:
|
|
41
|
-
window:
|
|
42
|
-
columns: 174
|
|
43
|
-
rows: 49
|
|
44
|
-
columnWeights: [4, 6, 6]
|
|
45
|
-
|
|
46
|
-
notifications:
|
|
47
|
-
player_finished: bell
|
|
48
|
-
turn_finished: desktop
|
|
49
|
-
|
|
50
|
-
captain:
|
|
51
|
-
adapter: claude
|
|
52
|
-
model: claude-sonnet-4-6
|
|
53
|
-
reasoningEffort: high
|
|
54
|
-
permissions:
|
|
55
|
-
mode: auto
|
|
56
|
-
|
|
57
|
-
players:
|
|
58
|
-
coder:
|
|
59
|
-
adapter: codex
|
|
60
|
-
model: gpt-5.5
|
|
61
|
-
reasoningEffort: xhigh
|
|
62
|
-
permissions:
|
|
63
|
-
mode: auto
|
|
64
|
-
writablePaths:
|
|
65
|
-
- .git
|
|
66
|
-
reviewer:
|
|
67
|
-
adapter: claude
|
|
68
|
-
model: claude-opus-4-8
|
|
69
|
-
reasoningEffort: xhigh
|
|
70
|
-
permissions:
|
|
71
|
-
mode: auto
|
|
72
|
-
committer: reviewer
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
# SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
-
|
|
4
|
-
# Example tmux-play config for the CODE playbook through the
|
|
5
|
-
# Playbook Captain shell — DR-008 §6.
|
|
6
|
-
# Run with:
|
|
7
|
-
# tmux-play --config tmux-play.config.yaml
|
|
8
|
-
|
|
9
|
-
notifications:
|
|
10
|
-
player_finished: bell
|
|
11
|
-
turn_finished: desktop
|
|
12
|
-
|
|
13
|
-
captain:
|
|
14
|
-
# Dev form: sibling-path import of the compiled shell adapter. After
|
|
15
|
-
# @sublang/playbook is published, swap this for:
|
|
16
|
-
# from: "@sublang/playbook/playbook-captain"
|
|
17
|
-
# Existing explicit configs that still import ./code.tmux-play.js or
|
|
18
|
-
# @sublang/playbook/code/tmux-play continue through a compatibility
|
|
19
|
-
# shim that delegates to the same shell with CODE registered.
|
|
20
|
-
from: ./playbook-captain.js
|
|
21
|
-
adapter: claude
|
|
22
|
-
model: claude-sonnet-4-6
|
|
23
|
-
reasoningEffort: high
|
|
24
|
-
# Agents run in cligent's classifier/reviewer-protected auto mode
|
|
25
|
-
# (cligent DR-005): claude → permissionMode auto, codex → auto_review.
|
|
26
|
-
permissions:
|
|
27
|
-
mode: auto
|
|
28
|
-
|
|
29
|
-
# `players[].id` shall match the baked playerId strings the adapter
|
|
30
|
-
# routes to. The CODE playbook bakes Coder→'coder' and
|
|
31
|
-
# Reviewer→'reviewer' at link time (DR-004 §2); the adapter does
|
|
32
|
-
# not remap, so changing these ids would break callPlayer routing.
|
|
33
|
-
# Each entry's `model` (when pinned) or `adapter` (otherwise) is
|
|
34
|
-
# substituted into the <coder-llm> / <reviewer-llm> placeholders in
|
|
35
|
-
# player prompts (DR-004 §6, PBRT-4) — pin a concrete `model:` if
|
|
36
|
-
# you want the Committer's commit-message trailers to name the
|
|
37
|
-
# specific model rather than the adapter family.
|
|
38
|
-
players:
|
|
39
|
-
- id: coder
|
|
40
|
-
adapter: claude
|
|
41
|
-
model: claude-opus-4-7
|
|
42
|
-
reasoningEffort: xhigh
|
|
43
|
-
permissions:
|
|
44
|
-
mode: auto
|
|
45
|
-
- id: reviewer
|
|
46
|
-
adapter: codex
|
|
47
|
-
model: gpt-5.5
|
|
48
|
-
reasoningEffort: xhigh
|
|
49
|
-
permissions:
|
|
50
|
-
mode: auto
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
# SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
-
|
|
4
|
-
# Production tmux-play config for the published CODE playbook package.
|
|
5
|
-
# This intentionally imports the Playbook Captain shell through the
|
|
6
|
-
# package export, not a local repo path. The runtime still expects the
|
|
7
|
-
# host app to provide the @sublang/cligent peer dependency.
|
|
8
|
-
|
|
9
|
-
notifications:
|
|
10
|
-
player_finished: bell
|
|
11
|
-
turn_finished: desktop
|
|
12
|
-
|
|
13
|
-
captain:
|
|
14
|
-
from: "@sublang/playbook/playbook-captain"
|
|
15
|
-
adapter: claude
|
|
16
|
-
model: claude-sonnet-4-6
|
|
17
|
-
reasoningEffort: high
|
|
18
|
-
permissions:
|
|
19
|
-
mode: auto
|
|
20
|
-
|
|
21
|
-
players:
|
|
22
|
-
- id: coder
|
|
23
|
-
adapter: claude
|
|
24
|
-
model: claude-opus-4-7
|
|
25
|
-
reasoningEffort: xhigh
|
|
26
|
-
permissions:
|
|
27
|
-
mode: auto
|
|
28
|
-
- id: reviewer
|
|
29
|
-
adapter: codex
|
|
30
|
-
model: gpt-5.5
|
|
31
|
-
reasoningEffort: xhigh
|
|
32
|
-
permissions:
|
|
33
|
-
mode: auto
|