@sublang/playbook 0.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.
@@ -0,0 +1,2 @@
1
+ import type { Captain } from '@sublang/cligent/tmux-play';
2
+ export default function createCodeTmuxPlayCaptain(options: unknown): Captain;
@@ -0,0 +1,77 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+ import createPlaybookRuntime from './code.playbook.js';
4
+ // Captain factory per TMUX-014: `(options: unknown) => Captain`.
5
+ // `options` is whatever `captain.options` carries in the YAML config;
6
+ // for the CODE playbook this is `CodePlaybookOptions` (coderPlayer,
7
+ // reviewerPlayer, plus the rest of CodingInput).
8
+ export default function createCodeTmuxPlayCaptain(options) {
9
+ const runtime = createPlaybookRuntime(options);
10
+ // CaptainSession is bound at init time and persists across turns;
11
+ // CaptainContext is rebuilt per turn and carries the call
12
+ // primitives. PlaybookPorts is built once at init, so the per-turn
13
+ // context lives in a closure-scoped slot the port callbacks query
14
+ // lazily.
15
+ let activeContext;
16
+ return {
17
+ async init(session) {
18
+ const ports = {
19
+ callPlayer: async (playerId, prompt, _signal) => {
20
+ if (!activeContext) {
21
+ throw new Error('callPlayer invoked outside a Boss turn');
22
+ }
23
+ // RoleRunResult per TMUX-033 already matches PlayerResult
24
+ // (`status: 'ok' | 'aborted' | 'error'`, `finalText?`,
25
+ // `error?`). cligent honors context.signal internally;
26
+ // the runtime's signal is the same source forwarded
27
+ // through handleBossInput, so dropping `_signal` is
28
+ // safe.
29
+ const r = await activeContext.callRole(playerId, prompt);
30
+ return {
31
+ status: r.status,
32
+ finalText: r.finalText,
33
+ error: r.error,
34
+ };
35
+ },
36
+ callJudge: async (prompt, _signal) => {
37
+ if (!activeContext) {
38
+ throw new Error('callJudge invoked outside a Boss turn');
39
+ }
40
+ const r = await activeContext.callCaptain(prompt);
41
+ if (r.status !== 'ok') {
42
+ throw new Error(r.error ?? `callCaptain status "${r.status}"`);
43
+ }
44
+ if (r.finalText === undefined) {
45
+ throw new Error('callCaptain returned status=ok with no finalText');
46
+ }
47
+ return r.finalText;
48
+ },
49
+ emitStatus: async (message, data) => {
50
+ await session.emitStatus(message, data);
51
+ },
52
+ emitTelemetry: async (event) => {
53
+ await session.emitTelemetry(event);
54
+ },
55
+ };
56
+ await runtime.init(ports);
57
+ },
58
+ async handleBossTurn(turn, context) {
59
+ activeContext = context;
60
+ try {
61
+ // Forward the Boss prompt + cligent's per-turn signal into
62
+ // the runtime; the runtime stashes the signal so the
63
+ // captain bridge passes it down to callPlayer / callJudge.
64
+ await runtime.handleBossInput({
65
+ text: turn.prompt,
66
+ signal: context.signal,
67
+ });
68
+ }
69
+ finally {
70
+ activeContext = undefined;
71
+ }
72
+ },
73
+ async dispose() {
74
+ await runtime.dispose();
75
+ },
76
+ };
77
+ }
@@ -0,0 +1,110 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ // tmux-play host adapter for the CODE playbook — DR-004 §11. This is
5
+ // the only file in IR-004 that imports cligent; removing the import
6
+ // shall not affect code.playbook.ts or its tests.
7
+
8
+ import type {
9
+ BossTurn,
10
+ Captain,
11
+ CaptainContext,
12
+ CaptainSession,
13
+ } from '@sublang/cligent/tmux-play';
14
+ import createPlaybookRuntime, {
15
+ type CodePlaybookOptions,
16
+ type PlaybookPorts,
17
+ type PlaybookRuntime,
18
+ } from './code.playbook.js';
19
+
20
+ // Captain factory per TMUX-014: `(options: unknown) => Captain`.
21
+ // `options` is whatever `captain.options` carries in the YAML config;
22
+ // for the CODE playbook this is `CodePlaybookOptions` (coderPlayer,
23
+ // reviewerPlayer, plus the rest of CodingInput).
24
+ export default function createCodeTmuxPlayCaptain(
25
+ options: unknown,
26
+ ): Captain {
27
+ const runtime: PlaybookRuntime = createPlaybookRuntime(
28
+ options as CodePlaybookOptions,
29
+ );
30
+
31
+ // CaptainSession is bound at init time and persists across turns;
32
+ // CaptainContext is rebuilt per turn and carries the call
33
+ // primitives. PlaybookPorts is built once at init, so the per-turn
34
+ // context lives in a closure-scoped slot the port callbacks query
35
+ // lazily.
36
+ let activeContext: CaptainContext | undefined;
37
+
38
+ return {
39
+ async init(session: CaptainSession): Promise<void> {
40
+ const ports: PlaybookPorts = {
41
+ callPlayer: async (playerId, prompt, _signal) => {
42
+ if (!activeContext) {
43
+ throw new Error('callPlayer invoked outside a Boss turn');
44
+ }
45
+ // RoleRunResult per TMUX-033 already matches PlayerResult
46
+ // (`status: 'ok' | 'aborted' | 'error'`, `finalText?`,
47
+ // `error?`). cligent honors context.signal internally;
48
+ // the runtime's signal is the same source forwarded
49
+ // through handleBossInput, so dropping `_signal` is
50
+ // safe.
51
+ const r = await activeContext.callRole(playerId, prompt);
52
+ return {
53
+ status: r.status,
54
+ finalText: r.finalText,
55
+ error: r.error,
56
+ };
57
+ },
58
+ callJudge: async (prompt, _signal) => {
59
+ if (!activeContext) {
60
+ throw new Error('callJudge invoked outside a Boss turn');
61
+ }
62
+ const r = await activeContext.callCaptain(prompt);
63
+ if (r.status !== 'ok') {
64
+ throw new Error(
65
+ r.error ?? `callCaptain status "${r.status}"`,
66
+ );
67
+ }
68
+ if (r.finalText === undefined) {
69
+ throw new Error(
70
+ 'callCaptain returned status=ok with no finalText',
71
+ );
72
+ }
73
+ return r.finalText;
74
+ },
75
+ emitStatus: async (message, data) => {
76
+ await session.emitStatus(
77
+ message,
78
+ data as Record<string, unknown> | undefined,
79
+ );
80
+ },
81
+ emitTelemetry: async (event) => {
82
+ await session.emitTelemetry(event);
83
+ },
84
+ };
85
+ await runtime.init(ports);
86
+ },
87
+
88
+ async handleBossTurn(
89
+ turn: BossTurn,
90
+ context: CaptainContext,
91
+ ): Promise<void> {
92
+ activeContext = context;
93
+ try {
94
+ // Forward the Boss prompt + cligent's per-turn signal into
95
+ // the runtime; the runtime stashes the signal so the
96
+ // captain bridge passes it down to callPlayer / callJudge.
97
+ await runtime.handleBossInput({
98
+ text: turn.prompt,
99
+ signal: context.signal,
100
+ });
101
+ } finally {
102
+ activeContext = undefined;
103
+ }
104
+ },
105
+
106
+ async dispose(): Promise<void> {
107
+ await runtime.dispose();
108
+ },
109
+ };
110
+ }
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@sublang/playbook",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Reference CODE playbook — XState v5 FSM, host-agnostic runtime, and tmux-play adapter for a coder/reviewer/committer loop driven by GEARS spec items.",
6
+ "license": "Apache-2.0",
7
+ "homepage": "https://github.com/sublang-ai/playbook#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/sublang-ai/playbook.git"
11
+ },
12
+ "bugs": "https://github.com/sublang-ai/playbook/issues",
13
+ "keywords": [
14
+ "ai",
15
+ "agent",
16
+ "playbook",
17
+ "state-machine",
18
+ "xstate",
19
+ "gears",
20
+ "sdlc",
21
+ "cligent",
22
+ "tmux-play"
23
+ ],
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "files": [
28
+ "code.fsm.ts",
29
+ "code.fsm.js",
30
+ "code.fsm.d.ts",
31
+ "code.fsm.introspect.ts",
32
+ "code.fsm.introspect.js",
33
+ "code.fsm.introspect.d.ts",
34
+ "code.playbook.ts",
35
+ "code.playbook.js",
36
+ "code.playbook.d.ts",
37
+ "code.tmux-play.ts",
38
+ "code.tmux-play.js",
39
+ "code.tmux-play.d.ts",
40
+ "code.gears.md",
41
+ "tmux-play.config.yaml",
42
+ "tmux-play.production.config.yaml",
43
+ "bin"
44
+ ],
45
+ "bin": {
46
+ "playbook-code": "./bin/playbook-code.js"
47
+ },
48
+ "exports": {
49
+ "./code/playbook": {
50
+ "types": "./code.playbook.d.ts",
51
+ "default": "./code.playbook.js"
52
+ },
53
+ "./code/tmux-play": {
54
+ "types": "./code.tmux-play.d.ts",
55
+ "default": "./code.tmux-play.js"
56
+ }
57
+ },
58
+ "scripts": {
59
+ "build": "tsc",
60
+ "test": "vitest run"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public",
64
+ "provenance": true
65
+ },
66
+ "peerDependencies": {
67
+ "@sublang/cligent": "^0.3.0",
68
+ "xstate": "^5.0.0"
69
+ },
70
+ "devDependencies": {
71
+ "@sublang/cligent": "^0.3.0",
72
+ "@types/node": "^22.0.0",
73
+ "typescript": "^5.8.0",
74
+ "vitest": "^3.0.0",
75
+ "xstate": "^5.19.4"
76
+ }
77
+ }
@@ -0,0 +1,33 @@
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 — DR-004 §11.
5
+ # Run with:
6
+ # tmux-play --config tmux-play.config.yaml
7
+
8
+ captain:
9
+ # Dev form: sibling-path import of the compiled adapter. After
10
+ # @sublang/playbook is published, swap this for the package
11
+ # specifier — e.g.
12
+ # from: "@sublang/playbook/code/tmux-play"
13
+ # (final form confirmed at publish time per DR-004 §11). Roles,
14
+ # options, and the rest of the config are unchanged.
15
+ from: ./code.tmux-play.js
16
+ adapter: claude
17
+ model: claude-opus-4-7
18
+ options:
19
+ # Substituted into the <coder-llm> placeholder in player prompts
20
+ # (DR-004 §6).
21
+ coderPlayer: claude
22
+ # Substituted into <reviewer-llm>.
23
+ reviewerPlayer: codex
24
+
25
+ # `roles[].id` shall match the baked playerId strings the adapter
26
+ # routes to. The CODE playbook bakes Coder→'coder' and
27
+ # Reviewer→'reviewer' at link time (DR-004 §2); the adapter does
28
+ # not remap, so changing these ids would break callRole routing.
29
+ roles:
30
+ - id: coder
31
+ adapter: claude
32
+ - id: reviewer
33
+ adapter: codex
@@ -0,0 +1,21 @@
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 adapter through the package export, not
6
+ # a local repo path. The runtime still expects the host app to provide the
7
+ # @sublang/cligent peer dependency.
8
+
9
+ captain:
10
+ from: "@sublang/playbook/code/tmux-play"
11
+ adapter: claude
12
+ model: claude-opus-4-7
13
+ options:
14
+ coderPlayer: claude
15
+ reviewerPlayer: codex
16
+
17
+ roles:
18
+ - id: coder
19
+ adapter: claude
20
+ - id: reviewer
21
+ adapter: codex