@sublang/playbook 0.2.0 → 0.4.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 CHANGED
@@ -43,33 +43,95 @@ the slc pipeline's `<basename>.<pipeline>/` output directory.
43
43
 
44
44
  ### Install (users)
45
45
 
46
- Install the package globally. The bundled production config wires
47
- Anthropic (Captain + Coder) and OpenAI Codex (Reviewer); `@sublang/
48
- cligent`, the two adapter SDKs, and `xstate` are all direct
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
- Each adapter reads its own auth: the Claude SDK uses your local
56
- Claude Code auth (or `ANTHROPIC_API_KEY`); the Codex SDK uses your
57
- local codex CLI auth (or `OPENAI_API_KEY`).
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
- The bundled configs run each agent in cligent's protected auto mode
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
- Then launch the reference playbook in a `tmux-play` session:
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
- The `playbook-code` bin resolves the bundled
69
- `tmux-play.production.config.yaml` and execs the bundled `tmux-play`
70
- CLI (from playbook's own `node_modules/@sublang/cligent`) against it;
71
- any extra flags pass through (`playbook-code --help` lists
72
- `tmux-play`'s).
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`, and each player's `adapter`. Keep
99
+ `captain.from` and the `players[].id` values fixed; the runtime binds
100
+ 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 `adapter`.
104
+
105
+ For example, this swaps the Coder to Codex and the Reviewer to Claude:
106
+
107
+ ```yaml
108
+ captain:
109
+ from: "@sublang/playbook/code/tmux-play"
110
+ adapter: claude
111
+ model: claude-opus-4-7
112
+ permissions:
113
+ mode: auto
114
+
115
+ players:
116
+ - id: coder # must stay `coder` — see PBRT-4
117
+ adapter: codex
118
+ permissions:
119
+ mode: auto
120
+ - id: reviewer # must stay `reviewer` — see PBRT-4
121
+ adapter: claude
122
+ permissions:
123
+ mode: auto
124
+ ```
125
+
126
+ Normal `playbook-code` runs use the seeded path above. If you need a
127
+ separate config file for a one-off run, pass it explicitly; this bypasses
128
+ the seed and readiness gate and forwards the arguments to `tmux-play`
129
+ verbatim, as pinned in
130
+ [PBCODE-1](specs/user/playbook-code.md#pbcode-1):
131
+
132
+ ```sh
133
+ playbook-code --config ./playbook-code.config.yaml
134
+ ```
73
135
 
74
136
  ### Install (contributors / from source)
75
137
 
@@ -83,7 +145,7 @@ pnpm build
83
145
  pnpm test
84
146
  ```
85
147
 
86
- `pnpm install` resolves `@sublang/cligent` (^0.4.0) from the registry;
148
+ `pnpm install` resolves `@sublang/cligent` (^0.6.0) from the registry;
87
149
  no local link required. To point pnpm at a local `cligent` checkout
88
150
  instead, copy
89
151
  [`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.2.0",
3
+ "version": "0.4.0",
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.4.0",
70
+ "@sublang/cligent": "^0.6.0",
70
71
  "xstate": "^5.19.4"
71
72
  },
72
73
  "devDependencies": {
@@ -2,42 +2,277 @@
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 configPath = resolve(here, '..', 'tmux-play.production.config.yaml');
19
-
20
- // cligent's `./tmux-play` subpath export only declares "import"
21
- // (no "require"/"default"), so createRequire(...).resolve trips
22
- // ERR_PACKAGE_PATH_NOT_EXPORTED. import.meta.resolve uses ESM rules
23
- // and follows the "import" condition — works from Node 20.6+.
24
- const tmuxPlayIndexUrl = import.meta.resolve('@sublang/cligent/tmux-play');
25
- const tmuxPlayBin = join(dirname(fileURLToPath(tmuxPlayIndexUrl)), 'cli.js');
26
-
27
- const child = spawn(
28
- process.execPath,
29
- [tmuxPlayBin, '--config', configPath, ...process.argv.slice(2)],
30
- { stdio: 'inherit' },
31
- );
32
-
33
- child.on('error', (err) => {
34
- process.stderr.write(
35
- `playbook-code: failed to launch tmux-play (${tmuxPlayBin}): ${err.message}\n`,
36
- );
37
- process.exit(127);
38
- });
39
-
40
- child.on('exit', (code, signal) => {
41
- if (signal) process.kill(process.pid, signal);
42
- else process.exit(code ?? 0);
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 for the Coder and Reviewer',
179
+ ' - keep captain.from and players[].id fixed',
180
+ '',
181
+ ].join('\n');
182
+ }
183
+
184
+ async function launchTmuxPlay(spawnFn, childArgs, stderr) {
185
+ return await new Promise((resolveResult) => {
186
+ let child;
187
+ try {
188
+ child = spawnFn(process.execPath, childArgs, { stdio: 'inherit' });
189
+ } catch (error) {
190
+ stderr.write(`playbook-code: failed to launch tmux-play: ${errorMessage(error)}\n`);
191
+ resolveResult({ code: 127 });
192
+ return;
193
+ }
194
+
195
+ let settled = false;
196
+ const settle = (result) => {
197
+ if (settled) return;
198
+ settled = true;
199
+ resolveResult(result);
200
+ };
201
+
202
+ child.on('error', (err) => {
203
+ stderr.write(
204
+ `playbook-code: failed to launch tmux-play: ${errorMessage(err)}\n`,
205
+ );
206
+ settle({ code: 127 });
207
+ });
208
+
209
+ child.on('exit', (code, signal) => {
210
+ if (signal) settle({ signal });
211
+ else settle({ code: code ?? 0 });
212
+ });
213
+ });
214
+ }
215
+
216
+ function resolveTmuxPlayBin() {
217
+ const tmuxPlayIndexUrl = import.meta.resolve('@sublang/cligent/tmux-play');
218
+ return join(dirname(fileURLToPath(tmuxPlayIndexUrl)), 'cli.js');
219
+ }
220
+
221
+ function stripYamlComment(line) {
222
+ let quote = '';
223
+ for (let index = 0; index < line.length; index += 1) {
224
+ const char = line[index];
225
+ if ((char === '"' || char === "'") && line[index - 1] !== '\\') {
226
+ quote = quote === char ? '' : quote || char;
227
+ continue;
228
+ }
229
+ if (char === '#' && !quote) {
230
+ return line.slice(0, index);
231
+ }
232
+ }
233
+ return line;
234
+ }
235
+
236
+ function unquoteYamlScalar(value) {
237
+ const trimmed = value.trim();
238
+ if (
239
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
240
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
241
+ ) {
242
+ return trimmed.slice(1, -1).trim();
243
+ }
244
+ return trimmed;
245
+ }
246
+
247
+ function collectAdapterValue(trimmedLine, adapters) {
248
+ const match = trimmedLine.match(/^adapter\s*:\s*(.+?)\s*$/);
249
+ if (!match) return;
250
+ const value = unquoteYamlScalar(match[1].trim());
251
+ if (value) adapters.add(value);
252
+ }
253
+
254
+ function leadingSpaceCount(line) {
255
+ return line.length - line.trimStart().length;
256
+ }
257
+
258
+ function errorMessage(error) {
259
+ return error instanceof Error ? error.message : String(error);
260
+ }
261
+
262
+ function isCliEntry(
263
+ argv1 = process.argv[1],
264
+ moduleUrl = import.meta.url,
265
+ ) {
266
+ if (!argv1) return false;
267
+ try {
268
+ return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
269
+ } catch {
270
+ return false;
271
+ }
272
+ }
273
+
274
+ if (isCliEntry()) {
275
+ const result = await runPlaybookCodeCli();
276
+ if (result.signal) process.kill(process.pid, result.signal);
277
+ else process.exit(result.code ?? 0);
278
+ }
@@ -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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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 it reflects the changes.',
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'),