@sublang/playbook 0.4.2 → 0.5.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.
@@ -17,15 +17,75 @@ import createPlaybookRuntime, {
17
17
  type PlaybookRuntime,
18
18
  } from './code.playbook.js';
19
19
 
20
+ // PBRT-29/30: CODE runtime options are carried under
21
+ // `captain.options.code`, a namespaced object the host forwards
22
+ // verbatim through `captain.options`. cligent neither reads nor
23
+ // validates `options.code` (PBRT-30); this adapter is the sole
24
+ // validator. The CODE options schema defines one key, `committer`: an
25
+ // optional Committer-alias player id, one of the baked player ids
26
+ // `coder` / `reviewer`. A valid `options.code` is absent, `{}`, or
27
+ // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown
28
+ // and rejected with a path-named error, and an out-of-range
29
+ // `committer` value is rejected naming `captain.options.code.committer`.
30
+ // A further CODE option shall be introduced as its own higher-numbered
31
+ // item that widens `CODE_OPTION_KEYS`; the validator still fails closed
32
+ // on stray keys.
33
+ const CODE_OPTION_KEYS = new Set<string>(['committer']);
34
+ const COMMITTER_PLAYER_IDS = new Set<string>(['coder', 'reviewer']);
35
+
36
+ // The validated CODE options set. `committer`, when present, is the
37
+ // resolved Committer-alias player id (PBRT-8 / PBRT-30); a future CODE
38
+ // option widens both this type and `CODE_OPTION_KEYS`.
39
+ export interface CodeOptions {
40
+ committer?: 'coder' | 'reviewer';
41
+ }
42
+
43
+ export function validateCodeOptions(captainOptions: unknown): CodeOptions {
44
+ const code = readCodeNamespace(captainOptions);
45
+ if (code === undefined) return {};
46
+ if (typeof code !== 'object' || code === null || Array.isArray(code)) {
47
+ throw new Error('captain.options.code must be an object');
48
+ }
49
+ for (const key of Object.keys(code)) {
50
+ if (!CODE_OPTION_KEYS.has(key)) {
51
+ throw new Error(`Unknown config field captain.options.code.${key}`);
52
+ }
53
+ }
54
+ const options: CodeOptions = {};
55
+ const committer = (code as Record<string, unknown>).committer;
56
+ if (committer !== undefined) {
57
+ if (typeof committer !== 'string' || !COMMITTER_PLAYER_IDS.has(committer)) {
58
+ throw new Error(
59
+ "captain.options.code.committer must be 'coder' or 'reviewer'",
60
+ );
61
+ }
62
+ options.committer = committer as 'coder' | 'reviewer';
63
+ }
64
+ return options;
65
+ }
66
+
67
+ function readCodeNamespace(captainOptions: unknown): unknown {
68
+ if (
69
+ typeof captainOptions !== 'object' ||
70
+ captainOptions === null ||
71
+ Array.isArray(captainOptions)
72
+ ) {
73
+ return undefined;
74
+ }
75
+ return (captainOptions as Record<string, unknown>).code;
76
+ }
77
+
20
78
  // Captain factory per TMUX-014: `(options: unknown) => Captain`.
21
- // `options` is whatever `captain.options` carries in the YAML config.
22
- // The per-run player identity strings (`coderPlayer`, `reviewerPlayer`)
23
- // are derived from `session.players` at init time per PBRT-4 —
24
- // preferring each entry's `model` and falling back to `adapter` when
25
- // no model is pinned so player prompts and commit-message trailers
26
- // carry the concrete model identity (e.g. `claude-opus-4-7`) rather
27
- // than the adapter family name (e.g. `claude`). Any same-named keys
28
- // in `options` are ignored.
79
+ // `options` is whatever `captain.options` carries in the YAML config;
80
+ // CODE reads only the namespaced `options.code` (PBRT-30). The per-run
81
+ // player identity strings (`coderPlayer`, `reviewerPlayer`) are
82
+ // derived from `session.players` at init time per PBRT-4 — preferring
83
+ // each entry's `model` and falling back to `adapter` when no model is
84
+ // pinned so player prompts and commit-message trailers carry the
85
+ // concrete model identity (e.g. `claude-opus-4-7`) rather than the
86
+ // adapter family name (e.g. `claude`). They come from `session.players`
87
+ // independent of `captain.options.code` and override any same-named
88
+ // keys.
29
89
  export default function createCodeTmuxPlayCaptain(
30
90
  options: unknown,
31
91
  ): Captain {
@@ -40,17 +100,28 @@ export default function createCodeTmuxPlayCaptain(
40
100
 
41
101
  return {
42
102
  async init(session: CaptainSession): Promise<void> {
103
+ // PBRT-30: validate `captain.options.code` before constructing
104
+ // the runtime so a stray key fails `init` closed with a
105
+ // path-named error; the empty schema yields an empty options set.
106
+ const codeOptions = validateCodeOptions(options);
43
107
  const playerIdentity = (id: string): string | undefined => {
44
108
  const entry = session.players.find((p) => p.id === id);
45
109
  return entry?.model ?? entry?.adapter;
46
110
  };
47
111
  const coderPlayer = playerIdentity('coder');
48
112
  const reviewerPlayer = playerIdentity('reviewer');
49
- runtime = createPlaybookRuntime({
50
- ...(options as CodePlaybookOptions),
113
+ // Identity strings from `session.players` override any same-named
114
+ // keys and are independent of `captain.options.code` (PBRT-30).
115
+ // The validated `committer` alias threads in as the runtime's
116
+ // Committer player id (`committerPlayer`, PBRT-8).
117
+ const runtimeOptions: CodePlaybookOptions = {
51
118
  coderPlayer,
52
119
  reviewerPlayer,
53
- });
120
+ ...(codeOptions.committer !== undefined
121
+ ? { committerPlayer: codeOptions.committer }
122
+ : {}),
123
+ };
124
+ runtime = createPlaybookRuntime(runtimeOptions);
54
125
  const ports: PlaybookPorts = {
55
126
  callPlayer: async (playerId, prompt, _signal) => {
56
127
  if (!activeContext) {
@@ -73,7 +144,12 @@ export default function createCodeTmuxPlayCaptain(
73
144
  if (!activeContext) {
74
145
  throw new Error('callJudge invoked outside a Boss turn');
75
146
  }
76
- const r = await activeContext.callCaptain(prompt);
147
+ // PBRT-15 / DR-007: run the judge call hidden so its JSON
148
+ // reply never reaches the Boss pane; the runtime composes the
149
+ // human-readable pane lines (PBRT-3) from the parsed result.
150
+ const r = await activeContext.callCaptain(prompt, {
151
+ visibility: 'hidden',
152
+ });
77
153
  if (r.status !== 'ok') {
78
154
  throw new Error(
79
155
  r.error ?? `callCaptain status "${r.status}"`,
@@ -1,29 +1,44 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
  # SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
3
 
4
- # Seed template for the user-level playbook-code config.
4
+ # Seed template for the user-level playbook-code CODE overlay.
5
5
  # First run copies this file to:
6
6
  # ${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook-code.config.yaml
7
7
  #
8
- # Safe tuning points:
9
- # - Change captain.adapter / captain.model / captain.reasoningEffort to
10
- # pick the judge/Captain agent and its reasoning tier.
11
- # - Change each player's adapter, model, and reasoningEffort to pick
12
- # the Coder and Reviewer agents.
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 CODE adapter module) and the
11
+ # coder / reviewer player ids, and can inherit theme plus captain-judge
12
+ # defaults from an existing tmux-play config — so you only tune the
13
+ # fields below.
13
14
  #
14
- # PBRT-4 host-configuration invariants:
15
- # - captain.from must keep pointing at the CODE tmux-play adapter module.
16
- # - players[].id must remain exactly "coder" and "reviewer"; the runtime routes
17
- # to those baked ids and does not remap them. The adapter derives the
18
- # per-run player identity strings (substituted into <coder-llm> /
19
- # <reviewer-llm> in player prompts) from each entry's model when pinned
20
- # and falls back to its adapter when no model is set, so the Committer's
21
- # commit-message trailers can name the concrete model.
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
+
35
+ layout:
36
+ window:
37
+ columns: 174
38
+ rows: 49
39
+ columnWeights: [4, 6, 6]
22
40
 
23
41
  captain:
24
- # PBRT-4 invariant: keep this adapter module path unchanged.
25
- from: "@sublang/playbook/code/tmux-play"
26
- # Tunable: the Captain/Judge adapter, model, and reasoning effort.
27
42
  adapter: claude
28
43
  model: claude-sonnet-4-6
29
44
  reasoningEffort: high
@@ -31,17 +46,18 @@ captain:
31
46
  mode: auto
32
47
 
33
48
  players:
34
- # PBRT-4 invariant: keep id: coder. Tune adapter/model if desired.
35
- - id: coder
36
- adapter: claude
37
- model: claude-opus-4-7
49
+ coder:
50
+ adapter: codex
51
+ model: gpt-5.5
38
52
  reasoningEffort: xhigh
39
53
  permissions:
40
54
  mode: auto
41
- # PBRT-4 invariant: keep id: reviewer. Tune adapter/model if desired.
42
- - id: reviewer
43
- adapter: codex
44
- model: gpt-5.5
55
+ writablePaths:
56
+ - .git
57
+ reviewer:
58
+ adapter: claude
59
+ model: claude-opus-4-8
45
60
  reasoningEffort: xhigh
46
61
  permissions:
47
62
  mode: auto
63
+ committer: reviewer