@kaydash9999/banker-plugins 0.26.1 → 0.27.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +9 -6
- package/bin/lib/codex-hooks.mjs +7 -1
- package/codex/command-skills/front-qa/SKILL.md +3 -1
- package/codex/hooks.json +15 -5
- package/codex/manifest.json +1 -0
- package/codex/skills/ralph/SKILL.md +272 -0
- package/codex/skills/ralph/scripts/gate-evidence.mjs +457 -0
- package/codex/skills/ralph/scripts/ralph-state.mjs +622 -0
- package/codex/skills/ralph/scripts/run-folder.mjs +299 -0
- package/codex/transform-matrix.md +5 -4
- package/commands/front-qa.md +3 -1
- package/hooks/ralph-continue-codex.mjs +59 -0
- package/package.json +1 -1
- package/skills/all-in-one/SKILL.md +22 -8
- package/skills/ralph-qa/references/gate-evidence.mjs +3 -3
- package/skills/ralph-qa/references/run-folder.mjs +3 -3
- package/skills/ultra-chain/SKILL.md +11 -4
- package/skills/ultra-init/SKILL.md +9 -7
- package/skills/ultraqa/references/gate-evidence.mjs +3 -3
- package/skills/ultraqa/references/run-folder.mjs +3 -3
- package/skills/visual-ralph/SKILL.md +9 -4
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* run-folder: the private run folders of ralph-qa, ultraqa and the Codex ralph, and the work tree snapshot their gate
|
|
4
|
+
* evidence is judged against. The source of truth is skills/ralph-qa/references/run-folder.mjs;
|
|
5
|
+
* skills/ultraqa/references and codex/skills/ralph/scripts hold byte-identical copies (scripts/sync-adapter.js).
|
|
6
|
+
*
|
|
7
|
+
* bases(ctx) { base, known }: this run's base, and every base a run of any version used
|
|
8
|
+
* sharedAbove(p) the nearest folder above p every account may write in, or null (POSIX)
|
|
9
|
+
* readyBase(ctx, base) makes the base, private (POSIX), or stops
|
|
10
|
+
* makeRunFolder(ctx, kind) <base>/<kind>.<six letters and digits>, private, with its marker .<kind>
|
|
11
|
+
* runFolder(dir, ctx, kinds) { d, kind } for such a folder right under a known private base, else a stop
|
|
12
|
+
* removeMarked(p, marker) the folder's contents, then its marker, then the folder
|
|
13
|
+
* removeRunFolder(dir, ctx, kinds) one run folder, checked as runFolder checks it; nothing outside it
|
|
14
|
+
* snapshot(repo, env) (async) HEAD, the status, the untracked files' content hashes, one fingerprint
|
|
15
|
+
* requireGitTree(repo, env, flag) the work tree's root, or the stop not-a-git-tree naming the folder it checked
|
|
16
|
+
* spawnFiles(exe, args, opts) spawnSync with files, not pipes, for the child's input and output
|
|
17
|
+
* hashFile(createHash, p) a file's sha256 read in pieces (a file past 2 GiB is no Buffer)
|
|
18
|
+
* lstatOr, isFile, listDir, norm(ctx, p), realOr small fs helpers seat-run.mjs shares (null, [], p on an error)
|
|
19
|
+
*
|
|
20
|
+
* ctx is { env, platform }. A base is never /tmp: another account may put files there (gemini, a ralph-qa seat, reads
|
|
21
|
+
* a .env it finds walking up from its folder). No shell and no network: git runs as a program with its arguments.
|
|
22
|
+
*/
|
|
23
|
+
import { spawnSync } from "node:child_process";
|
|
24
|
+
import { closeSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { homedir, tmpdir } from "node:os";
|
|
26
|
+
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
27
|
+
|
|
28
|
+
// A stop: `code` names it, the message is a Korean line (the caller puts its name before it), `detail` the paths or
|
|
29
|
+
// lines it concerns.
|
|
30
|
+
export class Stop extends Error {
|
|
31
|
+
constructor(code, message, detail = []) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.code = code;
|
|
34
|
+
this.detail = detail;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const lstatOr = (p) => {
|
|
39
|
+
try {
|
|
40
|
+
return lstatSync(p);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
export const isFile = (p) => Boolean(lstatOr(p)?.isFile());
|
|
46
|
+
export const listDir = (dir) => {
|
|
47
|
+
try {
|
|
48
|
+
return readdirSync(dir);
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const homeOf = (ctx) => (ctx.platform === "win32" ? ctx.env.USERPROFILE : ctx.env.HOME) || homedir();
|
|
55
|
+
export const norm = (ctx, p) => (ctx.platform === "win32" ? p.toLowerCase() : p);
|
|
56
|
+
export const realOr = (p) => {
|
|
57
|
+
try {
|
|
58
|
+
return realpathSync.native(p);
|
|
59
|
+
} catch {
|
|
60
|
+
return p;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// A folder only this account may write in (POSIX: its own, no group or other write bit).
|
|
65
|
+
export function privateDir(p) {
|
|
66
|
+
try {
|
|
67
|
+
const st = statSync(p);
|
|
68
|
+
return st.isDirectory() && st.uid === process.getuid() && (st.mode & 0o022) === 0;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const insideHome = (ctx, p) => norm(ctx, realOr(p)).startsWith(norm(ctx, realOr(homeOf(ctx))) + sep);
|
|
74
|
+
|
|
75
|
+
// The base for this run's folders, and every base a run of any version may have used, for the day rule. Linux: an
|
|
76
|
+
// XDG_RUNTIME_DIR, macOS: a $TMPDIR (its own per account by default), of this account's own and not below a shared
|
|
77
|
+
// folder (/tmp) the home is not below, else ~/.cache; Windows: %TEMP% when it is inside the home, else ~/.cache (the
|
|
78
|
+
// old Git Bash runs used ~/.cache).
|
|
79
|
+
const OWN_TEMP = { linux: "XDG_RUNTIME_DIR", darwin: "TMPDIR" };
|
|
80
|
+
export function bases(ctx) {
|
|
81
|
+
const cache = join(homeOf(ctx), ".cache");
|
|
82
|
+
const base = baseOn(ctx, cache);
|
|
83
|
+
const own = ctx.env[OWN_TEMP[ctx.platform]];
|
|
84
|
+
return { base, known: [...new Set([base, cache, ...(own ? [resolve(own)] : [])])] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function baseOn(ctx, cache) {
|
|
88
|
+
const { env } = ctx;
|
|
89
|
+
if (ctx.platform === "win32") return env.TEMP && insideHome(ctx, env.TEMP) ? resolve(env.TEMP) : cache;
|
|
90
|
+
const own = env[OWN_TEMP[ctx.platform]];
|
|
91
|
+
return own && isAbsolute(own) && privateDir(own) && !belowSharedAlone(ctx, own) ? resolve(own) : cache;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const belowSharedAlone = (ctx, p) => Boolean(sharedAbove(p)) && !sharedAbove(homeOf(ctx));
|
|
95
|
+
|
|
96
|
+
// The nearest folder above p, by its real path, that every account may write in (/tmp, /var/tmp, /dev/shm), or null.
|
|
97
|
+
// A private folder there is still a folder whose parents another account fills: gemini reads the .env it finds
|
|
98
|
+
// walking up to the root. An XDG_RUNTIME_DIR or $TMPDIR below one is refused unless the home is below one too (a test
|
|
99
|
+
// box's account under /tmp): then ~/.cache is no better. POSIX only.
|
|
100
|
+
export function sharedAbove(p) {
|
|
101
|
+
for (let d = dirname(realOr(resolve(p))); ; d = dirname(d)) {
|
|
102
|
+
try {
|
|
103
|
+
if ((statSync(d).mode & 0o002) !== 0) return d;
|
|
104
|
+
} catch {
|
|
105
|
+
/* not there or not readable: the next one up */
|
|
106
|
+
}
|
|
107
|
+
if (dirname(d) === d) return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function readyBase(ctx, base) {
|
|
112
|
+
try {
|
|
113
|
+
mkdirSync(base, { recursive: true, mode: 0o700 });
|
|
114
|
+
} catch (e) {
|
|
115
|
+
throw new Stop("base-not-writable", `기준 폴더를 만들 수 없다(${e.code}): ${base}`);
|
|
116
|
+
}
|
|
117
|
+
if (ctx.platform !== "win32" && !privateDir(base)) throw new Stop("base-not-private", `기준 폴더를 다른 계정도 쓸 수 있다: ${base}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// One folder by mkdtemp under the base, with its marker.
|
|
121
|
+
export function makeRunFolder(ctx, kind) {
|
|
122
|
+
const { base } = bases(ctx);
|
|
123
|
+
readyBase(ctx, base);
|
|
124
|
+
let d;
|
|
125
|
+
try {
|
|
126
|
+
d = mkdtempSync(join(base, `${kind}.`));
|
|
127
|
+
writeFileSync(join(d, `.${kind}`), "", { mode: 0o600 });
|
|
128
|
+
} catch (e) {
|
|
129
|
+
if (d) rmSync(d, { recursive: true, force: true });
|
|
130
|
+
throw new Stop("base-not-writable", `기준 폴더에 쓸 수 없다(${e.code}): ${base}`);
|
|
131
|
+
}
|
|
132
|
+
return { dir: d, base };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The folder's parent, by its real path, is one of the known bases by theirs (macOS's /var is a link to /private/var:
|
|
136
|
+
// either form names the same base), and only this account may write in it (POSIX).
|
|
137
|
+
export function underBase(ctx, d) {
|
|
138
|
+
const parent = norm(ctx, realOr(dirname(d)));
|
|
139
|
+
const known = bases(ctx).known.some((b) => norm(ctx, realOr(b)) === parent);
|
|
140
|
+
return known && (ctx.platform === "win32" || privateDir(dirname(d)));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const escaped = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
144
|
+
|
|
145
|
+
// A folder made as makeRunFolder makes one, of one of `kinds`: named <kind>.<letters and digits>, a folder and no link,
|
|
146
|
+
// with its marker, right under a known base that only this account may write in.
|
|
147
|
+
export function runFolder(dir, ctx, kinds) {
|
|
148
|
+
const d = resolve(String(dir));
|
|
149
|
+
const kind = kinds.find((k) => new RegExp(`^${escaped(k)}\\.[A-Za-z0-9]+$`).test(basename(d)));
|
|
150
|
+
if (!kind || !lstatOr(d)?.isDirectory() || !isFile(join(d, `.${kind}`))) {
|
|
151
|
+
throw new Stop("not-a-run-folder", `init 이 만든 폴더가 아니다: ${d}`);
|
|
152
|
+
}
|
|
153
|
+
if (!underBase(ctx, d)) throw new Stop("not-a-run-folder", `알려진 기준 폴더 바로 아래의 실행 폴더가 아니다: ${d}`);
|
|
154
|
+
return { d, kind };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// A marked folder removed, its marker last: a removal that stops part way leaves a folder the next cleanup still knows.
|
|
158
|
+
// One that cannot be removed gets back its marker (if the removal reached it) and its times, so it stays a marked folder.
|
|
159
|
+
export function removeMarked(p, marker) {
|
|
160
|
+
const was = lstatOr(p);
|
|
161
|
+
try {
|
|
162
|
+
for (const name of listDir(p).filter((n) => n !== marker)) rmSync(join(p, name), { recursive: true, force: true, maxRetries: 3 });
|
|
163
|
+
rmSync(p, { recursive: true, force: true, maxRetries: 3 });
|
|
164
|
+
} catch (e) {
|
|
165
|
+
keepMarked(p, marker, was);
|
|
166
|
+
throw e;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function keepMarked(p, marker, was) {
|
|
171
|
+
try {
|
|
172
|
+
if (!lstatOr(p)?.isDirectory()) return;
|
|
173
|
+
if (!isFile(join(p, marker))) writeFileSync(join(p, marker), "", { flag: "wx", mode: 0o600 });
|
|
174
|
+
if (was) utimesSync(p, was.atime, was.mtime);
|
|
175
|
+
} catch {
|
|
176
|
+
/* the folder takes no marker or times either */
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// One run folder and nothing else: a path a record names outside it is the caller's to report, never to remove.
|
|
181
|
+
export function removeRunFolder(dir, ctx, kinds) {
|
|
182
|
+
const { d, kind } = runFolder(dir, ctx, kinds);
|
|
183
|
+
try {
|
|
184
|
+
removeMarked(d, `.${kind}`);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
throw new Stop("cleanup-left", "실행 폴더를 다 지우지 못했다. 사용자에게 경로를 알린다", [`${d} (${e.code || e.message})`]);
|
|
187
|
+
}
|
|
188
|
+
return { removed: d };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---- a program with files for its input and output
|
|
192
|
+
|
|
193
|
+
// spawnSync with the child's stdin, stdout and stderr on files in a private temporary folder, read back into
|
|
194
|
+
// r.stdout and r.stderr (Buffers) and removed. Node's own pipes are AF_UNIX socket pairs, and the Codex Linux sandbox
|
|
195
|
+
// refuses their setsockopt: a pipe child's output can be lost and spawnSync reports EPERM though the program ran.
|
|
196
|
+
// A file descriptor works there. Without a temporary folder it falls back to the pipes. With `digest` (a function
|
|
197
|
+
// of the stdout file's path, or of the Buffer on the pipe fallback) r.stdout is what it gives, and the output, which
|
|
198
|
+
// may pass 2 GiB, is never read whole.
|
|
199
|
+
export function spawnFiles(exe, args, { input, digest, ...opts } = {}) {
|
|
200
|
+
let t;
|
|
201
|
+
try {
|
|
202
|
+
t = mkdtempSync(join(tmpdir(), "rq-io-"));
|
|
203
|
+
} catch {
|
|
204
|
+
const r = spawnSync(exe, args, { ...opts, input });
|
|
205
|
+
return digest && r.stdout ? { ...r, stdout: digest(r.stdout) } : r;
|
|
206
|
+
}
|
|
207
|
+
const fds = [];
|
|
208
|
+
const open = (name, flags) => (fds.push(openSync(join(t, name), flags, 0o600)), fds[fds.length - 1]);
|
|
209
|
+
try {
|
|
210
|
+
if (input !== undefined) writeFileSync(join(t, "in"), input, { mode: 0o600 });
|
|
211
|
+
const stdio = [input === undefined ? "ignore" : open("in", "r"), open("out", "wx"), open("err", "wx")];
|
|
212
|
+
const r = spawnSync(exe, args, { ...opts, stdio });
|
|
213
|
+
return { ...r, stdout: (digest || readFileSync)(join(t, "out")), stderr: readFileSync(join(t, "err")) };
|
|
214
|
+
} finally {
|
|
215
|
+
for (const fd of fds) closeSync(fd);
|
|
216
|
+
rmSync(t, { recursive: true, force: true });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// A file's sha256 in pieces of 1 MiB: readFileSync stops at 2 GiB (ERR_FS_FILE_TOO_LARGE). `createHash` comes from
|
|
221
|
+
// the caller (this module loads node:crypto only inside snapshot).
|
|
222
|
+
export function hashFile(createHash, p) {
|
|
223
|
+
const h = createHash("sha256");
|
|
224
|
+
const fd = openSync(p, "r");
|
|
225
|
+
try {
|
|
226
|
+
const buf = Buffer.alloc(1 << 20);
|
|
227
|
+
for (let n = readSync(fd, buf, 0, buf.length, null); n > 0; n = readSync(fd, buf, 0, buf.length, null)) h.update(buf.subarray(0, n));
|
|
228
|
+
} finally {
|
|
229
|
+
closeSync(fd);
|
|
230
|
+
}
|
|
231
|
+
return h.digest("hex");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---- the work tree snapshot
|
|
235
|
+
|
|
236
|
+
// git as a program, from the home folder (Windows looks in the working folder first for a bare name; a home that is
|
|
237
|
+
// not there, as in a test's env, gives the temporary folder), with no fsmonitor program and no external diff or text
|
|
238
|
+
// conversion.
|
|
239
|
+
function git(repo, env, args, digest) {
|
|
240
|
+
const cwd = existsSync(homedir()) ? homedir() : tmpdir();
|
|
241
|
+
const r = spawnFiles("git", ["-c", "core.fsmonitor=false", "-C", repo, ...args], { cwd, env, maxBuffer: 1 << 30, windowsHide: true, digest });
|
|
242
|
+
return { ok: r.status === 0, out: r.stdout || Buffer.alloc(0), err: String(r.stderr || r.error?.code || "") };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// The untracked entries of `git status --porcelain=v1 -z` (a rename or copy entry carries its old name next).
|
|
246
|
+
function untrackedOf(raw) {
|
|
247
|
+
const parts = String(raw).split("\0");
|
|
248
|
+
const out = [];
|
|
249
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
250
|
+
const e = parts[i];
|
|
251
|
+
if (e.startsWith("?? ")) out.push(e.slice(3));
|
|
252
|
+
else if (/^[RC]/.test(e)) i += 1;
|
|
253
|
+
}
|
|
254
|
+
return out.sort();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// A gate's evidence needs a git work tree: in a folder without one the fingerprint sees no change, so no record of
|
|
258
|
+
// it could ever hold (the conjunct's `unverifiable`). Stopped before anything runs, with the absolute folder it checked
|
|
259
|
+
// (`checked`, also in detail) and what to do: a call from outside the repo without `flag` checked the wrong folder.
|
|
260
|
+
export function requireGitTree(repo, env = process.env, flag = "--cwd") {
|
|
261
|
+
const checked = resolve(repo);
|
|
262
|
+
const top = git(checked, env, ["rev-parse", "--show-toplevel"]);
|
|
263
|
+
if (top.ok) return String(top.out).trim();
|
|
264
|
+
const e = new Stop("not-a-git-tree", `git 저장소가 아니다(검사한 폴더: ${checked}). 이 폴더가 대상 저장소가 아니면 검사할 저장소를 ${flag} 로 주십시오. 대상 저장소가 맞으면 작업 트리 지문이 변경을 보지 못해 게이트 증거를 만들 수 없다: 사용자가 git init 과 첫 커밋을 하거나, 게이트 없이 돈다`, [checked]);
|
|
265
|
+
e.checked = checked;
|
|
266
|
+
throw e;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// An untracked entry's content hash: a link's target, a file's bytes (in pieces), or what it is when it is neither.
|
|
270
|
+
function contentHash(root, rel, hash, createHash) {
|
|
271
|
+
const p = join(root, rel);
|
|
272
|
+
const st = lstatOr(p);
|
|
273
|
+
if (st?.isSymbolicLink()) return hash(Buffer.from(`link\0${readlinkSync(p)}`));
|
|
274
|
+
if (st?.isFile()) return hashFile(createHash, p);
|
|
275
|
+
return hash(Buffer.from(st ? "other" : "gone"));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// What a gate's result was judged on: HEAD, the status, the diff against HEAD (the index and the work tree) and every
|
|
279
|
+
// untracked file's content, hashed together; the diffs and the files are hashed in pieces, never read whole. A tree
|
|
280
|
+
// with no git has one fingerprint per folder: changes go unseen (`git: false`, which the gate conjunct does not take).
|
|
281
|
+
// Files git ignores are outside it: a build or a test run makes them anew every time.
|
|
282
|
+
export async function snapshot(repo, env = process.env) {
|
|
283
|
+
const { createHash } = await import("node:crypto");
|
|
284
|
+
const hash = (b) => createHash("sha256").update(b).digest("hex");
|
|
285
|
+
const digest = (src) => (Buffer.isBuffer(src) ? hash(src) : hashFile(createHash, src));
|
|
286
|
+
const top = git(resolve(repo), env, ["rev-parse", "--show-toplevel"]);
|
|
287
|
+
if (!top.ok) return { git: false, head: null, untracked: [], fingerprint: hash(`no-git\0${resolve(repo)}`) };
|
|
288
|
+
const root = String(top.out).trim();
|
|
289
|
+
const head = git(root, env, ["rev-parse", "--verify", "-q", "HEAD"]);
|
|
290
|
+
const status = git(root, env, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
291
|
+
const diffArgs = ["--binary", "--no-ext-diff", "--no-textconv"];
|
|
292
|
+
const diffs = (head.ok ? [["diff", "HEAD"]] : [["diff", "--cached"], ["diff"]]).map((a) => git(root, env, [...a, ...diffArgs], digest));
|
|
293
|
+
const failed = [status, ...diffs].find((r) => !r.ok);
|
|
294
|
+
if (failed) throw new Stop("git-failed", `작업 트리를 읽지 못했다: ${failed.err.split("\n")[0]}`);
|
|
295
|
+
const untracked = untrackedOf(status.out).map((path) => ({ path, sha256: contentHash(root, path, hash, createHash) }));
|
|
296
|
+
const headId = head.ok ? String(head.out).trim() : null;
|
|
297
|
+
const parts = [`head\0${headId}`, `status\0${hash(status.out)}`, ...diffs.map((r) => `diff\0${r.out}`), ...untracked.map((u) => `new\0${u.path}\0${u.sha256}`)];
|
|
298
|
+
return { git: true, root, head: headId, untracked, fingerprint: hash(parts.join("\n")) };
|
|
299
|
+
}
|
|
@@ -19,14 +19,14 @@ The generator (`banker setup --codex`) applies the rules below for `target: both
|
|
|
19
19
|
- `--scope user` (default) → `~/.codex/…`. `--scope project` → `./.codex/…`.
|
|
20
20
|
|
|
21
21
|
## Runtime-aware surfaces (both) — Codex uses OMX / Codex equivalents
|
|
22
|
-
`claude-only` 표면은 `ultraqa` 하나다(Codex 는 OMX 의 `$ultraqa` 를 쓰므로 npm 설치와 Codex 플러그인 스킬 목록에서 빠진다). 그 밖의 Claude 플러그인 표면은 `both`, Codex 에만 있는 `codex-only` 는 `graceful-pause`, `progress`, `teamwork`. Codex 이름이나 설명이 다른 `both` 스킬은 manifest 의 `codexName`, `codexDescription` 으로 정한다(`setup-omc` 는 `setup-omx`, `omc-reference` 는 `omx-reference`, `setup-omc-hud` 는 `setup-omx-hud`, 설명만 다른 것은 `append_wiki`, `compact-wiki`, `setup-tmux`, 커맨드 `setup`). 플러그인 길은 `codex/renamed-skills/` 의 생성 사본, npm 길은 설치 때 바꿔 쓴다. OMC/Claude 에 결합됐던 표면은 본문이 런타임 인식으로 작성돼 Codex에서도 동작한다 — Codex에선 OMC 대신 oh-my-codex(OMX)의 동명 스킬을 쓴다:
|
|
23
|
-
- 오케스트레이터 `all-in-one`·`ultra-init`·커맨드 `front-qa` → OMX `ralplan
|
|
22
|
+
`claude-only` 표면은 `ultraqa` 하나다(Codex 는 OMX 의 `$ultraqa` 를 쓰므로 npm 설치와 Codex 플러그인 스킬 목록에서 빠진다). 그 밖의 Claude 플러그인 표면은 `both`, Codex 에만 있는 `codex-only` 는 `graceful-pause`, `progress`, `ralph`, `teamwork`. Codex 이름이나 설명이 다른 `both` 스킬은 manifest 의 `codexName`, `codexDescription` 으로 정한다(`setup-omc` 는 `setup-omx`, `omc-reference` 는 `omx-reference`, `setup-omc-hud` 는 `setup-omx-hud`, 설명만 다른 것은 `append_wiki`, `compact-wiki`, `setup-tmux`, 커맨드 `setup`). 플러그인 길은 `codex/renamed-skills/` 의 생성 사본, npm 길은 설치 때 바꿔 쓴다. OMC/Claude 에 결합됐던 표면은 본문이 런타임 인식으로 작성돼 Codex에서도 동작한다 — Codex에선 OMC 대신 oh-my-codex(OMX)의 동명 스킬을 쓴다:
|
|
23
|
+
- 오케스트레이터 `all-in-one`·`ultra-init`·커맨드 `front-qa` → 계획은 OMX `ralplan`(계획만 쓰고 멈추게 함. OMX 0.21 의 ralplan 은 끝에 `$ultragoal` 로 넘어감), 구현은 banker 의 Codex 전용 `ralph`(OMX 0.21 이 `$ralph` 를 없앰). 마지막 QA 단계는 두 런타임 모두 banker `ralph-qa` 하나다(`--qa` 목표를 `--gates` 로 넘김, OMX `ultraqa` 에 기대지 않음). `ultra-init` 은 0.14.0 부터 ultragoal 원장을 쓰지 않는다(OMC 5.6.1 checkpoint 가 `/goal` 스냅샷을 요구해 ralph 와 Stop 훅 루프가 겹친다). Claude 쪽은 `ralph` 가 부르는 `omc ralph verify` 때문에 전역 `omc` CLI 가 플러그인과 같은 5.x 여야 한다(사전 점검은 종료 코드가 아니라 출력 문구로).
|
|
24
24
|
- `setup-omc` → `omx setup`(본문에 이미 존재). `setup-omc-hud` → Codex 자신의 상태표시줄이면 `[tui] status_line` 을 OMX 프리셋으로 켜고 끔(`assets/codex-status-line.mjs`, 없을 때만 넣고 banker 가 넣은 줄만 뺌), 같은 머신의 Claude Code 상태표시줄이면 그대로 설치(대상을 말하지 않으면 먼저 묻는다). `setup-stitch` → `codex mcp add`. `setup-bypass-permissions` → 대상 `codex` 면 `config.toml` 최상위 `approval_policy = "never"`, `sandbox_mode = "danger-full-access"`(`scripts/codex-bypass.mjs`, 켜기 전 값 기록과 `off` 원복, 조직 정책 확인), 대상 `claude` 면 같은 머신의 Claude Code 설정.
|
|
25
25
|
- `compact-copy` → Codex 내장 `/copy`(경로는 Codex 규약). `omc-reference` → Codex는 OMX 카탈로그 기준. `setup` 커맨드 → Codex는 프롬프트 선택 UX.
|
|
26
26
|
`setup-insane-search` 도 `both`: Codex에선 `codex plugin add insane-research-codex@gptaku-codex`.
|
|
27
27
|
|
|
28
28
|
신규 스킬(0.4.0)도 `both`·런타임 인식:
|
|
29
|
-
- `visual-ralph` → OMX `$ralph
|
|
29
|
+
- `visual-ralph` → banker `$banker-ralph`(OMX 0.21 이 `$ralph` 를 없앰)·OMX `$imagegen`(+`omx imagegen continuation`)·Visual Verdict; Claude은 `ralph`+`visual-verdict`+Stitch/ccg imagegen.
|
|
30
30
|
- `deep-init` → 서브에이전트 Claude=OMC explore/architect/writer, Codex=OMX worker/explore(부재 시 직접 수행). 순수 fs+doc.
|
|
31
31
|
- `deep-research` → Claude 번들 워크플로/`WebSearch`, Codex OMX `autoresearch`/`best-practice-research`.
|
|
32
32
|
- `ralph-qa` → 백본은 세션 모델로 띄운 검토 에이전트 3개 이상이다(Claude=Agent 의 `Plan` 을 `model` 없이, Codex=`spawn_agent` 를 역할 없이, 저자 대화를 넘기지 않고). 두 런타임 모두 Bash 가 있어 읽기 전용은 지시로 선다. 외부 좌석은 `--codex`·`--gemini`·`--opencode` 를 줄 때만 앉고, 저자 계열은 앉지 않는다(Codex 런타임의 `--codex` 는 자기 자신이라 부적격). Codex 의 `workspace-write` 샌드박스는 HOME 쓰기를 막아 gemini, opencode 가 시작부터 실패하고, Linux 에서는 페이로드 기준 폴더(`$XDG_RUNTIME_DIR`, `~/.cache`)도 막는다. 그래서 외부 좌석 전송의 실행기 명령 전부(`seat-run.mjs` 의 init, untracked, prepare, check, send, cleanup, put)를 승인을 받아 샌드박스 밖에서 실행한다.
|
|
@@ -48,7 +48,7 @@ The generator (`banker setup --codex`) applies the rules below for `target: both
|
|
|
48
48
|
- `anti-drm` — the same places as `tone-compact` through the same shared module (`instruction-toggle.mjs`, a byte-identical copy in each skill): `<config>/rules/banker-anti-drm.md` for Claude Code, one `USER:OMX:POLICY`-wrapped block in the instructions file Codex reads. The settings (`~/.config/banker/anti-drm.json`) are shared by both runtimes; `on` and `off` act on one runtime, while `config` and `on` with settings refresh every runtime that holds the rules.
|
|
49
49
|
- `omc-patch` — acts on the Claude Code OMC plugin cache (`~/.claude/plugins/`) even when run from Codex; Codex's own OMX is untouched. The skill always passes `--no-update` (a plain apply would switch the active OMC to the tool's hard-coded `TARGET_VERSION`). `--revert --root` restores only the active plugin folder when an older kit left the timeout helper inside a block, and `scripts/probe-pm.mjs` runs OMC's project-memory hook in a temp scratch project with its own home and state folder. Under `workspace-write`, `--check` and `probe-pm.mjs` run as they are; apply and revert need approval to run outside the sandbox (not `--add-dir ~/.claude`).
|
|
50
50
|
- `remains` — the same procedure in both runtimes; box runs go through `scripts/boxes.mjs` (Codex: `~/.codex/skills/banker-remains/scripts/boxes.mjs`), which needs `ssh`, `scp` and `git` on the machine and key-only logins to the boxes in `~/.config/banker/test-boxes.json`. It reads and runs tests only: no fix, no commit, no push.
|
|
51
|
-
- `ultra-chain` — the same nine stages in both runtimes. Claude=OMC 5 (`ralplan --deliberate`, `ralph --critic=critic`); Codex=OMX
|
|
51
|
+
- `ultra-chain` — the same nine stages in both runtimes. Claude=OMC 5 (`ralplan --deliberate`, `ralph --critic=critic`); Codex=OMX `$ralplan --deliberate` (told to stop at the plan), then banker's Codex `ralph` (`$banker-ralph`, plugin `$banker:ralph`; OMX 0.21 removed `$ralph`), which has no `--critic`. After ralph both runtimes run the skill's own critic seat loop (`critic#1..N`; Claude `Agent` `Plan`, Codex `spawn_agent`; ralph's own approval is recorded as `ralph-reviewer` and is not the gate). `--agents=N` (default 3, raised to 3, over 17 refused, kept in `run.json` as `agents` and `agentsSource`) sets the stage 3 and 6 `ralph-qa` seats, the stage 5 critic seats and the stage 7 visual judges; the ledger enforces the count, the seat ids and the all-approve rule, and a seat that cannot be spawned stops the stage as `INCONCLUSIVE`. Both add a `ralph-qa` plan quorum of `--agents` reviewers after ralplan (whose Architect and Critic are two fixed reviewers with no option to add more and are not counted), run OS tests through the sibling `remains/scripts/boxes.mjs` (Codex: `banker-remains`), judge screenshots with `--agents` fresh judges whose lowest score must reach 90 (OMX 0.20.2's `visual-verdict` is a deprecated stub), and render the process report from the skill's own ledger (`lineage` reads Claude Code sessions only). Box runs need `ssh`, `scp` and `git`; screenshots need Playwright with Chromium; under Codex's `workspace-write` sandbox the ssh calls and the review seats' home writes may ask for approval.
|
|
52
52
|
- `theme-selection` — the same procedure in both runtimes; the engine is `scripts/themes.mjs` (Codex: `~/.codex/skills/banker-theme-selection/scripts/themes.mjs`), which reads `assets/palettes.json` beside it. Pure Node with no network; it writes only the file `--out` names (refused when that file itself is a symbolic link), and the skill asks before it overwrites one that exists.
|
|
53
53
|
- `progress-mon` — the same procedure in both runtimes; the engine is `scripts/progress-mon.mjs` (Codex npm install: `~/.codex/skills/banker-progress-mon/scripts/progress-mon.mjs`; plugin install: `~/.codex/plugins/cache/<marketplace>/banker/<version>/skills/progress-mon/scripts/progress-mon.mjs`), every command with `--runtime claude|codex`. The data folder is `.omc/progress-mon/` for Claude Code and `.omx/progress-mon/` for Codex. Pure Node with no network; the server binds 127.0.0.1 by default (`--host` takes a private IPv4 only). On Codex `start`, `stop`, `status` and `init` each ask for `sandbox_permissions: "require_escalated"` (`init` writes the git exclude list), and since closing the session may stop the server, a long-lived screen runs from the user's terminal (`serve`). `init --memory agents-md` writes its rule to `AGENTS.override.md` when that file has content of its own, as Codex then reads it instead of `AGENTS.md`. The fifth tab, ADR, reads `adr.json` only (the agent cites `docs/adr/` and `docs/evidence/` from it); a project set up before the ADR tab gets, on the next bare call and after one confirmation (Claude Code AskUserQuestion, Codex request_user_input), a rerun of `init` with the memory kind from `state.json` (auto-memory passes the folder of its `MEMORY.md` as `--memory-dir`) that replaces the old rule line in place with a `.progress-mon.bak` backup, then the ADR step. Not the same as `/progress` (the session's own task panel).
|
|
54
54
|
- `init-dir` — the same procedure in both runtimes; the engine is `scripts/init-dir.mjs` (Codex npm install: `~/.codex/skills/banker-init-dir/scripts/init-dir.mjs`; plugin install: `~/.codex/plugins/cache/<marketplace>/banker/<version>/skills/init-dir/scripts/init-dir.mjs`), run with `--runtime claude|codex`. It writes one marked block (`<!-- banker:init-dir:start -->` to `<!-- banker:init-dir:end -->`) into whichever of `AGENTS.md` and `CLAUDE.md` exist (Codex with neither: `AGENTS.md`), the `AGENTS.md` block inside its own `USER:OMX:POLICY` pair; `AGENTS.override.md` is never written, only warned about when it has content of its own. The block editor `scripts/marker-block.mjs` is the source of truth for its future copies (`scripts/sync-adapter.js`). Pure Node with no network. On Codex a `--root` outside the workspace, or starting a generated service that must outlive the session, asks for `sandbox_permissions: "require_escalated"`.
|
|
@@ -59,6 +59,7 @@ The generator (`banker setup --codex`) applies the rules below for `target: both
|
|
|
59
59
|
- `tune-tmux` — the same procedure in both runtimes; the engine is `scripts/tune-tmux.mjs` (Codex npm install: `~/.codex/skills/banker-tune-tmux/scripts/tune-tmux.mjs`; plugin install: `~/.codex/plugins/cache/<marketplace>/banker/<version>/skills/tune-tmux/scripts/tune-tmux.mjs`) with `status` (the default), `on` and `off`. It writes one marked block (`# banker:tune-tmux:start` to `# banker:tune-tmux:end`) through `scripts/marker-block.mjs`, a byte-identical copy of init-dir's kept by `scripts/sync-adapter.js`, into the user's tmux config (`~/.tmux.conf` or the XDG `tmux.conf`) or psmux config (`~/.psmux.conf` and the rest of psmux's order); psmux gets zero lines today, as `scripts/psmux-caps.json` verifies nothing beyond its defaults. The conditional `#{||:}` is probed on a private `tmux -L` server, never the user's. A symbolic-link config is refused unless `--follow-symlink`. No network. On Codex `on`, `off` and `--reload` ask for `sandbox_permissions: "require_escalated"`, as the config file and the tmux socket are outside the workspace; Claude Code and Codex settings are never changed, and Codex Fullscreen history cannot be scrolled by tmux copy-mode (only the user's `--no-alt-screen` or `tui.alternate_screen = "never"` changes that).
|
|
60
60
|
- `/graceful-pause` — in Claude Code a function-hooks command (`hooks/register.mjs`, 2.1.289+, not a manifest surface) registered `immediate`, so it runs mid-turn; it appends one hidden user row the running turn reads at its next model request. In Codex the `codex-only` skill `graceful-pause` (`$banker-graceful-pause`): a message sent with Enter while a turn runs is held as "Messages to be submitted after next tool call" and put into the same turn when the running tool call ends, so the session stops after the current step (Codex 0.144.5: verified with `sleep 40` in a real TUI).
|
|
61
61
|
- `/progress` — in Claude Code a mod panel (`hooks/progress.mjs`). In Codex the `codex-only` skill `progress` (`$banker-progress`, `codex/skills/progress/scripts/progress.mjs`): it reads this session's rollout, found by `CODEX_THREAD_ID` (the id at the end of the rollout file name), and prints the last plan file the session wrote or read (tool-call commands and `apply_patch` file heads; `.omx/plans/` counts like `.omc/plans/`) as a tree with the same engine (`progress-plan.mjs`, a byte-identical copy kept by `scripts/sync-adapter.js`), then the last `update_plan` steps (or the typed requests) with the recent tool calls under the step in progress. Not a live panel: `--pane` opens a tmux side pane that redraws every 2 s.
|
|
62
|
+
- `ralph` — `codex-only` (`$banker-ralph`, plugin `$banker:ralph`, `codex/skills/ralph/scripts/ralph-state.mjs`): the Codex build loop the five orchestrators call in place of OMX `$ralph`, which OMX 0.21 turned into a stub pointing at `$ultragoal`. Each iteration: build, slop pass recorded with `ralph-state.mjs deslop`, gates through `gate-evidence.mjs run` (a byte-identical copy of ralph-qa's, with `run-folder.mjs`, kept by `scripts/sync-adapter.js`), then a fresh reviewer (`spawn_agent` without a role or the author's context) recorded with `verdict`. `done` ends the run only when the gate conjunct, an APPROVE with no REJECT and the slop pass all match the work tree fingerprint now (`RALPH COMPLETE`); `stop` ends it otherwise (`RALPH STOPPED`). The record is `ralph.md` in the gate folder outside the repo, because an untracked file in the repo would change the fingerprint. It uses no OMX (no `omx state`, no OMX Stop hook) and needs no tmux. Like the OMC ralph in Claude Code it keeps going: banker's own Codex Stop hook (`hooks/ralph-continue-codex.mjs`, installed by `banker setup --codex` and by the plugin's `codex/hooks.json`, trusted once in `/hooks`) answers `{"decision":"block"}` while the session that started the run (`CODEX_THREAD_ID` at `start`; `adopt` moves it to another session) has it active; five stops with no change to the record or the gate evidence let the turn end, and `BANKER_NO_RALPH_CONTINUE` turns it off. Codex goal tools, when present, are completed only after `done`. Needs a git work tree; under `workspace-write` its calls may ask for approval.
|
|
62
63
|
- `teamwork` — `codex-only` (`$banker-teamwork`, plugin `$banker:teamwork`, `codex/skills/teamwork/scripts/teamwork.mjs`): peer work between the Codex sessions of one Codex home. `on` copies the engine to `$CODEX_HOME/teamwork/bin/` beside an sh wrapper, adds a `SessionStart` and a `UserPromptSubmit` hook last under each event (taking the same `hooks.json.lock` as `bin/lib/codex-hooks.mjs`, `timeout: 5`, fixed in place; the command ends with `--rev <hash of engine and wrapper>`, so a newer engine asks for trust once more) and writes `$CODEX_HOME/rules/teamwork.rules`, a `prefix_rule` that lets only the wrapper's `peers`, `role`, `send`, `reply`, `status` and `version` run outside the sandbox (`on`, `off`, `uninstall` ask for approval; a same-named file without the `# teamwork` first line is left alone). The wrapper and the hooks run outside the sandbox, so `node`, `codex`, `ps` and `sh` are recorded as absolute paths at `on` and nothing is looked up on PATH afterwards; programs, and a Codex home, under a temp folder, the session folder or its git top, or a `writable_roots` entry are refused (exit 6). Requests and results travel by `codex queue --thread <UUID>` (Codex 0.149.0 or later). Windows ends `on` with exit 6 and the reason: on Codex 0.160.1 for Windows, `codex execpolicy check` matches the rule only for the wrapper called directly, not for the PowerShell call form (`& '<wrapper>' send ...`), so every call would ask for approval. `banker uninstall --codex` removes it only when no other way of banker into that Codex home is left.
|
|
63
64
|
- Update notice and usage counting — Claude Code uses the plugin's own hooks (`SessionStart`, `PostToolUse` Skill, `UserPromptExpansion`). Codex gets `SessionStart` (`update-notify.mjs --codex`, shown as a warning line in the TUI) and `Stop` (`telemetry-count-codex.mjs`, once per turn rather than per tool call). Codex shows a skill in two ways and the Stop hook counts both from this turn of the rollout: a model-picked skill is read by a tool call (`cat .../skills/banker-<name>/SKILL.md`), and a skill the user names with `$banker-<name>` is injected without a tool call as a user-role message starting `<skill>\n<name>..</name>\n<path>..</path>`, of which only the `<path>` head is read. Codex runs trusted hooks outside the sandbox, so the update query and check-in reach the network as in Claude Code.
|
|
64
65
|
- Rollout shape — `lineage`, `progress` and the Stop hook read Codex's session record, whose shape Codex does not promise (0.144 and 0.160 already differ: event `user_message`/`agent_message` vs `item_completed` items, `exec_command` calls vs `exec` code). A shape banker cannot read is never an empty result: lineage stops with exit 2 naming the Codex version when a rollout gives no user turn, progress prints a warning line, and the Stop hook, finding no `task_started` for the turn in the whole record, notes the version in `codex-shape-miss.json` so the next SessionStart (`update-notify.mjs --codex`) shows one warning. `banker doctor` checks the newest rollout (turn start, user text, tool calls) after a Codex update.
|
package/commands/front-qa.md
CHANGED
|
@@ -16,7 +16,9 @@ argument-hint: "[스펙 파일 경로. 생략하면 docs/note.md]"
|
|
|
16
16
|
omc ralph verify --help 2>&1 | grep -q 'Usage: omc ralph verify' && echo "omc ok" || echo "omc CLI 없음 또는 낡음"
|
|
17
17
|
```
|
|
18
18
|
- 실패하면 `/banker:setup` → oh-my-claudecode(또는 `setup-omc` 스킬 / `npm install -g oh-my-claude-sisyphus@latest`)로 먼저 구성한 뒤 이어서 진행. 점검을 통과하기 전에는 절차 1을 시작하지 않는다.
|
|
19
|
-
- **런타임 매핑:** 아래 절차의 `/ralplan
|
|
19
|
+
- **런타임 매핑:** 아래 절차의 `/ralplan` 은 Claude Code에선 OMC 스킬, **Codex에선 oh-my-codex(OMX)의 동명 스킬**이다(Codex는 OMC 대신 OMX 전제). `/ralph` 는 Claude Code에선 OMC 스킬, **Codex에선 banker 의 Codex 전용 `ralph`** 다(npm 길 `$banker-ralph`, 플러그인 길 `$banker:ralph`. OMX 0.21 이 `$ralph` 를 없애 OMX 것은 부르지 않음). 각 런타임에서 사용 가능한 형태로 호출한다.
|
|
20
|
+
- **Codex 의 2단계:** OMX 0.21 의 `$ralplan` 은 끝에 스스로 `$ultragoal` 로 넘어간다. 계획만 쓰고 멈추라고 넘긴다(`$ultragoal`, `$ralph`, `$team`, 구현으로 넘어가지 않음). 계획은 `.omx/plans/` 에 둔다.
|
|
21
|
+
- **Codex 의 3~6단계:** banker `ralph` 의 `SKILL.md`(스킬로 설치된 때: npm 길 `<S>/../banker-ralph/SKILL.md`, 플러그인 길 `<S>/../../skills/ralph/SKILL.md`, `<S>` 는 이 커맨드 스킬 폴더)를 따른다. 스테이지와 스펙 항목을 수용 기준으로, typecheck 와 vitest 와 parity-gate 를 게이트(게이트마다 명령 하나)로 넘긴다. 그 ralph 가 새 맥락 검토, 슬롭 점검, 회귀 확인을 함께 하고 `RALPH COMPLETE` 줄을 낸다. critic 은 ralph 의 검토자가 맡는다.
|
|
20
22
|
|
|
21
23
|
## 절차 (CLAUDE.md: 계획 없는 즉시실행 금지)
|
|
22
24
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* banker ralph 계속 훅 (Codex Stop). Claude Code 의 OMC ralph 처럼, banker 의 Codex ralph 실행이 끝나기 전에 턴이
|
|
4
|
+
* 끝나려 하면 계속하라는 이유를 돌려준다(`{"decision":"block","reason":...}`, OMX 의 Stop 훅과 같은 출력).
|
|
5
|
+
*
|
|
6
|
+
* - 막는 때: 훅 입력의 session_id 가 그 실행을 시작한 세션(start 때의 CODEX_THREAD_ID)이고, 그 실행이 이 작업 폴더의
|
|
7
|
+
* 저장소에서 active 일 때만. 검토자 같은 하위 에이전트 세션과 다른 세션은 막지 않는다.
|
|
8
|
+
* - 기록이나 게이트 증거가 바뀌지 않은 채 다섯 번 멈추려 하면 놓아준다(같은 턴의 두 번째 훅은 세지 않음).
|
|
9
|
+
* 판정은 codex/skills/ralph/scripts/ralph-state.mjs 의 continueFor() 가 한다.
|
|
10
|
+
* - BANKER_NO_RALPH_CONTINUE 가 있으면 아무것도 하지 않는다.
|
|
11
|
+
* - 어떤 오류도 삼키고 항상 exit 0. 막지 않을 때 표준 출력은 비운다.
|
|
12
|
+
*/
|
|
13
|
+
import { realpathSync } from 'node:fs';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
import { continueFor } from '../codex/skills/ralph/scripts/ralph-state.mjs';
|
|
17
|
+
|
|
18
|
+
const MAX_INPUT = 1 << 20;
|
|
19
|
+
|
|
20
|
+
function readStdin() {
|
|
21
|
+
return new Promise((resolvePromise) => {
|
|
22
|
+
let data = '';
|
|
23
|
+
process.stdin.setEncoding('utf8');
|
|
24
|
+
process.stdin.on('data', (chunk) => {
|
|
25
|
+
data += chunk;
|
|
26
|
+
if (data.length > MAX_INPUT) process.stdin.destroy();
|
|
27
|
+
});
|
|
28
|
+
process.stdin.on('end', () => resolvePromise(data));
|
|
29
|
+
process.stdin.on('close', () => resolvePromise(data));
|
|
30
|
+
process.stdin.on('error', () => resolvePromise(''));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The reason to keep the turn going for this hook input, or null.
|
|
35
|
+
export function decide(input, env = process.env, platform = process.platform) {
|
|
36
|
+
if (env.BANKER_NO_RALPH_CONTINUE) return null;
|
|
37
|
+
if (!input || typeof input !== 'object') return null;
|
|
38
|
+
return continueFor({ cwd: input.cwd, sessionId: input.session_id, turnId: input.turn_id, transcript: input.transcript_path }, { env, platform });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function main() {
|
|
42
|
+
try {
|
|
43
|
+
const input = JSON.parse(await readStdin());
|
|
44
|
+
const reason = decide(input);
|
|
45
|
+
if (reason) process.stdout.write(`${JSON.stringify({ decision: 'block', reason })}\n`);
|
|
46
|
+
} catch {
|
|
47
|
+
/* never in the way: no output, exit 0 */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const invokedDirectly = () => {
|
|
52
|
+
try {
|
|
53
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (invokedDirectly()) main().then(() => process.exit(0));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaydash9999/banker-plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "banker — Claude Code + Codex CLI plugin (QA, audit, docs, architecture, wiki skills). Install via Claude Code marketplace, or `npm i -g @kaydash9999/banker-plugins` then `banker setup`.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"banker": "bin/banker.js"
|
|
@@ -14,8 +14,9 @@ level: 4
|
|
|
14
14
|
This skill orchestrates OMC's `ralplan` → `ralph`, then banker's own `ralph-qa`
|
|
15
15
|
as the QA gate. **Check the dependency before Stage 1**; if it is missing or
|
|
16
16
|
stale, **guide installation first** rather than failing mid-pipeline. The `omc`
|
|
17
|
-
check below is for Claude Code
|
|
18
|
-
|
|
17
|
+
check below is for Claude Code. On Codex check OMX for Stage 1 (`omx --version`);
|
|
18
|
+
Stage 2 there is banker's Codex `ralph` (npm `$banker-ralph`, plugin
|
|
19
|
+
`$banker:ralph`), which needs neither OMX nor the `omc` CLI.
|
|
19
20
|
|
|
20
21
|
- **The `omc` CLI must have `ralph verify` (Claude Code).** OMC 5's `ralph` runs
|
|
21
22
|
`omc ralph verify` on its first iteration and stops when the command is
|
|
@@ -35,13 +36,14 @@ check below is for Claude Code; on Codex check OMX instead (`omx --version`), wh
|
|
|
35
36
|
pipeline whose Stage 2 will stop.
|
|
36
37
|
- **Stage 3 is banker's `ralph-qa` on both runtimes.** It ships with banker, so
|
|
37
38
|
the gate needs no OMC or OMX skill; OMC and OMX serve Stages 1 and 2 only.
|
|
38
|
-
- On Codex the
|
|
39
|
+
- On Codex the planning framework is OMX (`omx setup`). OMX 0.21 removed
|
|
40
|
+
`$ralph` (a stub that points at `$ultragoal`), so Stage 2 never calls OMX's.
|
|
39
41
|
|
|
40
|
-
**런타임 매핑 (Claude Code ↔ Codex).** Stage 1
|
|
42
|
+
**런타임 매핑 (Claude Code ↔ Codex).** Stage 1 의 `ralplan` 은 Claude Code에선 OMC 스킬이고 **Codex에선 oh-my-codex(OMX)가 제공하는 동명 스킬**이다. Stage 2 의 `ralph` 는 Claude Code에선 OMC 스킬이고 **Codex에선 banker 의 Codex 전용 `ralph`** 다(npm 길 `$banker-ralph`, 플러그인 길 `$banker:ralph`). 그 `SKILL.md` 는 npm 길 `<S>/../banker-ralph/SKILL.md`, 플러그인 길 `<S>/../../codex/skills/ralph/SKILL.md` 다. OMX 의 `$ralph` 는 부르지 않는다(OMX 0.21 이 없앰). Stage 3 은 두 런타임 모두 banker `ralph-qa` 다: Claude Code `Skill("banker:ralph-qa")`, Codex npm 길 `$banker-ralph-qa`, 플러그인 길 `$banker:ralph-qa`. Codex 에는 Skill 도구가 없으니 그 `SKILL.md` 를 읽고 그대로 따른다(npm 길 `<S>/../banker-ralph-qa/SKILL.md`, 플러그인 길 `<S>/../ralph-qa/SKILL.md`, `<S>` 는 이 스킬 폴더). `<RQ>` 는 ralph-qa 의 `references` 폴더다(Claude Code 와 Codex 플러그인 길 `<S>/../ralph-qa/references`, Codex npm 길 `<S>/../banker-ralph-qa/references`). 아래 `Skill("oh-my-claudecode:<name>")` 표기는 Claude Code 기준이다.
|
|
41
43
|
|
|
42
|
-
**Codex 의 tmux 밖 실행.** OMX 의 런타임 워크플로(`
|
|
44
|
+
**Codex 의 tmux 밖 실행.** banker `ralph` 는 tmux 가 필요 없다. OMX 의 런타임 워크플로(`team` 등)는 OMX tmux 런타임을 요구한다. OMX 의 `AGENTS.md` 는 tmux 밖(Codex App, 일반 `codex`, `codex exec`)이면 그 사실을 알리고 가장 가까운 대체 경로로 계속하라고 정한다(oh-my-codex 0.20.2 `templates/AGENTS.md`). 그래서 tmux 밖이라는 사실만으로는 멈추지 않는다. 그 단계는 이 세션이 그 스킬의 `SKILL.md` 절차를 네이티브 하위 에이전트로 그대로 따르고, 보고에 `<단계>: tmux 밖 대체 실행(OMX 런타임 없음)` 한 줄을 적는다. 종결 낱말은 다른 멈춤 조건과 마지막 ralph-qa 판정으로만 정한다. 대체 실행도 할 수 없으면(하위 에이전트 기능이 꺼짐 등) `STOPPED(omx-runtime-unavailable)` 로 멈추고 tmux 안의 OMX 에서 다시 실행하라고 안내한다.
|
|
43
45
|
|
|
44
|
-
**Codex 의 계획과 상태 경로.** 이 문서의 `.omc/plans/`, `.omc/state/` 는 Claude Code(OMC) 경로다. Codex 에서는 OMX 경로를 쓴다: 계획은 `.omx/plans/`(ralplan 이 쓰는 `prd-*.md`, `test-spec-*.md`), ralph 의 진행 기록은
|
|
46
|
+
**Codex 의 계획과 상태 경로.** 이 문서의 `.omc/plans/`, `.omc/state/` 는 Claude Code(OMC) 경로다. Codex 에서는 OMX 경로를 쓴다: 계획은 `.omx/plans/`(ralplan 이 쓰는 `prd-*.md`, `test-spec-*.md`), ralph 의 진행 기록은 저장소 밖 게이트 폴더의 `ralph.md`(banker `ralph` 의 `ralph-state.mjs`). Codex 에서 `.omc/` 아래에 계획을 쓰지 않는다.
|
|
45
47
|
|
|
46
48
|
## Purpose
|
|
47
49
|
|
|
@@ -148,6 +150,9 @@ then **stops**.
|
|
|
148
150
|
- **If `--checkpoint`:** before proceeding, use `AskUserQuestion` to present the
|
|
149
151
|
plan and offer *Approve & implement / Request changes / Stop*. Only continue
|
|
150
152
|
on approval.
|
|
153
|
+
- **Codex: plan only.** OMX 0.21's `$ralplan` ends by moving on to
|
|
154
|
+
`$ultragoal`. Tell it to write the plan and stop there: no `$ultragoal`,
|
|
155
|
+
`$ralph`, `$team` or implementation. Stage 2 is banker's `ralph`.
|
|
151
156
|
- **If consensus is NOT reached** (ralplan hits its 5-iteration cap with no
|
|
152
157
|
`APPROVE`): do not auto-proceed. Surface the best plan and ask the user how to
|
|
153
158
|
proceed. Never build on an unapproved plan.
|
|
@@ -167,8 +172,8 @@ One command per gate (no `&&` or `;` chains) and a `--timeout` on each.
|
|
|
167
172
|
record** (iteration 0 in `<g>/evidence.md`) is the gate's baseline: ralph-qa
|
|
168
173
|
counts a failure with the same command and failure signature as not this
|
|
169
174
|
change's (`baseline`). It covers checks that are not among ralph's feedback
|
|
170
|
-
commands
|
|
171
|
-
|
|
175
|
+
commands. On Codex banker's `ralph` keeps a baseline of its own in its own
|
|
176
|
+
gate folder; this one is still the gate's. Never use `git stash` or `git reset` to get a pre-change state for it; if
|
|
172
177
|
the tree already has changes, take the record from a clean copy of HEAD
|
|
173
178
|
(`git worktree add --detach <tmp> HEAD`, run with `--cwd <tmp>`, then
|
|
174
179
|
`git worktree remove <tmp>`). A fresh worktree has no installed dependencies
|
|
@@ -247,6 +252,15 @@ criteria → Step-7 reviewer verification → Step-7.5 deslop → Step-7.6 regre
|
|
|
247
252
|
`omc ralph verify` command unavailable): stop and report. Do **not** run
|
|
248
253
|
Stage 3 on a broken or partial build and call it done.
|
|
249
254
|
|
|
255
|
+
**Codex: banker `ralph`.** Follow its `SKILL.md` (paths above) instead of the
|
|
256
|
+
OMC `ralph` above. Hand it the plan file path with its stories and acceptance
|
|
257
|
+
criteria, the `--qa` goals' commands as its gates (one command per gate id),
|
|
258
|
+
`--no-deslop` if passed, and "no questions: write assumptions down". `--critic`
|
|
259
|
+
has no Codex counterpart: ralph's fresh reviewer takes that place. It runs its
|
|
260
|
+
own gate folder, apart from `<g>`. Go on to Stage 3 only after its
|
|
261
|
+
`RALPH COMPLETE` line; on `RALPH STOPPED` stop and report its reason. The
|
|
262
|
+
`omc ralph verify` bullets above are Claude Code only.
|
|
263
|
+
|
|
250
264
|
### Stage 3 — QA gate: banker `ralph-qa` (both runtimes)
|
|
251
265
|
|
|
252
266
|
Precondition: Stage 2's ralph has completed and self-cancelled.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* gate-evidence: a gate command run by this program, not by the model, and what it saw, as the evidence ralph-qa
|
|
4
|
-
* (--gates), ultraqa, all-in-one, ultra-init and ultra-chain judge by. The source of truth is
|
|
5
|
-
* skills/ralph-qa/references/gate-evidence.mjs; skills/ultraqa/references
|
|
6
|
-
* (scripts/sync-adapter.js).
|
|
4
|
+
* (--gates), ultraqa, the Codex ralph, all-in-one, ultra-init and ultra-chain judge by. The source of truth is
|
|
5
|
+
* skills/ralph-qa/references/gate-evidence.mjs; skills/ultraqa/references and codex/skills/ralph/scripts hold
|
|
6
|
+
* byte-identical copies (scripts/sync-adapter.js).
|
|
7
7
|
*
|
|
8
8
|
* node gate-evidence.mjs init [--cwd <repo>] the gate folder <g> of one ralph-qa run (ralph-qa-gates.<letters and digits>): made
|
|
9
9
|
* once before the baseline, kept across the iterations, apart from the payload folder seat-run.mjs makes anew
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* run-folder: the private run folders of ralph-qa and
|
|
4
|
-
* against. The source of truth is skills/ralph-qa/references/run-folder.mjs;
|
|
5
|
-
* byte-identical
|
|
3
|
+
* run-folder: the private run folders of ralph-qa, ultraqa and the Codex ralph, and the work tree snapshot their gate
|
|
4
|
+
* evidence is judged against. The source of truth is skills/ralph-qa/references/run-folder.mjs;
|
|
5
|
+
* skills/ultraqa/references and codex/skills/ralph/scripts hold byte-identical copies (scripts/sync-adapter.js).
|
|
6
6
|
*
|
|
7
7
|
* bases(ctx) { base, known }: this run's base, and every base a run of any version used
|
|
8
8
|
* sharedAbove(p) the nearest folder above p every account may write in, or null (POSIX)
|
|
@@ -60,7 +60,7 @@ ultra-chain 이 직접 하는 일은 여섯 가지다.
|
|
|
60
60
|
| 1 전문가 작업 방식 | `Skill("banker:lfm")` | `$banker-lfm` |
|
|
61
61
|
| 2 모호성 제거 | `Skill("banker:ultra-interview")` | `$banker-ultra-interview` |
|
|
62
62
|
| 3 합의 계획 | `Skill("oh-my-claudecode:ralplan")` `--deliberate`, 이어서 `Skill("banker:ralph-qa")` | `$ralplan --deliberate`, 이어서 `$banker-ralph-qa`(플러그인 길 `$banker:ralph-qa`) |
|
|
63
|
-
| 5 구현 | `Skill("oh-my-claudecode:ralph")` `--critic=critic`, 이어서 이 스킬의 critic 좌석 `<agents>` 개 루프 | `$ralph
|
|
63
|
+
| 5 구현 | `Skill("oh-my-claudecode:ralph")` `--critic=critic`, 이어서 이 스킬의 critic 좌석 `<agents>` 개 루프 | banker `$banker-ralph`(플러그인 길 `$banker:ralph`), 이어서 이 스킬의 critic 좌석 `<agents>` 개 루프(banker `ralph` 에는 `--critic` 이 없다. OMX 0.21 이 `$ralph` 를 없앰) |
|
|
64
64
|
| 5 critic 좌석 에이전트 | `Agent` 의 `Plan` 유형, `model` 없이 | `spawn_agent`, 역할 규칙은 아래 |
|
|
65
65
|
| 6 합의 검증 | `Skill("banker:ralph-qa")` | `$banker-ralph-qa`(플러그인 길 `$banker:ralph-qa`) |
|
|
66
66
|
| 7 시각 판정 에이전트 | 판정자 `<agents>` 명. `Plan` 유형, `model` 없이 | 판정자 `<agents>` 명. `spawn_agent`, 역할과 `model` 없이 |
|
|
@@ -70,11 +70,11 @@ ultra-chain 이 직접 하는 일은 여섯 가지다.
|
|
|
70
70
|
| 이어 하기 메모 | OMC 작업 노트에 덧붙이기(`notepad_write_working`). 우선 노트는 통째로 바뀌므로 쓰지 않음 | `.omx/notepad.md` 에 한 줄 덧붙이기 |
|
|
71
71
|
| 실행 기록 기본 폴더 | `.omc/ultra-chain/` | `.omx/ultra-chain/` |
|
|
72
72
|
|
|
73
|
-
- Codex 에는 Skill 도구가 없다. 위 표의 `$이름` 은 그 스킬의 `SKILL.md` 를 읽고 그대로 따른다는 뜻이다. banker 스킬은 npm 설치면 `<S>/../banker-<이름>/SKILL.md`, Codex 플러그인 설치(`<S>` 가 `plugins/cache/` 아래, 스킬 이름 `banker:<이름>`)면 `<S>/../<이름>/SKILL.md` 다. 표의 `$banker-<이름>` 은 플러그인 설치에서 `$banker:<이름>` 이다. OMX 스킬은 `~/.codex/skills/<이름>/SKILL.md` 다. 그 스킬이 상태 기록을 요구하면 그 지시대로 직접 기록한다. 큰 `SKILL.md`(ralph-qa 는 약 71KB)는 셸 출력 한도에서 잘리니 나눠 읽는다.
|
|
73
|
+
- Codex 에는 Skill 도구가 없다. 위 표의 `$이름` 은 그 스킬의 `SKILL.md` 를 읽고 그대로 따른다는 뜻이다. banker 스킬은 npm 설치면 `<S>/../banker-<이름>/SKILL.md`, Codex 플러그인 설치(`<S>` 가 `plugins/cache/` 아래, 스킬 이름 `banker:<이름>`)면 `<S>/../<이름>/SKILL.md` 다. 표의 `$banker-<이름>` 은 플러그인 설치에서 `$banker:<이름>` 이다. Codex 전용인 banker `ralph` 만 플러그인 설치에서 `<S>/../../codex/skills/ralph/SKILL.md` 다(npm 설치는 `<S>/../banker-ralph/SKILL.md`). OMX 스킬은 `~/.codex/skills/<이름>/SKILL.md` 다. 그 스킬이 상태 기록을 요구하면 그 지시대로 직접 기록한다. 큰 `SKILL.md`(ralph-qa 는 약 71KB)는 셸 출력 한도에서 잘리니 나눠 읽는다.
|
|
74
74
|
- Codex 의 하위 에이전트(critic 좌석, 시각 판정자)는 저자 대화를 넘기지 않는다. multi-agent v2 는 `fork_turns="none"` 을 주고, v1 은 `fork_context` 를 주지 않는다. 판정을 받으면 `close_agent` 로 닫는다.
|
|
75
75
|
- Codex 의 동시 하위 에이전트 한도보다 `<agents>` 가 크면 나눠 띄우고, 모두 받은 뒤 합친다.
|
|
76
76
|
- Codex 환경 지시가 역할(`agent_type`)을 요구하면, 고정 모델이 세션 모델과 같은 역할을 쓰고 지시 앞머리에 파일 쓰기 금지를 적는다. 그런 역할이 없으면 역할 없이 띄운다. 쓴 역할과 모델, 강도를 판정 기록의 `note` 에 적는다.
|
|
77
|
-
- **Codex 의 tmux 밖 실행.** OMX 의 런타임 워크플로(`
|
|
77
|
+
- **Codex 의 tmux 밖 실행.** banker `ralph` 는 tmux 가 필요 없다. OMX 의 런타임 워크플로(`team` 등)는 OMX tmux 런타임을 요구한다. OMX 의 `AGENTS.md` 는 tmux 밖(Codex App, 일반 `codex`, `codex exec`)이면 그 사실을 알리고 가장 가까운 대체 경로로 계속하라고 정한다(oh-my-codex 0.20.2 `templates/AGENTS.md`). 그래서 tmux 밖이라는 사실만으로는 멈추지 않는다. 그 단계는 이 세션이 그 스킬의 `SKILL.md` 절차를 네이티브 하위 에이전트로 그대로 따르고, 보고에 `<단계>: tmux 밖 대체 실행(OMX 런타임 없음)` 한 줄을 적는다. 종결 낱말은 다른 멈춤 조건과 마지막 ralph-qa 판정으로만 정한다. 대체 실행도 할 수 없으면(하위 에이전트 기능이 꺼짐 등) `STOPPED(omx-runtime-unavailable)` 로 멈추고 tmux 안의 OMX 에서 다시 실행하라고 안내한다.
|
|
78
78
|
- Codex 의 오래 도는 명령은 기다리는 시간을 박스 `--timeout` 보다 길게 둔다. 기다리다 끊기면 박스 정리와 결과 JSON 이 남지 않는다.
|
|
79
79
|
- Codex 세션을 닫으면 그 세션에서 띄운 서버도 끝날 수 있다. 오래 볼 미리보기는 사용자 터미널에서 띄우게 안내한다(9단계).
|
|
80
80
|
|
|
@@ -241,6 +241,7 @@ ralplan 은 계획 작성자 외에 Architect 와 Critic 두 검토자로 합의
|
|
|
241
241
|
- 계획에 `## 전문가 작업 방식 적용` 절을 두라는 지시. `01-masters.md` 의 `M-n` 마다 판정(적용, 조정, 제외)과 계획의 어느 단계에 넣었는지, 조정과 제외는 그 이유를 적는다. 판정을 바꾸면(예: `lfm` 이 적용인데 계획은 제외) 이유를 적는다
|
|
242
242
|
- 계획 저장 경로: Claude Code 는 `.omc/plans/ultra-chain-<run id>.md`. Codex 는 OMX 규칙대로 `.omx/plans/` 에 쓴다
|
|
243
243
|
- Codex: 인터뷰가 끝났으니 `$deep-interview` 를 돌리지 말라는 말
|
|
244
|
+
- Codex: 계획만 쓰고 멈추라는 말. OMX 0.21 의 `$ralplan` 은 끝에 스스로 `$ultragoal` 로 넘어간다. `$ultragoal`, `$ralph`, `$team`, 구현으로 넘어가지 않게 한다
|
|
244
245
|
- 계획에 넣을 것
|
|
245
246
|
- 대상 OS 와 OS 별 시험 명령. 비대화형으로 돌게 한다(`CI=true`, 감시 모드 끔. Windows 는 `cmd` 기준으로 `set CI=true&&`)
|
|
246
247
|
- 시험 출력 꼴. node:test 면 TAP(`--test-reporter=tap`)로 내게 한다. 박스 결과가 시험 수를 셀 수 있다
|
|
@@ -336,7 +337,13 @@ ralplan 은 계획 작성자 외에 Architect 와 Critic 두 검토자로 합의
|
|
|
336
337
|
- `prd.json` 이 없으면 OMC 의 `PRD` 꼴(`src/hooks/ralph/prd.ts`)로 쓴다. 최상위 `project`(저장소 이름), `branchName`(지금 브랜치), `description`(작업 요약), `userStories` 는 빠지면 OMC 가 PRD 전체를 무효로 읽는다. 이야기마다 `id`, `title`, `description`, `acceptanceCriteria`(배열), `priority`(수), `passes: false` 를 넣는다. 위에서 정한 피드백 명령은 `feedbackCommands`, 품질 등급은 `repoQualityClass` 로 같은 파일에 넣는다. 빠지면 `omc ralph verify` 가 package.json 의 스크립트로 대신 돈다.
|
|
337
338
|
- OMC 의 내부 모듈(`dist/` 아래)을 불러 PRD 나 상태를 만들지 않는다. 판이 다른 내부 API 를 부르게 된다. 이 확인과 고친 것을 `build` 의 `note` 에 적는다.
|
|
338
339
|
3. ralph 가 끝까지 가게 둔다(검토자 승인, deslop, 회귀 확인, `/oh-my-claudecode:cancel`). ralph 의 cancel 은 구현 단계의 끝이지 연쇄의 끝이 아니다. ralph 가 아직 도는 동안 6단계를 시작하지 않는다. 끝나면 `.omc/state/sessions/<id>/prd.json`(`OMC_STATE_DIR` 이 있으면 그 아래)을 `<run>/05-prd.json` 으로 복사한다. ralph 의 반복 한도는 ralph 자신의 것이다. ralph 보고에서 실제로 승인한 검토자가 critic 인지 확인해 기록한다. Skill 경로로 켠 ralph 는 검토자 설정이 architect 로 남을 수 있다. ralph 의 승인은 `build` 에 `note` 하나로 `verdict.by: "ralph-reviewer"` 로 기록한다(좌석 수 검사 대상이 아니다). 이어서 5의 critic 좌석 루프를 돈다.
|
|
339
|
-
4. Codex: `$ralph`
|
|
340
|
+
4. Codex: banker `$banker-ralph`(플러그인 길 `$banker:ralph`)를 따른다. OMX 의 `$ralph` 는 부르지 않는다(OMX 0.21 이 없앰).
|
|
341
|
+
- 2의 입력 가운데 계획, 목업, 기대 출력, 수용 기준을 넘긴다. `repoQualityClass`, `prd.json`, `state_read` 는 Claude Code 의 OMC 것이라 넘기지 않는다
|
|
342
|
+
- 게이트: 계획의 로컬 게이트 목록(게이트마다 명령 하나와 시간 제한)을 넘긴다. 2의 피드백 명령 꼴(`grep -Ev` 로 거른 꼴)이 아니라 종료 코드로 판정하는 보통 명령을 쓴다
|
|
343
|
+
- 인터뷰가 끝났으니 `$deep-interview` 를 돌리지 말라고 적는다. 연쇄 전체의 목표(goal)를 완료로 바꾸지 말라고도 적는다. 질문하지 말고 가정을 적으라고 적는다
|
|
344
|
+
- ralph 가 `RALPH COMPLETE` 줄을 낸 뒤에만 다음으로 간다. `RALPH STOPPED` 면 그 까닭으로 `stopped` 를 기록하고 멈춘다
|
|
345
|
+
- ralph 의 게이트 폴더는 이 연쇄의 `<g>` 와 따로다. ralph 가 끝나며 지운다. ralph 의 `RALPH COMPLETE` 줄을 `build` 의 `note` 에 적는다
|
|
346
|
+
- ralph 의 승인은 3과 같이 `verdict.by: "ralph-reviewer"` 로 기록한다. 이어서 5의 critic 좌석 루프를 돈다
|
|
340
347
|
5. critic 좌석 루프(두 런타임 공통). ralph 의 critic 은 1명 고정이고 ralph 가 끝나면 다시 판정할 수 없어서, ralph 가 끝난 뒤 이 스킬의 좌석 `<agents>` 개로 다시 판정한다.
|
|
341
348
|
- 좌석 `critic#1`..`critic#<agents>` 를 런타임 대응 표의 5 critic 좌석 에이전트로 띄운다. Claude Code 는 `Agent` 의 `Plan` 유형을 `model` 없이(읽기 전용 도구, 세션 모델) 쓴다. Codex 는 `spawn_agent` 이고, 역할 `critic` 의 고정 모델이 세션 모델과 같으면 그 역할을 쓰고, 아니면 런타임 대응 절의 역할 규칙을 따른다. 지시 앞머리에 파일 쓰기 금지를 적는다.
|
|
342
349
|
- Agent 도구나 `Plan` 유형을 띄울 수 없으면(Codex 는 하위 에이전트 기능이 꺼짐) 한 맥락이 좌석을 연기하지 않는다. 그 회차를 `INCONCLUSIVE` 로 기록하고 멈춘다(ralph-qa 의 "띄울 수 없으면 흉내 내지 않는다" 와 같은 규칙).
|