@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.
- package/LICENSE +201 -0
- package/README.md +181 -0
- package/bin/playbook-code.js +40 -0
- package/code.fsm.d.ts +86 -0
- package/code.fsm.introspect.d.ts +30 -0
- package/code.fsm.introspect.js +50 -0
- package/code.fsm.introspect.ts +129 -0
- package/code.fsm.js +1022 -0
- package/code.fsm.ts +1224 -0
- package/code.gears.md +250 -0
- package/code.playbook.d.ts +53 -0
- package/code.playbook.js +598 -0
- package/code.playbook.ts +743 -0
- package/code.tmux-play.d.ts +2 -0
- package/code.tmux-play.js +77 -0
- package/code.tmux-play.ts +110 -0
- package/package.json +77 -0
- package/tmux-play.config.yaml +33 -0
- package/tmux-play.production.config.yaml +21 -0
|
@@ -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
|