@sublang/playbook 0.2.0 → 0.4.1
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 +85 -16
- package/package.json +3 -2
- package/reference/sdlc/code.playbook/bin/playbook-code.js +271 -34
- package/reference/sdlc/code.playbook/code.fsm.js +15 -13
- package/reference/sdlc/code.playbook/code.fsm.ts +15 -13
- package/reference/sdlc/code.playbook/code.gears.md +15 -13
- package/reference/sdlc/code.playbook/code.playbook.d.ts +3 -5
- package/reference/sdlc/code.playbook/code.playbook.js +69 -63
- package/reference/sdlc/code.playbook/code.playbook.ts +70 -67
- package/reference/sdlc/code.playbook/code.tmux-play.js +30 -10
- package/reference/sdlc/code.playbook/code.tmux-play.ts +30 -13
- package/reference/sdlc/code.playbook/playbook-code.config.template.yaml +47 -0
- package/reference/sdlc/code.playbook/tmux-play.config.yaml +15 -11
- package/reference/sdlc/code.playbook/tmux-play.production.config.yaml +7 -5
package/README.md
CHANGED
|
@@ -43,33 +43,102 @@ the slc pipeline's `<basename>.<pipeline>/` output directory.
|
|
|
43
43
|
|
|
44
44
|
### Install (users)
|
|
45
45
|
|
|
46
|
-
Install the package globally.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
dependencies, so a single install pulls in everything:
|
|
46
|
+
Install the package globally. `@sublang/cligent`, the Claude and
|
|
47
|
+
Codex adapter SDKs, and `xstate` are direct dependencies, so a single
|
|
48
|
+
install pulls in the reference CODE playbook and its host:
|
|
50
49
|
|
|
51
50
|
```sh
|
|
52
51
|
npm install -g @sublang/playbook
|
|
53
52
|
```
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
Then launch the reference playbook in a `tmux-play` session:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
playbook-code
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For a one-shot run without a global install, use the same command
|
|
61
|
+
through npx:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npx playbook-code
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
On first run, `playbook-code` creates a commented user config at
|
|
68
|
+
`${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook-code.config.yaml`
|
|
69
|
+
from the bundled template, prints that path to stderr, then checks the
|
|
70
|
+
declared adapters before launching. Later runs reuse that file and do
|
|
71
|
+
not overwrite it.
|
|
72
|
+
|
|
73
|
+
Known adapter readiness is intentionally light: `claude` is ready when
|
|
74
|
+
local Claude Code auth exists or `ANTHROPIC_API_KEY` is set; `codex` is
|
|
75
|
+
ready when local Codex CLI auth exists or `OPENAI_API_KEY` is set. If a
|
|
76
|
+
known adapter is not ready, `playbook-code` prints its own help text
|
|
77
|
+
with the config path, auth pointers, and agent-swap recipe, then exits
|
|
78
|
+
without launching. You can view the same recovery text at any time:
|
|
58
79
|
|
|
59
|
-
|
|
80
|
+
```sh
|
|
81
|
+
playbook-code --help
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The seed template runs each agent in cligent's protected auto mode
|
|
60
85
|
(`permissions.mode: auto`), suppressing routine approval prompts.
|
|
61
86
|
|
|
62
|
-
|
|
87
|
+
### Configure agents
|
|
88
|
+
|
|
89
|
+
Edit the seeded user config when you want different coding agents:
|
|
63
90
|
|
|
64
91
|
```sh
|
|
65
|
-
playbook-code
|
|
92
|
+
$EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook-code.config.yaml"
|
|
66
93
|
```
|
|
67
94
|
|
|
68
|
-
|
|
69
|
-
`tmux-play
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
`
|
|
95
|
+
Both CODE players can use `claude` or `codex`; other adapter ids are
|
|
96
|
+
passed through to `tmux-play` with a warning because `playbook-code`
|
|
97
|
+
does not know how to preflight their auth. The safe tuning points are
|
|
98
|
+
`captain.adapter`, `captain.model`, each player's `adapter`, and each
|
|
99
|
+
player's `model`. Keep `captain.from` and the `players[].id` values
|
|
100
|
+
fixed; the runtime binds to those host-configuration invariants per
|
|
101
|
+
[PBRT-4](specs/user/playbook-runtime.md#pbrt-4) and derives the
|
|
102
|
+
`<coder-llm>` / `<reviewer-llm>` substitution strings from each
|
|
103
|
+
player entry's `model` when pinned and `adapter` otherwise — so the
|
|
104
|
+
Committer's commit-message trailers can name the concrete model
|
|
105
|
+
(e.g. `claude-opus-4-7`) rather than the adapter family (`claude`).
|
|
106
|
+
|
|
107
|
+
For example, this swaps the Coder to Codex and the Reviewer to Claude:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
captain:
|
|
111
|
+
from: "@sublang/playbook/code/tmux-play"
|
|
112
|
+
adapter: claude
|
|
113
|
+
model: claude-sonnet-4-6
|
|
114
|
+
reasoningEffort: high
|
|
115
|
+
permissions:
|
|
116
|
+
mode: auto
|
|
117
|
+
|
|
118
|
+
players:
|
|
119
|
+
- id: coder # must stay `coder` — see PBRT-4
|
|
120
|
+
adapter: codex
|
|
121
|
+
model: gpt-5.5
|
|
122
|
+
reasoningEffort: xhigh
|
|
123
|
+
permissions:
|
|
124
|
+
mode: auto
|
|
125
|
+
- id: reviewer # must stay `reviewer` — see PBRT-4
|
|
126
|
+
adapter: claude
|
|
127
|
+
model: claude-opus-4-7
|
|
128
|
+
reasoningEffort: xhigh
|
|
129
|
+
permissions:
|
|
130
|
+
mode: auto
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Normal `playbook-code` runs use the seeded path above. If you need a
|
|
134
|
+
separate config file for a one-off run, pass it explicitly; this bypasses
|
|
135
|
+
the seed and readiness gate and forwards the arguments to `tmux-play`
|
|
136
|
+
verbatim, as pinned in
|
|
137
|
+
[PBCODE-1](specs/user/playbook-code.md#pbcode-1):
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
playbook-code --config ./playbook-code.config.yaml
|
|
141
|
+
```
|
|
73
142
|
|
|
74
143
|
### Install (contributors / from source)
|
|
75
144
|
|
|
@@ -83,7 +152,7 @@ pnpm build
|
|
|
83
152
|
pnpm test
|
|
84
153
|
```
|
|
85
154
|
|
|
86
|
-
`pnpm install` resolves `@sublang/cligent` (^0.
|
|
155
|
+
`pnpm install` resolves `@sublang/cligent` (^0.6.0) from the registry;
|
|
87
156
|
no local link required. To point pnpm at a local `cligent` checkout
|
|
88
157
|
instead, copy
|
|
89
158
|
[`pnpm-workspace.yaml.example`](pnpm-workspace.yaml.example)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sublang/playbook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
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
6
|
"license": "Apache-2.0",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"reference/sdlc/code.playbook/code.gears.md",
|
|
41
41
|
"reference/sdlc/code.playbook/tmux-play.config.yaml",
|
|
42
42
|
"reference/sdlc/code.playbook/tmux-play.production.config.yaml",
|
|
43
|
+
"reference/sdlc/code.playbook/playbook-code.config.template.yaml",
|
|
43
44
|
"reference/sdlc/code.playbook/bin"
|
|
44
45
|
],
|
|
45
46
|
"bin": {
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"dependencies": {
|
|
67
68
|
"@anthropic-ai/claude-agent-sdk": "^0.3.143",
|
|
68
69
|
"@openai/codex-sdk": "^0.130.0",
|
|
69
|
-
"@sublang/cligent": "^0.
|
|
70
|
+
"@sublang/cligent": "^0.6.0",
|
|
70
71
|
"xstate": "^5.19.4"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
@@ -2,42 +2,279 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
4
4
|
|
|
5
|
-
// Thin shim that resolves the bundled tmux-play.production.config.yaml
|
|
6
|
-
// relative to this script's own location and execs cligent's tmux-play
|
|
7
|
-
// CLI from inside @sublang/playbook's own node_modules tree. We resolve
|
|
8
|
-
// the CLI through cligent's `./tmux-play` subpath export instead of
|
|
9
|
-
// relying on `tmux-play` being on PATH — npm global installs only link
|
|
10
|
-
// bins from top-level packages, so a transitive cligent's bin won't
|
|
11
|
-
// reach the user's PATH but its files are still resolvable here.
|
|
12
|
-
|
|
13
5
|
import { spawn } from 'node:child_process';
|
|
6
|
+
import {
|
|
7
|
+
constants,
|
|
8
|
+
copyFileSync,
|
|
9
|
+
existsSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
realpathSync,
|
|
13
|
+
} from 'node:fs';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
14
15
|
import { dirname, join, resolve } from 'node:path';
|
|
15
16
|
import { fileURLToPath } from 'node:url';
|
|
16
17
|
|
|
17
18
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
19
|
+
const templatePath = resolve(here, '..', 'playbook-code.config.template.yaml');
|
|
20
|
+
const READINESS_FAILURE_EXIT_CODE = 2;
|
|
21
|
+
|
|
22
|
+
export async function runPlaybookCodeCli(options = {}) {
|
|
23
|
+
const argv = [...(options.argv ?? process.argv.slice(2))];
|
|
24
|
+
const env = options.env ?? process.env;
|
|
25
|
+
const stdout = options.stdout ?? process.stdout;
|
|
26
|
+
const stderr = options.stderr ?? process.stderr;
|
|
27
|
+
const spawnFn = options.spawn ?? spawn;
|
|
28
|
+
const tmuxPlayBin = options.tmuxPlayBin ?? resolveTmuxPlayBin();
|
|
29
|
+
const home = options.homeDir ?? env.HOME ?? homedir();
|
|
30
|
+
const userConfigPath = resolveUserConfigPath(env, home);
|
|
31
|
+
|
|
32
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
33
|
+
stdout.write(helpText({ userConfigPath }));
|
|
34
|
+
return { code: 0 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const explicitConfig = hasExplicitConfig(argv);
|
|
38
|
+
const configPath = explicitConfig ? undefined : userConfigPath;
|
|
39
|
+
|
|
40
|
+
if (!explicitConfig) {
|
|
41
|
+
seedUserConfigIfMissing(userConfigPath, stderr);
|
|
42
|
+
const readiness = checkReadiness(userConfigPath, env, home);
|
|
43
|
+
for (const adapter of readiness.unknownAdapters) {
|
|
44
|
+
stderr.write(
|
|
45
|
+
`playbook-code: warning: no readiness check for adapter "${adapter}"\n`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (readiness.failingAdapters.length > 0) {
|
|
49
|
+
stderr.write(
|
|
50
|
+
helpText({
|
|
51
|
+
userConfigPath,
|
|
52
|
+
failingAdapters: readiness.failingAdapters,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
return { code: READINESS_FAILURE_EXIT_CODE };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const childArgs = explicitConfig
|
|
60
|
+
? [tmuxPlayBin, ...argv]
|
|
61
|
+
: [tmuxPlayBin, '--config', configPath, ...argv];
|
|
62
|
+
return await launchTmuxPlay(spawnFn, childArgs, stderr);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resolveUserConfigPath(env = process.env, home = homedir()) {
|
|
66
|
+
const configHome = env.XDG_CONFIG_HOME || join(home, '.config');
|
|
67
|
+
return join(configHome, 'playbook', 'playbook-code.config.yaml');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function collectAdaptersFromConfig(source) {
|
|
71
|
+
const adapters = new Set();
|
|
72
|
+
let section = '';
|
|
73
|
+
let captainChildIndent;
|
|
74
|
+
let playerItemIndent;
|
|
75
|
+
let playerChildIndent;
|
|
76
|
+
|
|
77
|
+
for (const line of source.split(/\r?\n/)) {
|
|
78
|
+
const uncommented = stripYamlComment(line);
|
|
79
|
+
if (uncommented.trim() === '') continue;
|
|
80
|
+
|
|
81
|
+
const indent = leadingSpaceCount(uncommented);
|
|
82
|
+
const trimmed = uncommented.trim();
|
|
83
|
+
|
|
84
|
+
if (indent === 0) {
|
|
85
|
+
if (trimmed === 'captain:') section = 'captain';
|
|
86
|
+
else if (trimmed === 'players:') section = 'players';
|
|
87
|
+
else section = '';
|
|
88
|
+
captainChildIndent = undefined;
|
|
89
|
+
playerItemIndent = undefined;
|
|
90
|
+
playerChildIndent = undefined;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (section === 'captain') {
|
|
95
|
+
captainChildIndent ??= indent;
|
|
96
|
+
if (indent === captainChildIndent) {
|
|
97
|
+
collectAdapterValue(trimmed, adapters);
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (section === 'players') {
|
|
103
|
+
if (trimmed.startsWith('- ')) {
|
|
104
|
+
playerItemIndent = indent;
|
|
105
|
+
playerChildIndent = undefined;
|
|
106
|
+
collectAdapterValue(trimmed.slice(2).trim(), adapters);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (playerItemIndent !== undefined && indent > playerItemIndent) {
|
|
110
|
+
playerChildIndent ??= indent;
|
|
111
|
+
if (indent === playerChildIndent) {
|
|
112
|
+
collectAdapterValue(trimmed, adapters);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return [...adapters];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function checkReadiness(configPath, env = process.env, home = homedir()) {
|
|
121
|
+
const config = readFileSync(configPath, 'utf8');
|
|
122
|
+
const adapters = collectAdaptersFromConfig(config);
|
|
123
|
+
const failingAdapters = [];
|
|
124
|
+
const unknownAdapters = [];
|
|
125
|
+
|
|
126
|
+
for (const adapter of adapters) {
|
|
127
|
+
if (adapter === 'claude') {
|
|
128
|
+
if (!env.ANTHROPIC_API_KEY && !existsSync(join(home, '.claude'))) {
|
|
129
|
+
failingAdapters.push(adapter);
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (adapter === 'codex') {
|
|
134
|
+
if (!env.OPENAI_API_KEY && !existsSync(join(home, '.codex'))) {
|
|
135
|
+
failingAdapters.push(adapter);
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
unknownAdapters.push(adapter);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { failingAdapters, unknownAdapters };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function seedUserConfigIfMissing(userConfigPath, stderr) {
|
|
146
|
+
if (existsSync(userConfigPath)) return;
|
|
147
|
+
mkdirSync(dirname(userConfigPath), { recursive: true });
|
|
148
|
+
copyFileSync(templatePath, userConfigPath, constants.COPYFILE_EXCL);
|
|
149
|
+
stderr.write(`playbook-code: created config at ${userConfigPath}\n`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function hasExplicitConfig(argv) {
|
|
153
|
+
return argv.some((arg) => arg === '--config' || arg.startsWith('--config='));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function helpText({ userConfigPath, failingAdapters = [] }) {
|
|
157
|
+
const failures =
|
|
158
|
+
failingAdapters.length > 0
|
|
159
|
+
? [
|
|
160
|
+
`Adapters not ready: ${failingAdapters.join(', ')}`,
|
|
161
|
+
'',
|
|
162
|
+
]
|
|
163
|
+
: [];
|
|
164
|
+
return [
|
|
165
|
+
...failures,
|
|
166
|
+
'Usage:',
|
|
167
|
+
' playbook-code [--config <path>] [tmux-play options]',
|
|
168
|
+
' playbook-code --help',
|
|
169
|
+
'',
|
|
170
|
+
`Default config: ${userConfigPath}`,
|
|
171
|
+
'',
|
|
172
|
+
'Adapter setup:',
|
|
173
|
+
' claude: run Claude Code once or set ANTHROPIC_API_KEY.',
|
|
174
|
+
' codex: run Codex CLI once or set OPENAI_API_KEY.',
|
|
175
|
+
'',
|
|
176
|
+
'Agent swap recipe:',
|
|
177
|
+
' - change captain.adapter and captain.model for the Captain/Judge',
|
|
178
|
+
' - change each player adapter and (optional) model for the Coder',
|
|
179
|
+
' and Reviewer; model when pinned, else adapter, is substituted',
|
|
180
|
+
' into <coder-llm>/<reviewer-llm> player prompts (PBRT-4)',
|
|
181
|
+
' - keep captain.from and players[].id fixed',
|
|
182
|
+
'',
|
|
183
|
+
].join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function launchTmuxPlay(spawnFn, childArgs, stderr) {
|
|
187
|
+
return await new Promise((resolveResult) => {
|
|
188
|
+
let child;
|
|
189
|
+
try {
|
|
190
|
+
child = spawnFn(process.execPath, childArgs, { stdio: 'inherit' });
|
|
191
|
+
} catch (error) {
|
|
192
|
+
stderr.write(`playbook-code: failed to launch tmux-play: ${errorMessage(error)}\n`);
|
|
193
|
+
resolveResult({ code: 127 });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let settled = false;
|
|
198
|
+
const settle = (result) => {
|
|
199
|
+
if (settled) return;
|
|
200
|
+
settled = true;
|
|
201
|
+
resolveResult(result);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
child.on('error', (err) => {
|
|
205
|
+
stderr.write(
|
|
206
|
+
`playbook-code: failed to launch tmux-play: ${errorMessage(err)}\n`,
|
|
207
|
+
);
|
|
208
|
+
settle({ code: 127 });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
child.on('exit', (code, signal) => {
|
|
212
|
+
if (signal) settle({ signal });
|
|
213
|
+
else settle({ code: code ?? 0 });
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function resolveTmuxPlayBin() {
|
|
219
|
+
const tmuxPlayIndexUrl = import.meta.resolve('@sublang/cligent/tmux-play');
|
|
220
|
+
return join(dirname(fileURLToPath(tmuxPlayIndexUrl)), 'cli.js');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function stripYamlComment(line) {
|
|
224
|
+
let quote = '';
|
|
225
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
226
|
+
const char = line[index];
|
|
227
|
+
if ((char === '"' || char === "'") && line[index - 1] !== '\\') {
|
|
228
|
+
quote = quote === char ? '' : quote || char;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (char === '#' && !quote) {
|
|
232
|
+
return line.slice(0, index);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return line;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function unquoteYamlScalar(value) {
|
|
239
|
+
const trimmed = value.trim();
|
|
240
|
+
if (
|
|
241
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
242
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
243
|
+
) {
|
|
244
|
+
return trimmed.slice(1, -1).trim();
|
|
245
|
+
}
|
|
246
|
+
return trimmed;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function collectAdapterValue(trimmedLine, adapters) {
|
|
250
|
+
const match = trimmedLine.match(/^adapter\s*:\s*(.+?)\s*$/);
|
|
251
|
+
if (!match) return;
|
|
252
|
+
const value = unquoteYamlScalar(match[1].trim());
|
|
253
|
+
if (value) adapters.add(value);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function leadingSpaceCount(line) {
|
|
257
|
+
return line.length - line.trimStart().length;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function errorMessage(error) {
|
|
261
|
+
return error instanceof Error ? error.message : String(error);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isCliEntry(
|
|
265
|
+
argv1 = process.argv[1],
|
|
266
|
+
moduleUrl = import.meta.url,
|
|
267
|
+
) {
|
|
268
|
+
if (!argv1) return false;
|
|
269
|
+
try {
|
|
270
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
271
|
+
} catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (isCliEntry()) {
|
|
277
|
+
const result = await runPlaybookCodeCli();
|
|
278
|
+
if (result.signal) process.kill(process.pid, result.signal);
|
|
279
|
+
else process.exit(result.code ?? 0);
|
|
280
|
+
}
|
|
@@ -113,7 +113,7 @@ const planAndImplementInput = (context) => ({
|
|
|
113
113
|
prompt: [
|
|
114
114
|
'Assess whether this can be completed in a single commit, following best practices.',
|
|
115
115
|
'If yes, implement and test, updating both code and specs; otherwise, decompose into tasks as a new IR under @specs/iterations.',
|
|
116
|
-
'Consult @specs/map.md for relevant context if needed; ensure
|
|
116
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; ensure @specs/map.md reflects the changes.',
|
|
117
117
|
'Do not commit.',
|
|
118
118
|
].join('\n'),
|
|
119
119
|
result: withNeedsBossReply({
|
|
@@ -501,7 +501,7 @@ export const codingMachine = setup({
|
|
|
501
501
|
'- Minimal: essential and concise; every item earns its place; also check with other items.',
|
|
502
502
|
'',
|
|
503
503
|
'Flag anything missing, redundant, over-specified, or under-specified.',
|
|
504
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
504
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
505
505
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
506
506
|
].join('\n'),
|
|
507
507
|
result: {
|
|
@@ -538,7 +538,7 @@ export const codingMachine = setup({
|
|
|
538
538
|
'Refer to the commit message.',
|
|
539
539
|
'Flag any issues or improvements (numbered; no duplication).',
|
|
540
540
|
"Think thoroughly — don't just approve or reject.",
|
|
541
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
541
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
542
542
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
543
543
|
].join('\n'),
|
|
544
544
|
result: {
|
|
@@ -582,7 +582,7 @@ export const codingMachine = setup({
|
|
|
582
582
|
'Flag anything missing, redundant, over-specified, or under-specified.',
|
|
583
583
|
'Flag any issues or improvements (numbered; no duplication).',
|
|
584
584
|
"Think thoroughly — don't just approve or reject.",
|
|
585
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
585
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
586
586
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
587
587
|
].join('\n'),
|
|
588
588
|
result: {
|
|
@@ -625,7 +625,7 @@ export const codingMachine = setup({
|
|
|
625
625
|
'- Minimal: essential and concise; every item earns its place; also check with other items.',
|
|
626
626
|
'',
|
|
627
627
|
'Flag anything missing, redundant, over-specified, or under-specified.',
|
|
628
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
628
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
629
629
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
630
630
|
].join('\n'),
|
|
631
631
|
result: {
|
|
@@ -663,7 +663,7 @@ export const codingMachine = setup({
|
|
|
663
663
|
'Refer to the commit message.',
|
|
664
664
|
'Flag any issues or improvements (numbered; no duplication).',
|
|
665
665
|
"Think thoroughly — don't just approve or reject.",
|
|
666
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
666
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
667
667
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
668
668
|
].join('\n'),
|
|
669
669
|
result: {
|
|
@@ -708,7 +708,7 @@ export const codingMachine = setup({
|
|
|
708
708
|
'Flag anything missing, redundant, over-specified, or under-specified.',
|
|
709
709
|
'Flag any issues or improvements (numbered; no duplication).',
|
|
710
710
|
"Think thoroughly — don't just approve or reject.",
|
|
711
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
711
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
712
712
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
713
713
|
].join('\n'),
|
|
714
714
|
result: {
|
|
@@ -749,7 +749,7 @@ export const codingMachine = setup({
|
|
|
749
749
|
'- Minimal: essential and concise; every item earns its place; also check with other items.',
|
|
750
750
|
'',
|
|
751
751
|
'Flag anything missing, redundant, over-specified, or under-specified.',
|
|
752
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
752
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
753
753
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
754
754
|
].join('\n'),
|
|
755
755
|
result: {
|
|
@@ -787,7 +787,7 @@ export const codingMachine = setup({
|
|
|
787
787
|
'Understand the intent.',
|
|
788
788
|
'Flag any issues or improvements (numbered; no duplication).',
|
|
789
789
|
"Think thoroughly — don't just approve or reject.",
|
|
790
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
790
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
791
791
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
792
792
|
].join('\n'),
|
|
793
793
|
result: {
|
|
@@ -832,7 +832,7 @@ export const codingMachine = setup({
|
|
|
832
832
|
'Flag anything missing, redundant, over-specified, or under-specified.',
|
|
833
833
|
'Flag any issues or improvements (numbered; no duplication).',
|
|
834
834
|
"Think thoroughly — don't just approve or reject.",
|
|
835
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
835
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
836
836
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
837
837
|
].join('\n'),
|
|
838
838
|
result: {
|
|
@@ -877,7 +877,7 @@ export const codingMachine = setup({
|
|
|
877
877
|
'- Minimal: essential and concise; every item earns its place; also check with other items.',
|
|
878
878
|
'',
|
|
879
879
|
'Flag anything missing, redundant, over-specified, or under-specified.',
|
|
880
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
880
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
881
881
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
882
882
|
'For each rebuttal below, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
|
|
883
883
|
].join('\n'),
|
|
@@ -918,7 +918,7 @@ export const codingMachine = setup({
|
|
|
918
918
|
'Understand the intent.',
|
|
919
919
|
'Flag any issues or improvements (numbered; no duplication).',
|
|
920
920
|
"Think thoroughly — don't just approve or reject.",
|
|
921
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
921
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
922
922
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
923
923
|
'For each rebuttal below, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
|
|
924
924
|
].join('\n'),
|
|
@@ -966,7 +966,7 @@ export const codingMachine = setup({
|
|
|
966
966
|
'Flag anything missing, redundant, over-specified, or under-specified.',
|
|
967
967
|
'Flag any issues or improvements (numbered; no duplication).',
|
|
968
968
|
"Think thoroughly — don't just approve or reject.",
|
|
969
|
-
'Consult @specs/map.md for relevant context if needed; verify
|
|
969
|
+
'Consult @specs/map.md or @specs/meta.md for relevant context if needed; verify @specs/map.md reflects the changes.',
|
|
970
970
|
"If the change is ready to commit or push, don't raise nitpicks.",
|
|
971
971
|
'For each rebuttal below, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.',
|
|
972
972
|
].join('\n'),
|
|
@@ -1066,6 +1066,7 @@ export const codingMachine = setup({
|
|
|
1066
1066
|
prompt: [
|
|
1067
1067
|
'Make a commit of the changes that belong in the repo, following @specs/dev/git.md (reread if necessary).',
|
|
1068
1068
|
'Coder is <coder-llm>.',
|
|
1069
|
+
'Format the `Co-authored-by` `<model>` token as the conventional human form of the substituted id (e.g., `claude-opus-4-7` → `Claude-Opus-4.7`, `gpt-5.5` → `GPT-5.5`).',
|
|
1069
1070
|
].join('\n'),
|
|
1070
1071
|
result: {
|
|
1071
1072
|
committedSpecs: 'Committed changes that touch only @specs/{user,dev,test}/.',
|
|
@@ -1127,6 +1128,7 @@ export const codingMachine = setup({
|
|
|
1127
1128
|
prompt: [
|
|
1128
1129
|
'Make a commit of the changes that belong in the repo, following @specs/dev/git.md (reread if necessary).',
|
|
1129
1130
|
'Coder is <coder-llm>; Reviewer is <reviewer-llm>.',
|
|
1131
|
+
'Format the `Co-authored-by` `<model>` token as the conventional human form of the substituted id (e.g., `claude-opus-4-7` → `Claude-Opus-4.7`, `gpt-5.5` → `GPT-5.5`).',
|
|
1130
1132
|
].join('\n'),
|
|
1131
1133
|
result: {
|
|
1132
1134
|
committed: 'Relevant changes were committed.',
|