@sabaiway/agent-workflow-kit 5.11.2 → 7.0.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/CHANGELOG.md +117 -0
- package/README.md +3 -2
- package/SKILL.md +5 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review-await-guard.test.mjs +176 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +61 -14
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +606 -467
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +42 -4
- package/bridges/codex-cli-bridge/SKILL.md +18 -5
- package/bridges/codex-cli-bridge/bin/codex-await-guard.test.mjs +161 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +22 -17
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +356 -363
- package/bridges/codex-cli-bridge/bin/codex-review.sh +6 -6
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +275 -286
- package/bridges/codex-cli-bridge/capability.json +1 -1
- package/bridges/codex-cli-bridge/references/driving-codex.md +4 -2
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +3 -2
- package/bridges/codex-cli-bridge/setup/README.md +3 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/hooks/gate-approve.mjs +1 -1
- package/references/modes/grounding.md +1 -1
- package/references/modes/mcp.md +37 -0
- package/references/modes/procedures.md +3 -3
- package/references/modes/recommendations.md +1 -0
- package/references/modes/uninstall.md +2 -1
- package/references/templates/agent_rules.md +4 -5
- package/tools/commands.mjs +7 -0
- package/tools/direct-run.mjs +3 -0
- package/tools/doc-parity.mjs +18 -2
- package/tools/grounding.mjs +10 -20
- package/tools/inject-methodology.mjs +2 -0
- package/tools/mcp-registration.mjs +283 -0
- package/tools/mcp-server.mjs +314 -0
- package/tools/mcp-stdio.mjs +229 -0
- package/tools/mcp.mjs +299 -0
- package/tools/procedures.mjs +7 -8
- package/tools/recommendations.mjs +90 -1
- package/tools/uninstall.mjs +356 -45
|
@@ -115,15 +115,19 @@ const makePathWithout = (root, exclude = []) => {
|
|
|
115
115
|
return dir;
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
// ASYNCHRONOUS on purpose: a blocking dispatch holds the event loop for its whole duration, which
|
|
119
|
+
// pins this file to one core. Awaiting the child lets a `{ concurrency }` describe overlap its
|
|
120
|
+
// tests; the per-test environment still rides the CHILD's options, never `process.env`.
|
|
121
|
+
const run = ({ repo, bin }, { args = ['code'], env = {}, path, cwd } = {}) => new Promise((settle) => {
|
|
119
122
|
const argvFile = join(repo, '.cap-argv');
|
|
120
123
|
const envFile = join(repo, '.cap-env');
|
|
121
124
|
const stdinFile = join(repo, '.cap-stdin');
|
|
122
125
|
const codexHome = join(repo, '..', 'codex-home');
|
|
123
|
-
const
|
|
126
|
+
const child = execFile('bash', [WRAPPER, ...args], {
|
|
124
127
|
cwd: cwd || repo,
|
|
125
128
|
encoding: 'utf8',
|
|
126
129
|
timeout: 30000,
|
|
130
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
127
131
|
env: {
|
|
128
132
|
PATH: path || `${bin}:${process.env.PATH}`,
|
|
129
133
|
HOME: repo,
|
|
@@ -137,80 +141,65 @@ const run = ({ repo, bin }, { args = ['code'], env = {}, path, cwd } = {}) => {
|
|
|
137
141
|
CODEX_FAKE_STDIN: stdinFile,
|
|
138
142
|
...env,
|
|
139
143
|
},
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// Async twin of run() for the sleep-bound timeout test: spawnSync blocks the event loop for
|
|
146
|
-
// the whole deliberate wait, so a concurrent describe could not overlap it. Same contract.
|
|
147
|
-
const runAsync = ({ repo, bin }, { args = ['code'], env = {}, path, cwd } = {}) =>
|
|
148
|
-
new Promise((done) => {
|
|
149
|
-
const argvFile = join(repo, '.cap-argv');
|
|
150
|
-
const envFile = join(repo, '.cap-env');
|
|
151
|
-
const stdinFile = join(repo, '.cap-stdin');
|
|
152
|
-
const codexHome = join(repo, '..', 'codex-home');
|
|
153
|
-
const child = execFile('bash', [WRAPPER, ...args], {
|
|
154
|
-
cwd: cwd || repo,
|
|
155
|
-
encoding: 'utf8',
|
|
156
|
-
timeout: 30000,
|
|
157
|
-
env: {
|
|
158
|
-
PATH: path || `${bin}:${process.env.PATH}`,
|
|
159
|
-
HOME: repo,
|
|
160
|
-
TMPDIR: process.env.TMPDIR ?? '/tmp',
|
|
161
|
-
CODEX_HOME: codexHome,
|
|
162
|
-
CODEX_FAKE_ARGV: argvFile,
|
|
163
|
-
CODEX_FAKE_ENV: envFile,
|
|
164
|
-
CODEX_FAKE_STDIN: stdinFile,
|
|
165
|
-
...env,
|
|
166
|
-
},
|
|
167
|
-
}, (error, stdout, stderr) => {
|
|
168
|
-
const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '');
|
|
169
|
-
done({ status: error ? (error.code ?? 1) : 0, stdout, stderr, codexHome, argv: readIf(argvFile), capEnv: readIf(envFile), capStdin: readIf(stdinFile) });
|
|
144
|
+
}, (error, stdout, stderr) => {
|
|
145
|
+
const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '');
|
|
146
|
+
settle({
|
|
147
|
+
status: error ? (error.code ?? 1) : 0, signal: error?.signal ?? null, stdout, stderr,
|
|
148
|
+
codexHome, argv: readIf(argvFile), capEnv: readIf(envFile), capStdin: readIf(stdinFile),
|
|
170
149
|
});
|
|
171
|
-
child.stdin.end();
|
|
172
150
|
});
|
|
151
|
+
// The wrapper refuses many inputs BEFORE it reads stdin, so the pipe can already be closed here.
|
|
152
|
+
// The blocking spawn swallowed that; an async one throws EPIPE at the test. A closed pipe is the
|
|
153
|
+
// refusal working — but ONLY EPIPE is: any other write failure is a real fault and must reach
|
|
154
|
+
// the test instead of passing as a green.
|
|
155
|
+
child.stdin.on('error', (err) => { if (err.code !== 'EPIPE') throw err; });
|
|
156
|
+
child.stdin.end();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// runAsync was the async twin kept for the sleep-bound timeout test, back when run() blocked.
|
|
160
|
+
// run() IS that twin now, so the twin is one name pointing at it — two spawn paths could only drift.
|
|
161
|
+
const runAsync = run;
|
|
173
162
|
|
|
174
|
-
describe('codex-review.sh — quality-first model/effort guard (1.1)', () => {
|
|
175
|
-
it('refuses a non-default CODEX_MODEL', () => {
|
|
163
|
+
describe('codex-review.sh — quality-first model/effort guard (1.1)', { concurrency: 2 }, () => {
|
|
164
|
+
it('refuses a non-default CODEX_MODEL', async () => {
|
|
176
165
|
const sb = makeSandbox();
|
|
177
|
-
const r = run(sb, { env: { CODEX_MODEL: 'gpt-5.4-mini' } });
|
|
166
|
+
const r = await run(sb, { env: { CODEX_MODEL: 'gpt-5.4-mini' } });
|
|
178
167
|
rmSync(sb.root, { recursive: true, force: true });
|
|
179
168
|
assert.notEqual(r.status, 0);
|
|
180
|
-
assert.match(r.stderr, /not the pinned
|
|
169
|
+
assert.match(r.stderr, /not the pinned model/);
|
|
181
170
|
assert.equal(r.capStdin, '', 'codex must not be invoked when the guard fires');
|
|
182
171
|
});
|
|
183
172
|
|
|
184
|
-
it('refuses a non-default CODEX_EFFORT', () => {
|
|
173
|
+
it('refuses a non-default CODEX_EFFORT', async () => {
|
|
185
174
|
const sb = makeSandbox();
|
|
186
|
-
const r = run(sb, { env: { CODEX_EFFORT: 'high' } });
|
|
175
|
+
const r = await run(sb, { env: { CODEX_EFFORT: 'high' } });
|
|
187
176
|
rmSync(sb.root, { recursive: true, force: true });
|
|
188
177
|
assert.notEqual(r.status, 0);
|
|
189
178
|
assert.match(r.stderr, /not the pinned max effort/);
|
|
190
179
|
});
|
|
191
180
|
|
|
192
|
-
it('CODEX_PROBE=1 relaxes the guard and warns', () => {
|
|
181
|
+
it('CODEX_PROBE=1 relaxes the guard and warns', async () => {
|
|
193
182
|
const sb = makeSandbox();
|
|
194
|
-
const r = run(sb, { env: { CODEX_PROBE: '1', CODEX_EFFORT: 'low' } });
|
|
183
|
+
const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_EFFORT: 'low' } });
|
|
195
184
|
rmSync(sb.root, { recursive: true, force: true });
|
|
196
185
|
assert.equal(r.status, 0, r.stderr);
|
|
197
186
|
assert.match(r.stderr, /THROWAWAY PROBE MODE/);
|
|
198
187
|
});
|
|
199
188
|
});
|
|
200
189
|
|
|
201
|
-
describe('codex-review.sh — clean output + session capture (1.2)', () => {
|
|
202
|
-
it('prints ONLY the final findings, not the JSON event stream', () => {
|
|
190
|
+
describe('codex-review.sh — clean output + session capture (1.2)', { concurrency: 2 }, () => {
|
|
191
|
+
it('prints ONLY the final findings, not the JSON event stream', async () => {
|
|
203
192
|
const sb = makeSandbox();
|
|
204
|
-
const r = run(sb);
|
|
193
|
+
const r = await run(sb);
|
|
205
194
|
rmSync(sb.root, { recursive: true, force: true });
|
|
206
195
|
assert.equal(r.status, 0, r.stderr);
|
|
207
196
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/);
|
|
208
197
|
assert.doesNotMatch(r.stdout, /thread\.started/);
|
|
209
198
|
});
|
|
210
199
|
|
|
211
|
-
it('passes the clean-capture flags and read-only sandbox to codex', () => {
|
|
200
|
+
it('passes the clean-capture flags and read-only sandbox to codex', async () => {
|
|
212
201
|
const sb = makeSandbox();
|
|
213
|
-
const r = run(sb);
|
|
202
|
+
const r = await run(sb);
|
|
214
203
|
rmSync(sb.root, { recursive: true, force: true });
|
|
215
204
|
for (const f of [/(^|\n)-o(\n|$)/, /(^|\n)--json(\n|$)/, /hide_agent_reasoning=true/,
|
|
216
205
|
/(^|\n)read-only(\n|$)/]) {
|
|
@@ -218,9 +207,9 @@ describe('codex-review.sh — clean output + session capture (1.2)', () => {
|
|
|
218
207
|
}
|
|
219
208
|
});
|
|
220
209
|
|
|
221
|
-
it('surfaces the session id on STDERR only — never the shared resume sidecar', () => {
|
|
210
|
+
it('surfaces the session id on STDERR only — never the shared resume sidecar', async () => {
|
|
222
211
|
const sb = makeSandbox();
|
|
223
|
-
const r = run(sb);
|
|
212
|
+
const r = await run(sb);
|
|
224
213
|
const sidecar = join(sb.repo, '.codex-last-session');
|
|
225
214
|
const wrote = existsSync(sidecar);
|
|
226
215
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -228,35 +217,35 @@ describe('codex-review.sh — clean output + session capture (1.2)', () => {
|
|
|
228
217
|
assert.equal(wrote, false, 'a review must NOT clobber codex-exec --resume-last target');
|
|
229
218
|
});
|
|
230
219
|
|
|
231
|
-
it('on a codex failure, prints the trace tail and exits codex code', () => {
|
|
220
|
+
it('on a codex failure, prints the trace tail and exits codex code', async () => {
|
|
232
221
|
const sb = makeSandbox();
|
|
233
|
-
const r = run(sb, { env: { CODEX_FAKE_EXIT: '5' } });
|
|
222
|
+
const r = await run(sb, { env: { CODEX_FAKE_EXIT: '5' } });
|
|
234
223
|
rmSync(sb.root, { recursive: true, force: true });
|
|
235
224
|
assert.equal(r.status, 5);
|
|
236
225
|
assert.match(r.stderr, /codex review failed \(exit 5\)/);
|
|
237
226
|
});
|
|
238
227
|
});
|
|
239
228
|
|
|
240
|
-
describe('codex-review.sh — leaner prompt + read-fence line (1.4 / 1.5)', () => {
|
|
241
|
-
it('code mode: obeys AGENTS.md from context, states the read fence, no read-AGENTS action', () => {
|
|
229
|
+
describe('codex-review.sh — leaner prompt + read-fence line (1.4 / 1.5)', { concurrency: 2 }, () => {
|
|
230
|
+
it('code mode: obeys AGENTS.md from context, states the read fence, no read-AGENTS action', async () => {
|
|
242
231
|
const sb = makeSandbox();
|
|
243
|
-
const r = run(sb, { args: ['code'] });
|
|
232
|
+
const r = await run(sb, { args: ['code'] });
|
|
244
233
|
rmSync(sb.root, { recursive: true, force: true });
|
|
245
234
|
assert.match(r.capStdin, /already merged into your context/);
|
|
246
235
|
assert.doesNotMatch(r.capStdin, /Also read the project's root AGENTS\.md/);
|
|
247
236
|
assert.match(r.capStdin, /Do not read files outside this git working tree/);
|
|
248
237
|
});
|
|
249
238
|
|
|
250
|
-
it('code mode: appends extra focus', () => {
|
|
239
|
+
it('code mode: appends extra focus', async () => {
|
|
251
240
|
const sb = makeSandbox();
|
|
252
|
-
const r = run(sb, { args: ['code', 'the new reducer'] });
|
|
241
|
+
const r = await run(sb, { args: ['code', 'the new reducer'] });
|
|
253
242
|
rmSync(sb.root, { recursive: true, force: true });
|
|
254
243
|
assert.match(r.capStdin, /Extra focus: the new reducer/);
|
|
255
244
|
});
|
|
256
245
|
|
|
257
|
-
it('plan mode: includes the plan body and a PLAN-specific read fence', () => {
|
|
246
|
+
it('plan mode: includes the plan body and a PLAN-specific read fence', async () => {
|
|
258
247
|
const sb = makeSandbox();
|
|
259
|
-
const r = run(sb, { args: ['plan', 'plan.md'] });
|
|
248
|
+
const r = await run(sb, { args: ['plan', 'plan.md'] });
|
|
260
249
|
rmSync(sb.root, { recursive: true, force: true });
|
|
261
250
|
assert.match(r.capStdin, /Do a thing in two steps/);
|
|
262
251
|
assert.match(r.capStdin, /the plan above plus the in-repo code/);
|
|
@@ -264,10 +253,10 @@ describe('codex-review.sh — leaner prompt + read-fence line (1.4 / 1.5)', () =
|
|
|
264
253
|
});
|
|
265
254
|
});
|
|
266
255
|
|
|
267
|
-
describe('codex-review.sh — best-effort env read-fence (1.5)', () => {
|
|
268
|
-
it('repoints HOME/XDG to a throwaway dir while keeping an absolute CODEX_HOME', () => {
|
|
256
|
+
describe('codex-review.sh — best-effort env read-fence (1.5)', { concurrency: 2 }, () => {
|
|
257
|
+
it('repoints HOME/XDG to a throwaway dir while keeping an absolute CODEX_HOME', async () => {
|
|
269
258
|
const sb = makeSandbox();
|
|
270
|
-
const r = run(sb);
|
|
259
|
+
const r = await run(sb);
|
|
271
260
|
const home = (r.capEnv.match(/^HOME=(.*)$/m) || [])[1];
|
|
272
261
|
const codexHome = (r.capEnv.match(/^CODEX_HOME=(.*)$/m) || [])[1];
|
|
273
262
|
const xdg = (r.capEnv.match(/^XDG_CONFIG_HOME=(.*)$/m) || [])[1];
|
|
@@ -277,9 +266,9 @@ describe('codex-review.sh — best-effort env read-fence (1.5)', () => {
|
|
|
277
266
|
assert.ok(xdg && xdg.startsWith(home), 'XDG_CONFIG_HOME must live under the fenced HOME');
|
|
278
267
|
});
|
|
279
268
|
|
|
280
|
-
it('resolves a literal ~/ in CODEX_HOME against HOME, not $PWD', () => {
|
|
269
|
+
it('resolves a literal ~/ in CODEX_HOME against HOME, not $PWD', async () => {
|
|
281
270
|
const sb = makeSandbox();
|
|
282
|
-
const r = run(sb, { env: { CODEX_HOME: '~/.codex' } });
|
|
271
|
+
const r = await run(sb, { env: { CODEX_HOME: '~/.codex' } });
|
|
283
272
|
const codexHome = (r.capEnv.match(/^CODEX_HOME=(.*)$/m) || [])[1];
|
|
284
273
|
rmSync(sb.root, { recursive: true, force: true });
|
|
285
274
|
// HOME handed to the wrapper is sb.repo → ~/.codex must expand to <repo>/.codex,
|
|
@@ -288,10 +277,10 @@ describe('codex-review.sh — best-effort env read-fence (1.5)', () => {
|
|
|
288
277
|
});
|
|
289
278
|
});
|
|
290
279
|
|
|
291
|
-
describe('codex-review.sh — subscription / config isolation (invariant)', () => {
|
|
292
|
-
it('clears every *_API_KEY + OPENAI_BASE_URL and passes --ignore-user-config', () => {
|
|
280
|
+
describe('codex-review.sh — subscription / config isolation (invariant)', { concurrency: 2 }, () => {
|
|
281
|
+
it('clears every *_API_KEY + OPENAI_BASE_URL and passes --ignore-user-config', async () => {
|
|
293
282
|
const sb = makeSandbox();
|
|
294
|
-
const r = run(sb, { env: {
|
|
283
|
+
const r = await run(sb, { env: {
|
|
295
284
|
OPENAI_API_KEY: 'sk-x', OPENAI_BASE_URL: 'http://evil.example', FOO_API_KEY: 'bar',
|
|
296
285
|
} });
|
|
297
286
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -303,7 +292,7 @@ describe('codex-review.sh — subscription / config isolation (invariant)', () =
|
|
|
303
292
|
});
|
|
304
293
|
});
|
|
305
294
|
|
|
306
|
-
describe('codex-review.sh — hard timeout (1.3)', { concurrency:
|
|
295
|
+
describe('codex-review.sh — hard timeout (1.3)', { concurrency: 2 }, () => {
|
|
307
296
|
it('kills a hung review at CODEX_HARD_TIMEOUT and reports it', async () => {
|
|
308
297
|
const sb = makeSandbox();
|
|
309
298
|
const started = Date.now();
|
|
@@ -317,10 +306,10 @@ describe('codex-review.sh — hard timeout (1.3)', { concurrency: true }, () =>
|
|
|
317
306
|
|
|
318
307
|
// Flow-orchestration Phase 4.2 (#26): the uncapped lane is CLOSED — without a capping binary the
|
|
319
308
|
// preflight refuses by name BEFORE any CLI run (the pre-fix wrapper warned and ran uncapped).
|
|
320
|
-
it('fails CLOSED when neither timeout nor gtimeout is on PATH — refuses by name, codex never runs', () => {
|
|
309
|
+
it('fails CLOSED when neither timeout nor gtimeout is on PATH — refuses by name, codex never runs', async () => {
|
|
321
310
|
const sb = makeSandbox();
|
|
322
311
|
const path = `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}`;
|
|
323
|
-
const r = run(sb, { path });
|
|
312
|
+
const r = await run(sb, { path });
|
|
324
313
|
rmSync(sb.root, { recursive: true, force: true });
|
|
325
314
|
assert.equal(r.status, 127, 'the hard-timeout preflight is a refusal, never a warned uncapped run');
|
|
326
315
|
assert.match(r.stderr, /hard-timeout preflight fails CLOSED/);
|
|
@@ -328,23 +317,23 @@ describe('codex-review.sh — hard timeout (1.3)', { concurrency: true }, () =>
|
|
|
328
317
|
});
|
|
329
318
|
});
|
|
330
319
|
|
|
331
|
-
describe('codex-review.sh — precomputed diff for code mode (2.1)', () => {
|
|
332
|
-
it('no-diff preflight: a clean tree exits 0 without spending a codex run', () => {
|
|
320
|
+
describe('codex-review.sh — precomputed diff for code mode (2.1)', { concurrency: 2 }, () => {
|
|
321
|
+
it('no-diff preflight: a clean tree exits 0 without spending a codex run', async () => {
|
|
333
322
|
const sb = makeSandbox({ clean: true });
|
|
334
|
-
const r = run(sb, { args: ['code'] });
|
|
323
|
+
const r = await run(sb, { args: ['code'] });
|
|
335
324
|
rmSync(sb.root, { recursive: true, force: true });
|
|
336
325
|
assert.equal(r.status, 0);
|
|
337
326
|
assert.match(r.stderr, /no uncommitted changes to review/);
|
|
338
327
|
assert.equal(r.capStdin, '', 'codex must NOT be invoked on a clean tree');
|
|
339
328
|
});
|
|
340
329
|
|
|
341
|
-
it('assembles repo map, status, staged + unstaged diffs; drops the run-git-yourself directive', () => {
|
|
330
|
+
it('assembles repo map, status, staged + unstaged diffs; drops the run-git-yourself directive', async () => {
|
|
342
331
|
const sb = makeSandbox();
|
|
343
332
|
const g = (...a) => spawnSync('git', a, { cwd: sb.repo, encoding: 'utf8' });
|
|
344
333
|
writeFileSync(join(sb.repo, 'AGENTS.md'), '# AGENTS\n\nHard Constraints: none.\nan unstaged edit\n');
|
|
345
334
|
writeFileSync(join(sb.repo, 'staged.mjs'), 'export const s = 1\n');
|
|
346
335
|
g('add', 'staged.mjs');
|
|
347
|
-
const r = run(sb, { args: ['code'] });
|
|
336
|
+
const r = await run(sb, { args: ['code'] });
|
|
348
337
|
rmSync(sb.root, { recursive: true, force: true });
|
|
349
338
|
assert.equal(r.status, 0, r.stderr);
|
|
350
339
|
for (const sec of [/repo file map/, /git status/, /staged diff/, /unstaged diff/, /staged\.mjs/]) {
|
|
@@ -353,50 +342,50 @@ describe('codex-review.sh — precomputed diff for code mode (2.1)', () => {
|
|
|
353
342
|
assert.doesNotMatch(r.capStdin, /Run `git status --short`/, 'the old self-discovery directive must be gone');
|
|
354
343
|
});
|
|
355
344
|
|
|
356
|
-
it('inlines untracked file CONTENTS, not just the path', () => {
|
|
345
|
+
it('inlines untracked file CONTENTS, not just the path', async () => {
|
|
357
346
|
const sb = makeSandbox();
|
|
358
347
|
writeFileSync(join(sb.repo, 'untra.txt'), 'UNIQUE_UNTRACKED_BODY\n');
|
|
359
|
-
const r = run(sb, { args: ['code'] });
|
|
348
|
+
const r = await run(sb, { args: ['code'] });
|
|
360
349
|
rmSync(sb.root, { recursive: true, force: true });
|
|
361
350
|
assert.match(r.capStdin, /untracked: untra\.txt/);
|
|
362
351
|
assert.match(r.capStdin, /UNIQUE_UNTRACKED_BODY/);
|
|
363
352
|
});
|
|
364
353
|
|
|
365
|
-
it('skips binary untracked files (noted; raw bytes not inlined)', () => {
|
|
354
|
+
it('skips binary untracked files (noted; raw bytes not inlined)', async () => {
|
|
366
355
|
const sb = makeSandbox();
|
|
367
356
|
writeFileSync(join(sb.repo, 'blob.bin'), Buffer.from([0x00, 0x01, 0x02, 0x00, 0x42]));
|
|
368
|
-
const r = run(sb, { args: ['code'] });
|
|
357
|
+
const r = await run(sb, { args: ['code'] });
|
|
369
358
|
rmSync(sb.root, { recursive: true, force: true });
|
|
370
359
|
assert.match(r.capStdin, /binary, skipped\): blob\.bin/);
|
|
371
360
|
});
|
|
372
361
|
|
|
373
|
-
it('handles untracked paths with spaces (NUL-safe)', () => {
|
|
362
|
+
it('handles untracked paths with spaces (NUL-safe)', async () => {
|
|
374
363
|
const sb = makeSandbox();
|
|
375
364
|
writeFileSync(join(sb.repo, 'a b c.txt'), 'SPACED_BODY\n');
|
|
376
|
-
const r = run(sb, { args: ['code'] });
|
|
365
|
+
const r = await run(sb, { args: ['code'] });
|
|
377
366
|
rmSync(sb.root, { recursive: true, force: true });
|
|
378
367
|
assert.match(r.capStdin, /untracked: a b c\.txt/);
|
|
379
368
|
assert.match(r.capStdin, /SPACED_BODY/);
|
|
380
369
|
});
|
|
381
370
|
|
|
382
|
-
it('does not follow untracked symlinks (no out-of-tree content leak)', () => {
|
|
371
|
+
it('does not follow untracked symlinks (no out-of-tree content leak)', async () => {
|
|
383
372
|
const sb = makeSandbox();
|
|
384
373
|
const secret = join(sb.root, 'outside-secret.txt'); // OUTSIDE the repo
|
|
385
374
|
writeFileSync(secret, 'TOP_SECRET_LEAK_MARKER\n');
|
|
386
375
|
symlinkSync(secret, join(sb.repo, 'link-to-outside')); // untracked symlink → outside
|
|
387
|
-
const r = run(sb, { args: ['code'] });
|
|
376
|
+
const r = await run(sb, { args: ['code'] });
|
|
388
377
|
rmSync(sb.root, { recursive: true, force: true });
|
|
389
378
|
assert.match(r.capStdin, /untracked \(symlink\): link-to-outside -> /);
|
|
390
379
|
assert.doesNotMatch(r.capStdin, /TOP_SECRET_LEAK_MARKER/, 'symlink target content must never leak');
|
|
391
380
|
});
|
|
392
381
|
|
|
393
|
-
it('oversized → git-dir temp file: 600 perms, untruncated, carve-out fence, cleaned up', () => {
|
|
382
|
+
it('oversized → git-dir temp file: 600 perms, untruncated, carve-out fence, cleaned up', async () => {
|
|
394
383
|
const sb = makeSandbox();
|
|
395
384
|
writeFileSync(join(sb.repo, 'unique.txt'), 'OVERSIZE_UNIQUE_MARKER\n');
|
|
396
385
|
writeFileSync(join(sb.repo, 'big.txt'), 'x'.repeat(5000));
|
|
397
386
|
const perms = join(sb.root, 'cap-perms');
|
|
398
387
|
const copy = join(sb.root, 'cap-diffcopy');
|
|
399
|
-
const r = run(sb, { args: ['code'], env: {
|
|
388
|
+
const r = await run(sb, { args: ['code'], env: {
|
|
400
389
|
CODEX_REVIEW_MAX_TOTAL_BYTES: '100', CODEX_FAKE_DIFF_PERMS: perms, CODEX_FAKE_DIFF_COPY: copy,
|
|
401
390
|
} });
|
|
402
391
|
const leftover = readdirSync(join(sb.repo, '.git')).filter((f) => f.startsWith('codex-review-diff.'));
|
|
@@ -415,29 +404,29 @@ describe('codex-review.sh — precomputed diff for code mode (2.1)', () => {
|
|
|
415
404
|
});
|
|
416
405
|
});
|
|
417
406
|
|
|
418
|
-
describe('codex-review.sh — optional structured findings (2.2)', () => {
|
|
419
|
-
it('CODEX_REVIEW_SCHEMA=1 passes --output-schema to codex', () => {
|
|
407
|
+
describe('codex-review.sh — optional structured findings (2.2)', { concurrency: 2 }, () => {
|
|
408
|
+
it('CODEX_REVIEW_SCHEMA=1 passes --output-schema to codex', async () => {
|
|
420
409
|
const sb = makeSandbox();
|
|
421
410
|
// Schema mode parses the schema's verdict FIELD (D4) — the fixture output must carry it.
|
|
422
|
-
const r = run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{"verdict":"ship","findings":[]}' } });
|
|
411
|
+
const r = await run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{"verdict":"ship","findings":[]}' } });
|
|
423
412
|
rmSync(sb.root, { recursive: true, force: true });
|
|
424
413
|
assert.equal(r.status, 0, r.stderr);
|
|
425
414
|
assert.match(r.argv, /(^|\n)--output-schema(\n|$)/);
|
|
426
415
|
});
|
|
427
416
|
|
|
428
|
-
it('is OFF by default — no --output-schema', () => {
|
|
417
|
+
it('is OFF by default — no --output-schema', async () => {
|
|
429
418
|
const sb = makeSandbox();
|
|
430
|
-
const r = run(sb, { args: ['code'] });
|
|
419
|
+
const r = await run(sb, { args: ['code'] });
|
|
431
420
|
rmSync(sb.root, { recursive: true, force: true });
|
|
432
421
|
assert.doesNotMatch(r.argv, /--output-schema/);
|
|
433
422
|
});
|
|
434
423
|
|
|
435
|
-
it('falls back to a raw-text run when the schema run fails (loud; exit 0) — and parses the TEXT verdict', () => {
|
|
424
|
+
it('falls back to a raw-text run when the schema run fails (loud; exit 0) — and parses the TEXT verdict', async () => {
|
|
436
425
|
const sb = makeSandbox();
|
|
437
426
|
// No CODEX_FAKE_FINAL: the fallback run emits the TEXT default (FAKE_FINAL_MESSAGE +
|
|
438
427
|
// Verdict: ship) — the wrapper must parse the mode of the run that actually SUCCEEDED,
|
|
439
428
|
// or every fallback would read verdict-less and die on the D4 arm.
|
|
440
|
-
const r = run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FAIL_ON_SCHEMA: '1' } });
|
|
429
|
+
const r = await run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FAIL_ON_SCHEMA: '1' } });
|
|
441
430
|
rmSync(sb.root, { recursive: true, force: true });
|
|
442
431
|
assert.equal(r.status, 0, r.stderr);
|
|
443
432
|
assert.match(r.stderr, /without the schema constraint/);
|
|
@@ -445,117 +434,117 @@ describe('codex-review.sh — optional structured findings (2.2)', () => {
|
|
|
445
434
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/);
|
|
446
435
|
});
|
|
447
436
|
|
|
448
|
-
it('schema ON makes the directive ask for schema JSON, not one-per-line text', () => {
|
|
437
|
+
it('schema ON makes the directive ask for schema JSON, not one-per-line text', async () => {
|
|
449
438
|
const sb = makeSandbox();
|
|
450
|
-
const r = run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1' } });
|
|
439
|
+
const r = await run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1' } });
|
|
451
440
|
rmSync(sb.root, { recursive: true, force: true });
|
|
452
441
|
assert.match(r.capStdin, /JSON object matching the provided output schema/);
|
|
453
442
|
assert.doesNotMatch(r.capStdin, /one per line/);
|
|
454
443
|
});
|
|
455
444
|
|
|
456
|
-
it('schema OFF (default) asks for one-finding-per-line text', () => {
|
|
445
|
+
it('schema OFF (default) asks for one-finding-per-line text', async () => {
|
|
457
446
|
const sb = makeSandbox();
|
|
458
|
-
const r = run(sb, { args: ['code'] });
|
|
447
|
+
const r = await run(sb, { args: ['code'] });
|
|
459
448
|
rmSync(sb.root, { recursive: true, force: true });
|
|
460
449
|
assert.match(r.capStdin, /one per line/);
|
|
461
450
|
});
|
|
462
451
|
});
|
|
463
452
|
|
|
464
|
-
describe('codex-review.sh — environment preflight (fail fast, before a run)', () => {
|
|
465
|
-
it('STOPs with 127 when codex is not on PATH', () => {
|
|
453
|
+
describe('codex-review.sh — environment preflight (fail fast, before a run)', { concurrency: 2 }, () => {
|
|
454
|
+
it('STOPs with 127 when codex is not on PATH', async () => {
|
|
466
455
|
const sb = makeSandbox();
|
|
467
456
|
const path = farmFor(['codex']); // no fake codex, no real codex
|
|
468
|
-
const r = run(sb, { args: ['code'], path });
|
|
457
|
+
const r = await run(sb, { args: ['code'], path });
|
|
469
458
|
rmSync(sb.root, { recursive: true, force: true });
|
|
470
459
|
assert.equal(r.status, 127);
|
|
471
460
|
assert.match(r.stderr, /'codex'.*not found on PATH/);
|
|
472
461
|
assert.equal(r.capStdin, '', 'codex must never be invoked');
|
|
473
462
|
});
|
|
474
463
|
|
|
475
|
-
it('STOPs (exit 1) when codex is not on a ChatGPT subscription', () => {
|
|
464
|
+
it('STOPs (exit 1) when codex is not on a ChatGPT subscription', async () => {
|
|
476
465
|
const sb = makeSandbox();
|
|
477
|
-
const r = run(sb, { args: ['code'], env: { CODEX_FAKE_LOGIN: 'Logged in using API key' } });
|
|
466
|
+
const r = await run(sb, { args: ['code'], env: { CODEX_FAKE_LOGIN: 'Logged in using API key' } });
|
|
478
467
|
rmSync(sb.root, { recursive: true, force: true });
|
|
479
468
|
assert.equal(r.status, 1);
|
|
480
469
|
assert.match(r.stderr, /not on a ChatGPT subscription/);
|
|
481
470
|
assert.equal(r.capStdin, '', 'a wrong login must never spend a run');
|
|
482
471
|
});
|
|
483
472
|
|
|
484
|
-
it('STOPs (exit 2) when not inside a git work tree', () => {
|
|
473
|
+
it('STOPs (exit 2) when not inside a git work tree', async () => {
|
|
485
474
|
const sb = makeSandbox();
|
|
486
475
|
const nongit = join(sb.root, 'nongit');
|
|
487
476
|
mkdirSync(nongit, { recursive: true });
|
|
488
477
|
writeFileSync(join(nongit, 'AGENTS.md'), '# AGENTS\n');
|
|
489
|
-
const r = run(sb, { args: ['code'], cwd: nongit });
|
|
478
|
+
const r = await run(sb, { args: ['code'], cwd: nongit });
|
|
490
479
|
rmSync(sb.root, { recursive: true, force: true });
|
|
491
480
|
assert.equal(r.status, 2);
|
|
492
481
|
assert.match(r.stderr, /must run inside a git working tree/);
|
|
493
482
|
});
|
|
494
483
|
|
|
495
|
-
it('STOPs (exit 2) when there is no root AGENTS.md', () => {
|
|
484
|
+
it('STOPs (exit 2) when there is no root AGENTS.md', async () => {
|
|
496
485
|
const sb = makeSandbox();
|
|
497
486
|
rmSync(join(sb.repo, 'AGENTS.md'));
|
|
498
|
-
const r = run(sb, { args: ['code'] });
|
|
487
|
+
const r = await run(sb, { args: ['code'] });
|
|
499
488
|
rmSync(sb.root, { recursive: true, force: true });
|
|
500
489
|
assert.equal(r.status, 2);
|
|
501
490
|
assert.match(r.stderr, /no root AGENTS\.md/);
|
|
502
491
|
});
|
|
503
492
|
});
|
|
504
493
|
|
|
505
|
-
describe('codex-review.sh — CODEX_HOME resolution arms (1.5)', () => {
|
|
506
|
-
it('resolves a bare ~ in CODEX_HOME to HOME', () => {
|
|
494
|
+
describe('codex-review.sh — CODEX_HOME resolution arms (1.5)', { concurrency: 2 }, () => {
|
|
495
|
+
it('resolves a bare ~ in CODEX_HOME to HOME', async () => {
|
|
507
496
|
const sb = makeSandbox();
|
|
508
|
-
const r = run(sb, { args: ['code'], env: { CODEX_HOME: '~' } });
|
|
497
|
+
const r = await run(sb, { args: ['code'], env: { CODEX_HOME: '~' } });
|
|
509
498
|
const codexHome = (r.capEnv.match(/^CODEX_HOME=(.*)$/m) || [])[1];
|
|
510
499
|
rmSync(sb.root, { recursive: true, force: true });
|
|
511
500
|
assert.equal(codexHome, sb.repo, 'bare ~ → the HOME handed to the wrapper');
|
|
512
501
|
});
|
|
513
502
|
|
|
514
|
-
it('anchors a relative CODEX_HOME to $PWD', () => {
|
|
503
|
+
it('anchors a relative CODEX_HOME to $PWD', async () => {
|
|
515
504
|
const sb = makeSandbox();
|
|
516
|
-
const r = run(sb, { args: ['code'], env: { CODEX_HOME: 'rel/.codex' } });
|
|
505
|
+
const r = await run(sb, { args: ['code'], env: { CODEX_HOME: 'rel/.codex' } });
|
|
517
506
|
const codexHome = (r.capEnv.match(/^CODEX_HOME=(.*)$/m) || [])[1];
|
|
518
507
|
rmSync(sb.root, { recursive: true, force: true });
|
|
519
508
|
assert.equal(codexHome, join(sb.repo, 'rel/.codex'), 'a relative path anchors to cwd, never left bare');
|
|
520
509
|
});
|
|
521
510
|
});
|
|
522
511
|
|
|
523
|
-
describe('codex-review.sh — mode dispatch & plan validation', () => {
|
|
524
|
-
it('unknown mode prints usage and STOPs (exit 2)', () => {
|
|
512
|
+
describe('codex-review.sh — mode dispatch & plan validation', { concurrency: 2 }, () => {
|
|
513
|
+
it('unknown mode prints usage and STOPs (exit 2)', async () => {
|
|
525
514
|
const sb = makeSandbox();
|
|
526
|
-
const r = run(sb, { args: ['bogus'] });
|
|
515
|
+
const r = await run(sb, { args: ['bogus'] });
|
|
527
516
|
rmSync(sb.root, { recursive: true, force: true });
|
|
528
517
|
assert.equal(r.status, 2);
|
|
529
518
|
assert.match(r.stderr, /usage: .* plan <plan-file> \[--nonce <n>\] \| code \[--nonce <n>\]/);
|
|
530
519
|
});
|
|
531
520
|
|
|
532
|
-
it('no mode prints usage and STOPs (exit 2)', () => {
|
|
521
|
+
it('no mode prints usage and STOPs (exit 2)', async () => {
|
|
533
522
|
const sb = makeSandbox();
|
|
534
|
-
const r = run(sb, { args: [] });
|
|
523
|
+
const r = await run(sb, { args: [] });
|
|
535
524
|
rmSync(sb.root, { recursive: true, force: true });
|
|
536
525
|
assert.equal(r.status, 2);
|
|
537
526
|
assert.match(r.stderr, /usage:/);
|
|
538
527
|
});
|
|
539
528
|
|
|
540
|
-
it('plan mode: STOPs (exit 2) when the plan file is missing', () => {
|
|
529
|
+
it('plan mode: STOPs (exit 2) when the plan file is missing', async () => {
|
|
541
530
|
const sb = makeSandbox();
|
|
542
|
-
const r = run(sb, { args: ['plan', 'nope.md'] });
|
|
531
|
+
const r = await run(sb, { args: ['plan', 'nope.md'] });
|
|
543
532
|
rmSync(sb.root, { recursive: true, force: true });
|
|
544
533
|
assert.equal(r.status, 2);
|
|
545
534
|
assert.match(r.stderr, /plan file 'nope\.md' not found/);
|
|
546
535
|
});
|
|
547
536
|
|
|
548
|
-
it('plan mode: STOPs (exit 2) on unexpected trailing arguments', () => {
|
|
537
|
+
it('plan mode: STOPs (exit 2) on unexpected trailing arguments', async () => {
|
|
549
538
|
const sb = makeSandbox();
|
|
550
|
-
const r = run(sb, { args: ['plan', 'plan.md', 'extra', 'junk'] });
|
|
539
|
+
const r = await run(sb, { args: ['plan', 'plan.md', 'extra', 'junk'] });
|
|
551
540
|
rmSync(sb.root, { recursive: true, force: true });
|
|
552
541
|
assert.equal(r.status, 2);
|
|
553
542
|
assert.match(r.stderr, /unexpected arguments after plan file: extra junk/);
|
|
554
543
|
});
|
|
555
544
|
});
|
|
556
545
|
|
|
557
|
-
describe('codex-review.sh — assemble & output edge cases', () => {
|
|
558
|
-
it('skips a non-regular untracked path (an embedded git repo dir) without reading it', () => {
|
|
546
|
+
describe('codex-review.sh — assemble & output edge cases', { concurrency: 2 }, () => {
|
|
547
|
+
it('skips a non-regular untracked path (an embedded git repo dir) without reading it', async () => {
|
|
559
548
|
// git enumerates an untracked path as non-regular only as a DIRECTORY: a FIFO /
|
|
560
549
|
// socket / device is not listed by `git ls-files --others` at all, but an embedded
|
|
561
550
|
// git repo surfaces as `nested/` — a directory, so `[[ ! -f ]]` skips it (and a
|
|
@@ -566,35 +555,35 @@ describe('codex-review.sh — assemble & output edge cases', () => {
|
|
|
566
555
|
const g = (...a) => spawnSync('git', a, { cwd: nested, encoding: 'utf8' });
|
|
567
556
|
g('init', '-q');
|
|
568
557
|
writeFileSync(join(nested, 'inner.txt'), 'INNER_SHOULD_NOT_BE_INLINED\n');
|
|
569
|
-
const r = run(sb, { args: ['code'] });
|
|
558
|
+
const r = await run(sb, { args: ['code'] });
|
|
570
559
|
rmSync(sb.root, { recursive: true, force: true });
|
|
571
560
|
assert.equal(r.status, 0, r.stderr);
|
|
572
561
|
assert.match(r.capStdin, /non-regular, skipped\): nested\//);
|
|
573
562
|
assert.doesNotMatch(r.capStdin, /INNER_SHOULD_NOT_BE_INLINED/, 'a non-regular path must not be inlined');
|
|
574
563
|
});
|
|
575
564
|
|
|
576
|
-
it('appends extra focus on the oversized (temp-file) path too', () => {
|
|
565
|
+
it('appends extra focus on the oversized (temp-file) path too', async () => {
|
|
577
566
|
const sb = makeSandbox();
|
|
578
567
|
writeFileSync(join(sb.repo, 'big.txt'), 'x'.repeat(5000));
|
|
579
|
-
const r = run(sb, { args: ['code', 'watch the parser'], env: { CODEX_REVIEW_MAX_TOTAL_BYTES: '100' } });
|
|
568
|
+
const r = await run(sb, { args: ['code', 'watch the parser'], env: { CODEX_REVIEW_MAX_TOTAL_BYTES: '100' } });
|
|
580
569
|
rmSync(sb.root, { recursive: true, force: true });
|
|
581
570
|
assert.equal(r.status, 0, r.stderr);
|
|
582
571
|
assert.match(r.capStdin, /with ONE exception/, 'this is the oversized temp-file path');
|
|
583
572
|
assert.match(r.capStdin, /Extra focus: watch the parser/);
|
|
584
573
|
});
|
|
585
574
|
|
|
586
|
-
it('warns and prints the trace tail when codex writes no final-message file (then fails the D4 arm)', () => {
|
|
575
|
+
it('warns and prints the trace tail when codex writes no final-message file (then fails the D4 arm)', async () => {
|
|
587
576
|
const sb = makeSandbox();
|
|
588
|
-
const r = run(sb, { args: ['code'], env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
577
|
+
const r = await run(sb, { args: ['code'], env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
589
578
|
rmSync(sb.root, { recursive: true, force: true });
|
|
590
579
|
assert.notEqual(r.status, 0, 'no final message ⇒ no verdict ⇒ a FAILED review (D4)');
|
|
591
580
|
assert.match(r.stderr, /no final-message file/);
|
|
592
581
|
assert.match(r.stdout, /turn\.completed/, 'the trace tail still carries the event stream');
|
|
593
582
|
});
|
|
594
583
|
|
|
595
|
-
it('prints no session line when codex emits no thread id', () => {
|
|
584
|
+
it('prints no session line when codex emits no thread id', async () => {
|
|
596
585
|
const sb = makeSandbox();
|
|
597
|
-
const r = run(sb, { args: ['code'], env: { CODEX_FAKE_NO_THREAD: '1' } });
|
|
586
|
+
const r = await run(sb, { args: ['code'], env: { CODEX_FAKE_NO_THREAD: '1' } });
|
|
598
587
|
rmSync(sb.root, { recursive: true, force: true });
|
|
599
588
|
assert.equal(r.status, 0, r.stderr);
|
|
600
589
|
assert.doesNotMatch(r.stderr, /session:/);
|
|
@@ -700,8 +689,8 @@ const consultsEnv = (source, name) =>
|
|
|
700
689
|
// `<facts-file>` behind a stray character) — the catalog declares the whole token a user types.
|
|
701
690
|
const SLOT_RE = /@?<[^<>]+>|\[[^[\]]*\]/g;
|
|
702
691
|
|
|
703
|
-
describe('codex-review.sh — --help contract (manifest-pinned)', () => {
|
|
704
|
-
it('--help and -h exit 0 pre-preflight (no codex, no git, no AGENTS.md)', () => {
|
|
692
|
+
describe('codex-review.sh — --help contract (manifest-pinned)', { concurrency: 2 }, () => {
|
|
693
|
+
it('--help and -h exit 0 pre-preflight (no codex, no git, no AGENTS.md)', async () => {
|
|
705
694
|
for (const arg of ['--help', '-h']) {
|
|
706
695
|
const r = runHelp(arg);
|
|
707
696
|
assert.equal(r.status, 0, `${arg}: ${r.stderr}`);
|
|
@@ -710,57 +699,57 @@ describe('codex-review.sh — --help contract (manifest-pinned)', () => {
|
|
|
710
699
|
}
|
|
711
700
|
});
|
|
712
701
|
|
|
713
|
-
it('Usage set-EQUALS the manifest invocation descriptors (both directions)', () => {
|
|
702
|
+
it('Usage set-EQUALS the manifest invocation descriptors (both directions)', async () => {
|
|
714
703
|
const help = runHelp('--help').stdout;
|
|
715
704
|
const got = helpSection(help, 'Usage:').filter((l) => l.startsWith('codex-review')).map(norm);
|
|
716
705
|
assert.ok(REVIEW_CONTRACT.invocations.length > 0, 'manifest invocations must be non-empty');
|
|
717
706
|
setEq(got, REVIEW_CONTRACT.invocations.map(norm), 'help Usage ⟷ manifest invocations');
|
|
718
707
|
});
|
|
719
708
|
|
|
720
|
-
it('Grounding renders the manifest grounding note verbatim', () => {
|
|
709
|
+
it('Grounding renders the manifest grounding note verbatim', async () => {
|
|
721
710
|
const help = runHelp('--help').stdout;
|
|
722
711
|
assert.equal(norm(helpSection(help, 'Grounding:').join(' ')), norm(REVIEW_CONTRACT.grounding));
|
|
723
712
|
});
|
|
724
713
|
|
|
725
|
-
it('Round-2 / resume set-EQUALS the manifest continue descriptors (empty — one-shot)', () => {
|
|
714
|
+
it('Round-2 / resume set-EQUALS the manifest continue descriptors (empty — one-shot)', async () => {
|
|
726
715
|
const help = runHelp('--help').stdout;
|
|
727
716
|
const got = helpSection(help, 'Round-2 / resume:').filter((l) => l.startsWith('codex-review')).map(norm);
|
|
728
717
|
setEq(got, (REVIEW_CONTRACT.continue ?? []).map(norm), 'help continue ⟷ manifest continue');
|
|
729
718
|
assert.deepEqual(REVIEW_CONTRACT.continue, [], 'codex-review is one-shot — no continue descriptor');
|
|
730
719
|
});
|
|
731
720
|
|
|
732
|
-
it('Receipt renders the manifest receipt contract verbatim (AD-038 three-way lockstep)', () => {
|
|
721
|
+
it('Receipt renders the manifest receipt contract verbatim (AD-038 three-way lockstep)', async () => {
|
|
733
722
|
const help = runHelp('--help').stdout;
|
|
734
723
|
assert.equal(norm(helpSection(help, 'Receipt:').join(' ')), norm(REVIEW_CONTRACT.receipt));
|
|
735
724
|
assert.match(REVIEW_CONTRACT.receipt, /sha256 over the canonical uncommitted-state payload/, 'the fingerprint definition lives in the manifest contract');
|
|
736
725
|
});
|
|
737
726
|
|
|
738
|
-
it('Notes renders the manifest review contract.notes verbatim (AD-061 — a typed contract key that MUST surface)', () => {
|
|
727
|
+
it('Notes renders the manifest review contract.notes verbatim (AD-061 — a typed contract key that MUST surface)', async () => {
|
|
739
728
|
const help = runHelp('--help').stdout;
|
|
740
729
|
assert.ok((REVIEW_CONTRACT.notes ?? []).length >= 2, 'the review contract declares the banner-only-timeout + quote-verbatim notes');
|
|
741
730
|
assert.equal(norm(helpSection(help, 'Notes:').join(' ')), norm(REVIEW_CONTRACT.notes.join(' ')));
|
|
742
731
|
});
|
|
743
732
|
});
|
|
744
733
|
|
|
745
|
-
describe('codex-review.sh — source-level reverse guard (parser arms ⟷ manifest)', () => {
|
|
734
|
+
describe('codex-review.sh — source-level reverse guard (parser arms ⟷ manifest)', { concurrency: 2 }, () => {
|
|
746
735
|
const arms = extractArgCaseArms(readFileSync(WRAPPER, 'utf8'));
|
|
747
736
|
|
|
748
|
-
it('the real mode arms equal the manifest modes (adding a mode without the manifest fails here)', () => {
|
|
737
|
+
it('the real mode arms equal the manifest modes (adding a mode without the manifest fails here)', async () => {
|
|
749
738
|
const modes = splitArms(arms.get('"$mode"')).filter((a) => a !== '*');
|
|
750
739
|
assert.ok(MANIFEST.roles.review.modes.length > 0, 'manifest modes must be non-empty');
|
|
751
740
|
setEq(new Set(modes), MANIFEST.roles.review.modes, 'parser mode arms ⟷ manifest modes');
|
|
752
741
|
});
|
|
753
742
|
|
|
754
|
-
it('the first-arg entrypoints are exactly --help/-h (no undeclared resume/flag entrypoint)', () => {
|
|
743
|
+
it('the first-arg entrypoints are exactly --help/-h (no undeclared resume/flag entrypoint)', async () => {
|
|
755
744
|
setEq(new Set(splitArms(arms.get('"${1:-}"'))), ['--help', '-h']);
|
|
756
745
|
});
|
|
757
746
|
|
|
758
|
-
it('every manifest mode is really accepted (forward guard)', () => {
|
|
747
|
+
it('every manifest mode is really accepted (forward guard)', async () => {
|
|
759
748
|
const drive = { plan: ['plan', 'plan.md'], code: ['code'] };
|
|
760
749
|
for (const mode of MANIFEST.roles.review.modes) {
|
|
761
750
|
assert.ok(drive[mode], `no test drive for manifest mode "${mode}" — add one`);
|
|
762
751
|
const sb = makeSandbox();
|
|
763
|
-
const r = run(sb, { args: drive[mode] });
|
|
752
|
+
const r = await run(sb, { args: drive[mode] });
|
|
764
753
|
rmSync(sb.root, { recursive: true, force: true });
|
|
765
754
|
assert.equal(r.status, 0, `mode ${mode}: ${r.stderr}`);
|
|
766
755
|
}
|
|
@@ -773,10 +762,10 @@ describe('codex-review.sh — source-level reverse guard (parser arms ⟷ manife
|
|
|
773
762
|
// a git-dir temp file — so its budget is set at the default inline cap: the map can never outgrow
|
|
774
763
|
// the whole payload, and every realistic repo's map assembles byte-UNCHANGED. Behaviour alone
|
|
775
764
|
// cannot tell "a huge budget" from "no budget at all", so the pinned value is asserted at source.
|
|
776
|
-
describe('codex-review.sh — repo file map budget (Phase 2)', () => {
|
|
765
|
+
describe('codex-review.sh — repo file map budget (Phase 2)', { concurrency: 2 }, () => {
|
|
777
766
|
const MAP_DIR = 'deeply/nested/fixture/directory/for/the/repo/file/map/budget';
|
|
778
767
|
|
|
779
|
-
it("codex-review's assembled payload is byte-unchanged by the map budget", () => {
|
|
768
|
+
it("codex-review's assembled payload is byte-unchanged by the map budget", async () => {
|
|
780
769
|
const sb = makeSandbox();
|
|
781
770
|
const g = (...a) => spawnSync('git', a, { cwd: sb.repo, encoding: 'utf8' });
|
|
782
771
|
mkdirSync(join(sb.repo, MAP_DIR), { recursive: true });
|
|
@@ -787,7 +776,7 @@ describe('codex-review.sh — repo file map budget (Phase 2)', () => {
|
|
|
787
776
|
g('add', '-A');
|
|
788
777
|
g('commit', '-qm', 'map fixture');
|
|
789
778
|
for (let i = 0; i < 100; i += 1) writeFileSync(join(sb.repo, `${MAP_DIR}/zz-modified-file-${String(i).padStart(3, '0')}.txt`), `body ${i} v2 — changed\n`);
|
|
790
|
-
const r = run(sb, { args: ['code'] });
|
|
779
|
+
const r = await run(sb, { args: ['code'] });
|
|
791
780
|
rmSync(sb.root, { recursive: true, force: true });
|
|
792
781
|
assert.equal(r.status, 0, r.stderr);
|
|
793
782
|
assert.doesNotMatch(r.capStdin, /TRUNCATED/, 'a 17 KB map is far inside the budget — no degradation');
|
|
@@ -795,7 +784,7 @@ describe('codex-review.sh — repo file map budget (Phase 2)', () => {
|
|
|
795
784
|
assert.ok(r.capStdin.includes(`${MAP_DIR}/aa-untouched-file-099.txt`), 'including its last entry');
|
|
796
785
|
});
|
|
797
786
|
|
|
798
|
-
it('the wrapper pins a map budget no smaller than its default inline-payload cap', () => {
|
|
787
|
+
it('the wrapper pins a map budget no smaller than its default inline-payload cap', async () => {
|
|
799
788
|
const source = readFileSync(WRAPPER, 'utf8');
|
|
800
789
|
const pinned = source.match(/^AW_REVIEW_MAP_BUDGET_BYTES=(\d+)$/m);
|
|
801
790
|
assert.ok(pinned, 'codex-review.sh must PIN AW_REVIEW_MAP_BUDGET_BYTES (a plain assignment, never an env knob)');
|
|
@@ -809,20 +798,20 @@ describe('codex-review.sh — repo file map budget (Phase 2)', () => {
|
|
|
809
798
|
// The kit validator owns the catalog's INTERNAL shape; these arms pin what only the wrapper source
|
|
810
799
|
// can settle — the catalog documents THIS wrapper's real modes and real escape hatches, and every
|
|
811
800
|
// contract invocation the wrapper honours is cataloged (adding a mode without one fails here).
|
|
812
|
-
describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)', () => {
|
|
801
|
+
describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)', { concurrency: 2 }, () => {
|
|
813
802
|
const source = readFileSync(WRAPPER, 'utf8');
|
|
814
803
|
const arms = extractArgCaseArms(source);
|
|
815
804
|
const catalog = MANIFEST.modeCatalog ?? [];
|
|
816
805
|
const reviewEntries = catalog.filter((e) => e.role === 'review');
|
|
817
806
|
const reviewPrimaries = reviewEntries.filter((e) => e.kind === 'primary');
|
|
818
807
|
|
|
819
|
-
it('the catalog submodes ARE the wrapper\'s real parser mode arms (both directions)', () => {
|
|
808
|
+
it('the catalog submodes ARE the wrapper\'s real parser mode arms (both directions)', async () => {
|
|
820
809
|
const modes = splitArms(arms.get('"$mode"')).filter((a) => a !== '*');
|
|
821
810
|
assert.ok(reviewPrimaries.length > 0, 'the manifest must catalog its review modes');
|
|
822
811
|
setEq(reviewPrimaries.map((e) => e.submode), modes, 'catalog submodes ⟷ real parser mode arms');
|
|
823
812
|
});
|
|
824
813
|
|
|
825
|
-
it('every review entry composes BY REFERENCE and every reference resolves', () => {
|
|
814
|
+
it('every review entry composes BY REFERENCE and every reference resolves', async () => {
|
|
826
815
|
for (const entry of reviewEntries) {
|
|
827
816
|
assert.ok(
|
|
828
817
|
Array.isArray(entry.invocationRefs) && entry.invocationRefs.length > 0,
|
|
@@ -838,7 +827,7 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
|
|
|
838
827
|
}
|
|
839
828
|
});
|
|
840
829
|
|
|
841
|
-
it('every review contract invocation is claimed by exactly ONE catalog entry (no uncataloged mode)', () => {
|
|
830
|
+
it('every review contract invocation is claimed by exactly ONE catalog entry (no uncataloged mode)', async () => {
|
|
842
831
|
const claims = reviewEntries.flatMap((e) => e.invocationRefs.map((r) => `${r.contractField}[${r.index}]`));
|
|
843
832
|
assert.equal(new Set(claims).size, claims.length, 'a contract invocation is claimed at most once');
|
|
844
833
|
const declared = [
|
|
@@ -848,7 +837,7 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
|
|
|
848
837
|
setEq(new Set(claims), declared, 'catalog claims ⟷ declared contract invocations');
|
|
849
838
|
});
|
|
850
839
|
|
|
851
|
-
it('every env-hook the catalog aims at a review mode is a real EXECUTABLE guard, not a mention', () => {
|
|
840
|
+
it('every env-hook the catalog aims at a review mode is a real EXECUTABLE guard, not a mention', async () => {
|
|
852
841
|
const hooks = catalog.filter((e) => e.kind === 'env-hook' && e.parents.some((p) => reviewPrimaries.some((r) => r.key === p)));
|
|
853
842
|
assert.ok(hooks.length > 0, 'CODEX_PROBE must be cataloged as an env-hook over the review modes');
|
|
854
843
|
for (const hook of hooks) {
|
|
@@ -859,7 +848,7 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
|
|
|
859
848
|
}
|
|
860
849
|
});
|
|
861
850
|
|
|
862
|
-
it('the catalog operand slots set-EQUAL the slots its rendered forms really carry (both directions)', () => {
|
|
851
|
+
it('the catalog operand slots set-EQUAL the slots its rendered forms really carry (both directions)', async () => {
|
|
863
852
|
for (const entry of reviewEntries) {
|
|
864
853
|
const forms = entry.invocationRefs.map((r) => REVIEW_CONTRACT[r.contractField][r.index]);
|
|
865
854
|
// The DEDUPLICATED UNION over every resolved form: a plural-ref entry legitimately spreads its
|
|
@@ -869,7 +858,7 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
|
|
|
869
858
|
}
|
|
870
859
|
});
|
|
871
860
|
|
|
872
|
-
it('an entry rendering a LITERAL descriptor is slot-checked too (env-hooks have no role to filter on)', () => {
|
|
861
|
+
it('an entry rendering a LITERAL descriptor is slot-checked too (env-hooks have no role to filter on)', async () => {
|
|
873
862
|
// The contract-backed arm above filters by role — and an env-hook HAS no role, so its descriptor
|
|
874
863
|
// would never be slot-checked. That is exactly how a hardcoded dead path can reach the discovery
|
|
875
864
|
// surface looking ready-to-run. Every literal-descriptor kind is covered here: env-hooks and
|
|
@@ -882,7 +871,7 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
|
|
|
882
871
|
}
|
|
883
872
|
});
|
|
884
873
|
|
|
885
|
-
it('CODEX_PROBE really relaxes the guard on EVERY review parent the catalog claims (behavioural)', () => {
|
|
874
|
+
it('CODEX_PROBE really relaxes the guard on EVERY review parent the catalog claims (behavioural)', async () => {
|
|
886
875
|
// The catalog CLAIMS these modes are modified by the hook; prove it per parent rather than
|
|
887
876
|
// trusting a source scan: an off-pin model is exit 2 normally, exit 0 under probe.
|
|
888
877
|
const hook = catalog.find((e) => e.key === 'CODEX_PROBE');
|
|
@@ -894,13 +883,13 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
|
|
|
894
883
|
// The exit code alone is weak evidence: pin the real DISPATCH too (capStdin is non-empty only
|
|
895
884
|
// when codex was actually invoked), so a run that dies early can never pass the probe-on branch.
|
|
896
885
|
const guarded = makeSandbox();
|
|
897
|
-
const off = run(guarded, { args: drive[parent], env: { CODEX_MODEL: 'not-the-pinned-model' } });
|
|
886
|
+
const off = await run(guarded, { args: drive[parent], env: { CODEX_MODEL: 'not-the-pinned-model' } });
|
|
898
887
|
rmSync(guarded.root, { recursive: true, force: true });
|
|
899
888
|
assert.equal(off.status, 2, `${parent}: the quality guard must refuse an off-pin model without the hook`);
|
|
900
889
|
assert.equal(off.capStdin, '', `${parent}: the guard must refuse BEFORE spending a run`);
|
|
901
890
|
|
|
902
891
|
const probed = makeSandbox();
|
|
903
|
-
const on = run(probed, { args: drive[parent], env: { CODEX_MODEL: 'not-the-pinned-model', CODEX_PROBE: '1' } });
|
|
892
|
+
const on = await run(probed, { args: drive[parent], env: { CODEX_MODEL: 'not-the-pinned-model', CODEX_PROBE: '1' } });
|
|
904
893
|
rmSync(probed.root, { recursive: true, force: true });
|
|
905
894
|
assert.equal(on.status, 0, `${parent}: CODEX_PROBE=1 must really relax the guard — the catalog claims it does`);
|
|
906
895
|
assert.notEqual(on.capStdin, '', `${parent}: CODEX_PROBE=1 must really reach codex, not merely exit 0`);
|
|
@@ -922,10 +911,10 @@ const readReceipts = (repo) => {
|
|
|
922
911
|
};
|
|
923
912
|
const sha256Hex = (buf) => createHash('sha256').update(buf).digest('hex');
|
|
924
913
|
|
|
925
|
-
describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
926
|
-
it('a successful code review appends ONE fixture-shaped receipt (text-mode verdict parse)', () => {
|
|
914
|
+
describe('codex-review.sh — review receipts (AD-038)', { concurrency: 2 }, () => {
|
|
915
|
+
it('a successful code review appends ONE fixture-shaped receipt (text-mode verdict parse)', async () => {
|
|
927
916
|
const sb = makeSandbox();
|
|
928
|
-
const r = run(sb, { env: { CODEX_FAKE_FINAL: '[major] — a.txt:1 — x — y\nVerdict: revise' } });
|
|
917
|
+
const r = await run(sb, { env: { CODEX_FAKE_FINAL: '[major] — a.txt:1 — x — y\nVerdict: revise' } });
|
|
929
918
|
const receipts = readReceipts(sb.repo);
|
|
930
919
|
rmSync(sb.root, { recursive: true, force: true });
|
|
931
920
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -945,13 +934,13 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
945
934
|
});
|
|
946
935
|
|
|
947
936
|
// The probe marker (BRIDGE-MODES-CATALOG, D3): a CODEX_PROBE=1 review runs with the
|
|
948
|
-
//
|
|
937
|
+
// pinned-model/max-effort guard OFF, so its receipt must be distinguishable — the kit's
|
|
949
938
|
// review-state gate rejects a probe-marked receipt. EVERY receipt carries the marker (true or
|
|
950
939
|
// false): it self-declares, so the gate reads the fact rather than inferring it from a version
|
|
951
940
|
// string that bumps in a different release phase. Silence is not a declaration.
|
|
952
|
-
it('CODEX_PROBE=1 stamps probe:true — a throwaway probe can never attest a tree (D3)', () => {
|
|
941
|
+
it('CODEX_PROBE=1 stamps probe:true — a throwaway probe can never attest a tree (D3)', async () => {
|
|
953
942
|
const sb = makeSandbox();
|
|
954
|
-
const r = run(sb, { env: { CODEX_PROBE: '1', CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
943
|
+
const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
955
944
|
const receipts = readReceipts(sb.repo);
|
|
956
945
|
rmSync(sb.root, { recursive: true, force: true });
|
|
957
946
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -961,32 +950,32 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
961
950
|
|
|
962
951
|
// Every receipt SELF-DECLARES: the kit's gate reads the marker, never the wrapper version — so
|
|
963
952
|
// the marker must not depend on a version bump landing in the same release phase.
|
|
964
|
-
it('a normal review self-declares probe:false — the receipt states the fact, not a version', () => {
|
|
953
|
+
it('a normal review self-declares probe:false — the receipt states the fact, not a version', async () => {
|
|
965
954
|
const sb = makeSandbox();
|
|
966
|
-
run(sb, { env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
955
|
+
await run(sb, { env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
967
956
|
const receipts = readReceipts(sb.repo);
|
|
968
957
|
rmSync(sb.root, { recursive: true, force: true });
|
|
969
958
|
assert.equal(receipts[0].probe, false, 'silence is not a declaration — the gate rejects an unmarked receipt');
|
|
970
959
|
});
|
|
971
960
|
|
|
972
|
-
it('the marker tracks the RELAXED GUARD, not the model — a probe on an off-pinned model still marks', () => {
|
|
961
|
+
it('the marker tracks the RELAXED GUARD, not the model — a probe on an off-pinned model still marks', async () => {
|
|
973
962
|
const sb = makeSandbox();
|
|
974
|
-
const r = run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-5-mini', CODEX_EFFORT: 'low', CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
963
|
+
const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-5-mini', CODEX_EFFORT: 'low', CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
975
964
|
const receipts = readReceipts(sb.repo);
|
|
976
965
|
rmSync(sb.root, { recursive: true, force: true });
|
|
977
966
|
assert.equal(r.status, 0, r.stderr);
|
|
978
967
|
assert.equal(receipts[0].probe, true, 'exactly the runs the guard let through unpinned are marked');
|
|
979
968
|
});
|
|
980
969
|
|
|
981
|
-
it('the code-mode fingerprint tracks the uncommitted state (same tree → same hash; edit → different)', () => {
|
|
970
|
+
it('the code-mode fingerprint tracks the uncommitted state (same tree → same hash; edit → different)', async () => {
|
|
982
971
|
const sb = makeSandbox();
|
|
983
972
|
// Route the fake-codex capture files to /dev/null so the runs themselves leave the repo
|
|
984
973
|
// byte-identical (the default capture files land inside the repo and would change the tree).
|
|
985
974
|
const quiet = { CODEX_FAKE_ARGV: '/dev/null', CODEX_FAKE_ENV: '/dev/null', CODEX_FAKE_STDIN: '/dev/null', CODEX_FAKE_FINAL: 'Verdict: ship' };
|
|
986
|
-
run(sb, { env: quiet });
|
|
987
|
-
run(sb, { env: quiet });
|
|
975
|
+
await run(sb, { env: quiet });
|
|
976
|
+
await run(sb, { env: quiet });
|
|
988
977
|
writeFileSync(join(sb.repo, 'pending.txt'), 'edited after the first two reviews\n');
|
|
989
|
-
run(sb, { env: quiet });
|
|
978
|
+
await run(sb, { env: quiet });
|
|
990
979
|
const receipts = readReceipts(sb.repo);
|
|
991
980
|
rmSync(sb.root, { recursive: true, force: true });
|
|
992
981
|
assert.equal(receipts.length, 3);
|
|
@@ -994,9 +983,9 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
994
983
|
assert.notEqual(receipts[1].fingerprint, receipts[2].fingerprint, 'an edited tree changes the fingerprint');
|
|
995
984
|
});
|
|
996
985
|
|
|
997
|
-
it('CODEX_REVIEW_SCHEMA=1 reads the schema verdict field', () => {
|
|
986
|
+
it('CODEX_REVIEW_SCHEMA=1 reads the schema verdict field', async () => {
|
|
998
987
|
const sb = makeSandbox();
|
|
999
|
-
const r = run(sb, {
|
|
988
|
+
const r = await run(sb, {
|
|
1000
989
|
env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{"findings":[],"verdict":"ship","notes":"ok"}' },
|
|
1001
990
|
});
|
|
1002
991
|
const receipts = readReceipts(sb.repo);
|
|
@@ -1005,19 +994,19 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1005
994
|
assert.equal(receipts[0].verdict, 'ship');
|
|
1006
995
|
});
|
|
1007
996
|
|
|
1008
|
-
it('no parseable verdict is a FAILED run — never recorded as "unknown" (D4 owns the arm)', () => {
|
|
997
|
+
it('no parseable verdict is a FAILED run — never recorded as "unknown" (D4 owns the arm)', async () => {
|
|
1009
998
|
const sb = makeSandbox();
|
|
1010
|
-
const r = run(sb, { env: { CODEX_FAKE_FINAL: 'looks fine to me overall' } });
|
|
999
|
+
const r = await run(sb, { env: { CODEX_FAKE_FINAL: 'looks fine to me overall' } });
|
|
1011
1000
|
const receipts = readReceipts(sb.repo);
|
|
1012
1001
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1013
1002
|
assert.notEqual(r.status, 0);
|
|
1014
1003
|
assert.equal(receipts.length, 0, 'an unknown verdict never reaches the receipt store');
|
|
1015
1004
|
});
|
|
1016
1005
|
|
|
1017
|
-
it('plan mode: artifact "plan", fingerprint = the artifact-file sha256', () => {
|
|
1006
|
+
it('plan mode: artifact "plan", fingerprint = the artifact-file sha256', async () => {
|
|
1018
1007
|
const sb = makeSandbox();
|
|
1019
1008
|
const planBytes = readFileSync(join(sb.repo, 'plan.md'));
|
|
1020
|
-
const r = run(sb, { args: ['plan', 'plan.md'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1009
|
+
const r = await run(sb, { args: ['plan', 'plan.md'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1021
1010
|
const receipts = readReceipts(sb.repo);
|
|
1022
1011
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1023
1012
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1025,10 +1014,10 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1025
1014
|
assert.equal(receipts[0].fingerprint, sha256Hex(planBytes), 'plan fingerprint = file sha256');
|
|
1026
1015
|
});
|
|
1027
1016
|
|
|
1028
|
-
it('AW_REVIEW_RECEIPTS overrides the receipt destination', () => {
|
|
1017
|
+
it('AW_REVIEW_RECEIPTS overrides the receipt destination', async () => {
|
|
1029
1018
|
const sb = makeSandbox();
|
|
1030
1019
|
const override = join(sb.root, 'my-receipts.jsonl');
|
|
1031
|
-
const r = run(sb, { env: { AW_REVIEW_RECEIPTS: override, CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1020
|
+
const r = await run(sb, { env: { AW_REVIEW_RECEIPTS: override, CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1032
1021
|
const inGitDir = readReceipts(sb.repo);
|
|
1033
1022
|
const atOverride = existsSync(override) ? readFileSync(override, 'utf8') : '';
|
|
1034
1023
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1040,15 +1029,15 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1040
1029
|
// The wrapper-minted finding manifest (flow-orchestration Phase 4.2, Decision 2/P5/P24-25):
|
|
1041
1030
|
// nonce-supplied dispatches mint {schema, backend, nonce, fingerprint, findings} beside the
|
|
1042
1031
|
// receipt, atomic + no-clobber + ORDERED — a failed mint EXCLUDES the receipt append.
|
|
1043
|
-
describe('finding manifest (AW_REVIEW_NONCE)', () => {
|
|
1032
|
+
describe('finding manifest (AW_REVIEW_NONCE)', { concurrency: 2 }, () => {
|
|
1044
1033
|
const manifestPath = (repo, nonce) => join(repo, '.git', `agent-workflow-finding-manifest-codex-${nonce}.json`);
|
|
1045
1034
|
// Capture files ride /dev/null so repeated runs see an UNCHANGED tree (the manifest binds the
|
|
1046
1035
|
// fingerprint — a moved tree would legitimately change its bytes).
|
|
1047
1036
|
const quiet = { CODEX_FAKE_ARGV: '/dev/null', CODEX_FAKE_ENV: '/dev/null', CODEX_FAKE_STDIN: '/dev/null', CODEX_FAKE_FINAL: 'Verdict: ship' };
|
|
1048
1037
|
|
|
1049
|
-
it('a nonce-supplied code dispatch mints the {backend, nonce}-named manifest carrying the captured findings + the receipt fingerprint', () => {
|
|
1038
|
+
it('a nonce-supplied code dispatch mints the {backend, nonce}-named manifest carrying the captured findings + the receipt fingerprint', async () => {
|
|
1050
1039
|
const sb = makeSandbox();
|
|
1051
|
-
const r = run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'r1-d1' } });
|
|
1040
|
+
const r = await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'r1-d1' } });
|
|
1052
1041
|
const receipts = readReceipts(sb.repo);
|
|
1053
1042
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'r1-d1'), 'utf8'));
|
|
1054
1043
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1063,9 +1052,9 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1063
1052
|
assert.equal(receipts[0].nonce, 'r1-d1', 'a nonce-supplied receipt carries the dispatch nonce — the flow round-land matcher requires exact equality (dispatch identity end-to-end)');
|
|
1064
1053
|
});
|
|
1065
1054
|
|
|
1066
|
-
it('a nonce-less invocation mints NO manifest and adds NO nonce field — the receipt field set is unchanged (Decision 2 both branches)', () => {
|
|
1055
|
+
it('a nonce-less invocation mints NO manifest and adds NO nonce field — the receipt field set is unchanged (Decision 2 both branches)', async () => {
|
|
1067
1056
|
const sb = makeSandbox();
|
|
1068
|
-
const r = run(sb, { env: { ...quiet } });
|
|
1057
|
+
const r = await run(sb, { env: { ...quiet } });
|
|
1069
1058
|
const receipts = readReceipts(sb.repo);
|
|
1070
1059
|
const gitEntries = readdirSync(join(sb.repo, '.git')).filter((n) => n.startsWith('agent-workflow-finding-manifest-'));
|
|
1071
1060
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1074,11 +1063,11 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1074
1063
|
assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'the receipt line field set is unchanged');
|
|
1075
1064
|
});
|
|
1076
1065
|
|
|
1077
|
-
it('a byte-identical re-mint is an idempotent no-op — the second receipt still lands', () => {
|
|
1066
|
+
it('a byte-identical re-mint is an idempotent no-op — the second receipt still lands', async () => {
|
|
1078
1067
|
const sb = makeSandbox();
|
|
1079
|
-
assert.equal(run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'r1-d1' } }).status, 0);
|
|
1068
|
+
assert.equal((await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'r1-d1' } })).status, 0);
|
|
1080
1069
|
const before = readFileSync(manifestPath(sb.repo, 'r1-d1'), 'utf8');
|
|
1081
|
-
const r2 = run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'r1-d1' } });
|
|
1070
|
+
const r2 = await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'r1-d1' } });
|
|
1082
1071
|
const receipts = readReceipts(sb.repo);
|
|
1083
1072
|
const after = readFileSync(manifestPath(sb.repo, 'r1-d1'), 'utf8');
|
|
1084
1073
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1087,10 +1076,10 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1087
1076
|
assert.equal(after, before, 'the manifest bytes are untouched');
|
|
1088
1077
|
});
|
|
1089
1078
|
|
|
1090
|
-
it('DIFFERENT bytes at the derived name refuse loudly AND EXCLUDE the receipt append (ordering, behaviorally)', () => {
|
|
1079
|
+
it('DIFFERENT bytes at the derived name refuse loudly AND EXCLUDE the receipt append (ordering, behaviorally)', async () => {
|
|
1091
1080
|
const sb = makeSandbox();
|
|
1092
1081
|
writeFileSync(manifestPath(sb.repo, 'r1-d1'), '{"schema":1,"backend":"codex","nonce":"r1-d1","fingerprint":null,"findings":"other bytes"}\n');
|
|
1093
|
-
const r = run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'r1-d1' } });
|
|
1082
|
+
const r = await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'r1-d1' } });
|
|
1094
1083
|
const receipts = readReceipts(sb.repo);
|
|
1095
1084
|
const manifest = readFileSync(manifestPath(sb.repo, 'r1-d1'), 'utf8');
|
|
1096
1085
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1101,11 +1090,11 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1101
1090
|
assert.match(manifest, /other bytes/, 'the pre-existing manifest is never clobbered');
|
|
1102
1091
|
});
|
|
1103
1092
|
|
|
1104
|
-
it('a tmp-unlink failure after a successful mint warns with the ORPHAN PATH and still appends the receipt (preload-forced; parity carries the agy twin)', () => {
|
|
1093
|
+
it('a tmp-unlink failure after a successful mint warns with the ORPHAN PATH and still appends the receipt (preload-forced; parity carries the agy twin)', async () => {
|
|
1105
1094
|
const sb = makeSandbox();
|
|
1106
1095
|
const preload = join(sb.root, 'unlink-fail-preload.cjs');
|
|
1107
1096
|
writeFileSync(preload, "const fs = require('node:fs');\nconst real = fs.unlinkSync;\nfs.unlinkSync = (p) => { if (String(p).includes('.tmp')) { const e = new Error('EPERM'); e.code = 'EPERM'; throw e; } return real(p); };\n");
|
|
1108
|
-
const r = run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'orph1', NODE_OPTIONS: `--require ${preload}` } });
|
|
1097
|
+
const r = await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'orph1', NODE_OPTIONS: `--require ${preload}` } });
|
|
1109
1098
|
const receipts = readReceipts(sb.repo);
|
|
1110
1099
|
const manifestExists = existsSync(manifestPath(sb.repo, 'orph1'));
|
|
1111
1100
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1124,12 +1113,12 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1124
1113
|
{ rc: 3, name: 'no-clobber (rc 3)', preload: UNLINK_FAIL, plant: true, errRe: /DIFFERENT bytes or is not a regular file/ },
|
|
1125
1114
|
{ rc: 1, name: 'fs failure (rc 1)', preload: UNLINK_FAIL + LINK_FAIL, plant: false, errRe: /could not compose or write the finding manifest/ },
|
|
1126
1115
|
]) {
|
|
1127
|
-
it(`a FAILURE exit (${failure.name}) whose temp unlink also fails names the ORPHAN PATH in the error`, () => {
|
|
1116
|
+
it(`a FAILURE exit (${failure.name}) whose temp unlink also fails names the ORPHAN PATH in the error`, async () => {
|
|
1128
1117
|
const sb = makeSandbox();
|
|
1129
1118
|
if (failure.plant) writeFileSync(manifestPath(sb.repo, 'orphf1'), 'planted different bytes\n');
|
|
1130
1119
|
const preload = join(sb.root, 'orphan-fail-preload.cjs');
|
|
1131
1120
|
writeFileSync(preload, failure.preload);
|
|
1132
|
-
const r = run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'orphf1', NODE_OPTIONS: `--require ${preload}` } });
|
|
1121
|
+
const r = await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'orphf1', NODE_OPTIONS: `--require ${preload}` } });
|
|
1133
1122
|
const receipts = readReceipts(sb.repo);
|
|
1134
1123
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1135
1124
|
assert.equal(r.status, 0, 'the review itself still succeeds (the artifact lane failed loudly)');
|
|
@@ -1139,10 +1128,10 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1139
1128
|
});
|
|
1140
1129
|
}
|
|
1141
1130
|
|
|
1142
|
-
it('a FAILURE exit whose temp IS removable stays orphan-silent (no leftover, no orphan line)', () => {
|
|
1131
|
+
it('a FAILURE exit whose temp IS removable stays orphan-silent (no leftover, no orphan line)', async () => {
|
|
1143
1132
|
const sb = makeSandbox();
|
|
1144
1133
|
writeFileSync(manifestPath(sb.repo, 'orphf2'), 'planted different bytes\n');
|
|
1145
|
-
const r = run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'orphf2' } });
|
|
1134
|
+
const r = await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'orphf2' } });
|
|
1146
1135
|
const leftovers = readdirSync(join(sb.repo, '.git')).filter((n) => n.endsWith('.tmp'));
|
|
1147
1136
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1148
1137
|
assert.equal(r.status, 0, 'the review itself still succeeds (the artifact lane failed loudly)');
|
|
@@ -1151,15 +1140,15 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1151
1140
|
assert.deepEqual(leftovers, [], 'the temp really was removed');
|
|
1152
1141
|
});
|
|
1153
1142
|
|
|
1154
|
-
it('a SYMLINK at the derived manifest path refuses and EXCLUDES the receipt — even when its target is byte-identical', () => {
|
|
1143
|
+
it('a SYMLINK at the derived manifest path refuses and EXCLUDES the receipt — even when its target is byte-identical', async () => {
|
|
1155
1144
|
const sb = makeSandbox();
|
|
1156
|
-
assert.equal(run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'sym1' } }).status, 0);
|
|
1145
|
+
assert.equal((await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'sym1' } })).status, 0);
|
|
1157
1146
|
const mPath = manifestPath(sb.repo, 'sym1');
|
|
1158
1147
|
const target = join(sb.repo, '.git', 'manifest-target-copy.json');
|
|
1159
1148
|
writeFileSync(target, readFileSync(mPath));
|
|
1160
1149
|
rmSync(mPath);
|
|
1161
1150
|
symlinkSync(target, mPath);
|
|
1162
|
-
const r = run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'sym1' } });
|
|
1151
|
+
const r = await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'sym1' } });
|
|
1163
1152
|
const receipts = readReceipts(sb.repo);
|
|
1164
1153
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1165
1154
|
assert.equal(r.status, 0, 'the review itself still succeeds (the artifact lane failed loudly)');
|
|
@@ -1167,11 +1156,11 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1167
1156
|
assert.equal(receipts.length, 1, 'the second receipt is EXCLUDED — a symlinked manifest is never read through as the idempotent no-op');
|
|
1168
1157
|
});
|
|
1169
1158
|
|
|
1170
|
-
it('a FIFO at the derived manifest path refuses fast and EXCLUDES the receipt (O_NONBLOCK — no hang; fix characterization)', () => {
|
|
1159
|
+
it('a FIFO at the derived manifest path refuses fast and EXCLUDES the receipt (O_NONBLOCK — no hang; fix characterization)', async () => {
|
|
1171
1160
|
const sb = makeSandbox();
|
|
1172
1161
|
const mPath = manifestPath(sb.repo, 'fifo1');
|
|
1173
1162
|
assert.equal(spawnSync('mkfifo', [mPath], { encoding: 'utf8' }).status, 0, 'mkfifo fixture');
|
|
1174
|
-
const r = run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'fifo1' } });
|
|
1163
|
+
const r = await run(sb, { env: { ...quiet, AW_REVIEW_NONCE: 'fifo1' } });
|
|
1175
1164
|
const receipts = readReceipts(sb.repo);
|
|
1176
1165
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1177
1166
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1179,18 +1168,18 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1179
1168
|
assert.equal(receipts.length, 0, 'a FIFO manifest is never read (fstat-first) and the receipt is excluded');
|
|
1180
1169
|
});
|
|
1181
1170
|
|
|
1182
|
-
it('a BOM-prefixed findings payload round-trips VERBATIM into the manifest (U+FEFF preserved)', () => {
|
|
1171
|
+
it('a BOM-prefixed findings payload round-trips VERBATIM into the manifest (U+FEFF preserved)', async () => {
|
|
1183
1172
|
const sb = makeSandbox();
|
|
1184
|
-
const r = run(sb, { env: { ...quiet, CODEX_FAKE_FINAL: '\uFEFFFinding A\nVerdict: ship', AW_REVIEW_NONCE: 'b1' } });
|
|
1173
|
+
const r = await run(sb, { env: { ...quiet, CODEX_FAKE_FINAL: '\uFEFFFinding A\nVerdict: ship', AW_REVIEW_NONCE: 'b1' } });
|
|
1185
1174
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'b1'), 'utf8'));
|
|
1186
1175
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1187
1176
|
assert.equal(r.status, 0, r.stderr);
|
|
1188
1177
|
assert.equal(manifest.findings, '\uFEFFFinding A\nVerdict: ship\n', 'the captured findings are VERBATIM — a stripped BOM would move the findingDigest');
|
|
1189
1178
|
});
|
|
1190
1179
|
|
|
1191
|
-
it('an unsafe nonce refuses PRE-SPEND (exit 2, codex never runs) — a non-ASCII letter refuses under a UTF-8 locale too', () => {
|
|
1180
|
+
it('an unsafe nonce refuses PRE-SPEND (exit 2, codex never runs) — a non-ASCII letter refuses under a UTF-8 locale too', async () => {
|
|
1192
1181
|
const sb = makeSandbox();
|
|
1193
|
-
const r = run(sb, { env: { AW_REVIEW_NONCE: '../escape' } });
|
|
1182
|
+
const r = await run(sb, { env: { AW_REVIEW_NONCE: '../escape' } });
|
|
1194
1183
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1195
1184
|
assert.equal(r.status, 2);
|
|
1196
1185
|
assert.match(r.stderr, /safe nonce grammar/);
|
|
@@ -1198,25 +1187,25 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1198
1187
|
// The grammar ENUMERATES the ASCII set (no ranges): a locale-collated [A-Za-z] could admit a
|
|
1199
1188
|
// non-ASCII letter the kit's JS reader then refuses, breaking correlation after a paid run.
|
|
1200
1189
|
const utf8 = makeSandbox();
|
|
1201
|
-
const r2 = run(utf8, { env: { AW_REVIEW_NONCE: 'r\u00e91', LC_ALL: 'en_US.UTF-8', LANG: 'en_US.UTF-8' } });
|
|
1190
|
+
const r2 = await run(utf8, { env: { AW_REVIEW_NONCE: 'r\u00e91', LC_ALL: 'en_US.UTF-8', LANG: 'en_US.UTF-8' } });
|
|
1202
1191
|
rmSync(utf8.root, { recursive: true, force: true });
|
|
1203
1192
|
assert.equal(r2.status, 2, 'a non-ASCII nonce letter refuses whatever the locale collation says');
|
|
1204
1193
|
assert.match(r2.stderr, /safe nonce grammar/);
|
|
1205
1194
|
});
|
|
1206
1195
|
|
|
1207
|
-
it('plan mode with a nonce mints the manifest too (fingerprint = the artifact sha256)', () => {
|
|
1196
|
+
it('plan mode with a nonce mints the manifest too (fingerprint = the artifact sha256)', async () => {
|
|
1208
1197
|
const sb = makeSandbox();
|
|
1209
1198
|
const planBytes = readFileSync(join(sb.repo, 'plan.md'));
|
|
1210
|
-
const r = run(sb, { args: ['plan', 'plan.md'], env: { CODEX_FAKE_FINAL: 'Verdict: ship', AW_REVIEW_NONCE: 'p1' } });
|
|
1199
|
+
const r = await run(sb, { args: ['plan', 'plan.md'], env: { CODEX_FAKE_FINAL: 'Verdict: ship', AW_REVIEW_NONCE: 'p1' } });
|
|
1211
1200
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'p1'), 'utf8'));
|
|
1212
1201
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1213
1202
|
assert.equal(r.status, 0, r.stderr);
|
|
1214
1203
|
assert.equal(manifest.fingerprint, sha256Hex(planBytes));
|
|
1215
1204
|
});
|
|
1216
1205
|
|
|
1217
|
-
it('a FAILED review (no verdict) mints neither receipt nor manifest — the pair rides success only', () => {
|
|
1206
|
+
it('a FAILED review (no verdict) mints neither receipt nor manifest — the pair rides success only', async () => {
|
|
1218
1207
|
const sb = makeSandbox();
|
|
1219
|
-
const r = run(sb, { env: { CODEX_FAKE_FINAL: 'no verdict here', AW_REVIEW_NONCE: 'r9' } });
|
|
1208
|
+
const r = await run(sb, { env: { CODEX_FAKE_FINAL: 'no verdict here', AW_REVIEW_NONCE: 'r9' } });
|
|
1220
1209
|
const receipts = readReceipts(sb.repo);
|
|
1221
1210
|
const exists = existsSync(manifestPath(sb.repo, 'r9'));
|
|
1222
1211
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1227,9 +1216,9 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1227
1216
|
|
|
1228
1217
|
// The --nonce flag (FLOW-NONCE-DISPATCH-LANE): the plain-argument lane onto the SAME seam —
|
|
1229
1218
|
// for hosts whose dispatch policy has no env-prefix form.
|
|
1230
|
-
it('--nonce in code mode rides the AW_REVIEW_NONCE seam: manifest minted, receipt nonce-stamped, focus words intact', () => {
|
|
1219
|
+
it('--nonce in code mode rides the AW_REVIEW_NONCE seam: manifest minted, receipt nonce-stamped, focus words intact', async () => {
|
|
1231
1220
|
const sb = makeSandbox();
|
|
1232
|
-
const r = run(sb, { args: ['code', '--nonce', 'f1-d1', 'look', 'harder'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1221
|
+
const r = await run(sb, { args: ['code', '--nonce', 'f1-d1', 'look', 'harder'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1233
1222
|
const receipts = readReceipts(sb.repo);
|
|
1234
1223
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'f1-d1'), 'utf8'));
|
|
1235
1224
|
const stdin = r.capStdin;
|
|
@@ -1240,45 +1229,45 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1240
1229
|
assert.match(stdin, /Extra focus: look harder/, 'the flag pair is stripped — trailing focus words still ride');
|
|
1241
1230
|
});
|
|
1242
1231
|
|
|
1243
|
-
it('--nonce in plan mode mints the manifest (the consult-dispatch lane) — the flag never trips the no-extra-args refusal', () => {
|
|
1232
|
+
it('--nonce in plan mode mints the manifest (the consult-dispatch lane) — the flag never trips the no-extra-args refusal', async () => {
|
|
1244
1233
|
const sb = makeSandbox();
|
|
1245
|
-
const r = run(sb, { args: ['plan', 'plan.md', '--nonce', 'p2'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1234
|
+
const r = await run(sb, { args: ['plan', 'plan.md', '--nonce', 'p2'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1246
1235
|
const exists = existsSync(manifestPath(sb.repo, 'p2'));
|
|
1247
1236
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1248
1237
|
assert.equal(r.status, 0, r.stderr);
|
|
1249
1238
|
assert.equal(exists, true);
|
|
1250
1239
|
});
|
|
1251
1240
|
|
|
1252
|
-
it('an unsafe --nonce value refuses PRE-SPEND (exit 2, codex never runs) — same grammar as the env screen', () => {
|
|
1241
|
+
it('an unsafe --nonce value refuses PRE-SPEND (exit 2, codex never runs) — same grammar as the env screen', async () => {
|
|
1253
1242
|
const sb = makeSandbox();
|
|
1254
|
-
const r = run(sb, { args: ['code', '--nonce', 'a/b'] });
|
|
1243
|
+
const r = await run(sb, { args: ['code', '--nonce', 'a/b'] });
|
|
1255
1244
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1256
1245
|
assert.equal(r.status, 2);
|
|
1257
1246
|
assert.match(r.stderr, /safe nonce grammar/);
|
|
1258
1247
|
assert.equal(r.capStdin, '', 'the refusal fires before any CLI spend');
|
|
1259
1248
|
});
|
|
1260
1249
|
|
|
1261
|
-
it('a missing value, a duplicate flag, and a disagreeing env+flag pair each refuse (exit 2); an agreeing pair proceeds', () => {
|
|
1250
|
+
it('a missing value, a duplicate flag, and a disagreeing env+flag pair each refuse (exit 2); an agreeing pair proceeds', async () => {
|
|
1262
1251
|
const sb = makeSandbox();
|
|
1263
|
-
const missing = run(sb, { args: ['code', '--nonce'] });
|
|
1252
|
+
const missing = await run(sb, { args: ['code', '--nonce'] });
|
|
1264
1253
|
assert.equal(missing.status, 2);
|
|
1265
1254
|
assert.match(missing.stderr, /--nonce needs a value/);
|
|
1266
|
-
const dup = run(sb, { args: ['code', '--nonce', 'n1', '--nonce', 'n2'] });
|
|
1255
|
+
const dup = await run(sb, { args: ['code', '--nonce', 'n1', '--nonce', 'n2'] });
|
|
1267
1256
|
assert.equal(dup.status, 2);
|
|
1268
1257
|
assert.match(dup.stderr, /duplicate --nonce/);
|
|
1269
|
-
const clash = run(sb, { args: ['code', '--nonce', 'n1'], env: { AW_REVIEW_NONCE: 'n2' } });
|
|
1258
|
+
const clash = await run(sb, { args: ['code', '--nonce', 'n1'], env: { AW_REVIEW_NONCE: 'n2' } });
|
|
1270
1259
|
assert.equal(clash.status, 2);
|
|
1271
1260
|
assert.match(clash.stderr, /disagrees with the AW_REVIEW_NONCE environment value/);
|
|
1272
|
-
const agree = run(sb, { args: ['code', '--nonce', 'n3'], env: { AW_REVIEW_NONCE: 'n3', CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1261
|
+
const agree = await run(sb, { args: ['code', '--nonce', 'n3'], env: { AW_REVIEW_NONCE: 'n3', CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1273
1262
|
const exists = existsSync(manifestPath(sb.repo, 'n3'));
|
|
1274
1263
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1275
1264
|
assert.equal(agree.status, 0, agree.stderr);
|
|
1276
1265
|
assert.equal(exists, true, 'an agreeing pair is ONE seam value — the dispatch proceeds');
|
|
1277
1266
|
});
|
|
1278
1267
|
|
|
1279
|
-
it('a grammar-valid leading-dash --nonce value is ACCEPTED — the flag lane spans the whole declared grammar', () => {
|
|
1268
|
+
it('a grammar-valid leading-dash --nonce value is ACCEPTED — the flag lane spans the whole declared grammar', async () => {
|
|
1280
1269
|
const sb = makeSandbox();
|
|
1281
|
-
const r = run(sb, { args: ['code', '--nonce', '--n1'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1270
|
+
const r = await run(sb, { args: ['code', '--nonce', '--n1'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
1282
1271
|
const receipts = readReceipts(sb.repo);
|
|
1283
1272
|
const exists = existsSync(manifestPath(sb.repo, '--n1'));
|
|
1284
1273
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1287,18 +1276,18 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1287
1276
|
assert.equal(receipts[0].nonce, '--n1', 'the receipt carries the exact grammar-valid value — flag lane ≡ env lane');
|
|
1288
1277
|
});
|
|
1289
1278
|
|
|
1290
|
-
it('an EMPTY --nonce value refuses pre-spend under the grammar screen (presence is tracked separately from the value)', () => {
|
|
1279
|
+
it('an EMPTY --nonce value refuses pre-spend under the grammar screen (presence is tracked separately from the value)', async () => {
|
|
1291
1280
|
const sb = makeSandbox();
|
|
1292
|
-
const r = run(sb, { args: ['code', '--nonce', ''] });
|
|
1281
|
+
const r = await run(sb, { args: ['code', '--nonce', ''] });
|
|
1293
1282
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1294
1283
|
assert.equal(r.status, 2);
|
|
1295
1284
|
assert.match(r.stderr, /safe nonce grammar/);
|
|
1296
1285
|
assert.equal(r.capStdin, '', 'the refusal fires before any CLI spend');
|
|
1297
1286
|
});
|
|
1298
1287
|
|
|
1299
|
-
it('a duplicate --nonce after an EMPTY first value still refuses as a duplicate — an empty value never erases presence', () => {
|
|
1288
|
+
it('a duplicate --nonce after an EMPTY first value still refuses as a duplicate — an empty value never erases presence', async () => {
|
|
1300
1289
|
const sb = makeSandbox();
|
|
1301
|
-
const r = run(sb, { args: ['code', '--nonce', '', '--nonce', 'n2'] });
|
|
1290
|
+
const r = await run(sb, { args: ['code', '--nonce', '', '--nonce', 'n2'] });
|
|
1302
1291
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1303
1292
|
assert.equal(r.status, 2);
|
|
1304
1293
|
assert.match(r.stderr, /duplicate --nonce/);
|
|
@@ -1306,9 +1295,9 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1306
1295
|
});
|
|
1307
1296
|
});
|
|
1308
1297
|
|
|
1309
|
-
it('a receipt write failure warns loudly but never fails the review (fail-safe direction)', () => {
|
|
1298
|
+
it('a receipt write failure warns loudly but never fails the review (fail-safe direction)', async () => {
|
|
1310
1299
|
const sb = makeSandbox();
|
|
1311
|
-
const r = run(sb, {
|
|
1300
|
+
const r = await run(sb, {
|
|
1312
1301
|
env: { AW_REVIEW_RECEIPTS: join(sb.repo, 'no-such-dir', 'r.jsonl'), CODEX_FAKE_FINAL: 'Verdict: ship' },
|
|
1313
1302
|
});
|
|
1314
1303
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -1317,18 +1306,18 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
|
|
|
1317
1306
|
assert.match(r.stdout, /Verdict: ship/, 'the findings still reach stdout');
|
|
1318
1307
|
});
|
|
1319
1308
|
|
|
1320
|
-
it('a failed codex run writes NO receipt (only a successful review attests)', () => {
|
|
1309
|
+
it('a failed codex run writes NO receipt (only a successful review attests)', async () => {
|
|
1321
1310
|
const sb = makeSandbox();
|
|
1322
|
-
const r = run(sb, { env: { CODEX_FAKE_EXIT: '5' } });
|
|
1311
|
+
const r = await run(sb, { env: { CODEX_FAKE_EXIT: '5' } });
|
|
1323
1312
|
const receipts = readReceipts(sb.repo);
|
|
1324
1313
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1325
1314
|
assert.notEqual(r.status, 0);
|
|
1326
1315
|
assert.equal(receipts.length, 0);
|
|
1327
1316
|
});
|
|
1328
1317
|
|
|
1329
|
-
it('the clean-tree preflight exits before any receipt is written', () => {
|
|
1318
|
+
it('the clean-tree preflight exits before any receipt is written', async () => {
|
|
1330
1319
|
const sb = makeSandbox({ clean: true });
|
|
1331
|
-
const r = run(sb);
|
|
1320
|
+
const r = await run(sb);
|
|
1332
1321
|
const receipts = readReceipts(sb.repo);
|
|
1333
1322
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1334
1323
|
assert.equal(r.status, 0);
|
|
@@ -1351,45 +1340,45 @@ const writeSettings = (sb, text) => {
|
|
|
1351
1340
|
};
|
|
1352
1341
|
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
1353
1342
|
|
|
1354
|
-
describe('codex-review.sh — service tier knob (bridges 2.3.0)', () => {
|
|
1355
|
-
it('default: no env, no file → NO service_tier flag in codex argv', () => {
|
|
1343
|
+
describe('codex-review.sh — service tier knob (bridges 2.3.0)', { concurrency: 2 }, () => {
|
|
1344
|
+
it('default: no env, no file → NO service_tier flag in codex argv', async () => {
|
|
1356
1345
|
const sb = makeSandbox();
|
|
1357
|
-
const r = run(sb);
|
|
1346
|
+
const r = await run(sb);
|
|
1358
1347
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1359
1348
|
assert.equal(r.status, 0, r.stderr);
|
|
1360
1349
|
assert.doesNotMatch(r.argv, /service_tier/, 'default OFF: the flag must be absent');
|
|
1361
1350
|
assert.doesNotMatch(r.stderr, /bridge settings/, 'no file → no settings chatter');
|
|
1362
1351
|
});
|
|
1363
1352
|
|
|
1364
|
-
it('env CODEX_SERVICE_TIER=priority → -c service_tier=priority reaches codex argv', () => {
|
|
1353
|
+
it('env CODEX_SERVICE_TIER=priority → -c service_tier=priority reaches codex argv', async () => {
|
|
1365
1354
|
const sb = makeSandbox();
|
|
1366
|
-
const r = run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
1355
|
+
const r = await run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
1367
1356
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1368
1357
|
assert.equal(r.status, 0, r.stderr);
|
|
1369
1358
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/);
|
|
1370
1359
|
});
|
|
1371
1360
|
|
|
1372
|
-
it('a file-set tier lands (file wins over the built-in default)', () => {
|
|
1361
|
+
it('a file-set tier lands (file wins over the built-in default)', async () => {
|
|
1373
1362
|
const sb = makeSandbox();
|
|
1374
1363
|
writeSettings(sb, 'CODEX_SERVICE_TIER=priority\n');
|
|
1375
|
-
const r = run(sb);
|
|
1364
|
+
const r = await run(sb);
|
|
1376
1365
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1377
1366
|
assert.equal(r.status, 0, r.stderr);
|
|
1378
1367
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/);
|
|
1379
1368
|
});
|
|
1380
1369
|
|
|
1381
|
-
it('an EXPLICITLY EMPTY env (CODEX_SERVICE_TIER=) disables a file-set tier for one run', () => {
|
|
1370
|
+
it('an EXPLICITLY EMPTY env (CODEX_SERVICE_TIER=) disables a file-set tier for one run', async () => {
|
|
1382
1371
|
const sb = makeSandbox();
|
|
1383
1372
|
writeSettings(sb, 'CODEX_SERVICE_TIER=priority\n');
|
|
1384
|
-
const r = run(sb, { env: { CODEX_SERVICE_TIER: '' } });
|
|
1373
|
+
const r = await run(sb, { env: { CODEX_SERVICE_TIER: '' } });
|
|
1385
1374
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1386
1375
|
assert.equal(r.status, 0, r.stderr);
|
|
1387
1376
|
assert.doesNotMatch(r.argv, /service_tier/, 'env wins over file — empty means knob off');
|
|
1388
1377
|
});
|
|
1389
1378
|
|
|
1390
|
-
it('an invalid env tier warns and reviews on the standard tier (never passed to codex)', () => {
|
|
1379
|
+
it('an invalid env tier warns and reviews on the standard tier (never passed to codex)', async () => {
|
|
1391
1380
|
const sb = makeSandbox();
|
|
1392
|
-
const r = run(sb, { env: { CODEX_SERVICE_TIER: 'turbo' } });
|
|
1381
|
+
const r = await run(sb, { env: { CODEX_SERVICE_TIER: 'turbo' } });
|
|
1393
1382
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1394
1383
|
assert.equal(r.status, 0, r.stderr);
|
|
1395
1384
|
assert.match(r.stderr, /not a supported service tier/);
|
|
@@ -1397,52 +1386,52 @@ describe('codex-review.sh — service tier knob (bridges 2.3.0)', () => {
|
|
|
1397
1386
|
});
|
|
1398
1387
|
});
|
|
1399
1388
|
|
|
1400
|
-
describe('codex-review.sh — bridge settings file semantics (bridges 2.3.0)', () => {
|
|
1401
|
-
it('a file-set CODEX_REVIEW_MAX_TOTAL_BYTES is effective (switches to the temp-file path)', () => {
|
|
1389
|
+
describe('codex-review.sh — bridge settings file semantics (bridges 2.3.0)', { concurrency: 2 }, () => {
|
|
1390
|
+
it('a file-set CODEX_REVIEW_MAX_TOTAL_BYTES is effective (switches to the temp-file path)', async () => {
|
|
1402
1391
|
const sb = makeSandbox();
|
|
1403
1392
|
writeFileSync(join(sb.repo, 'big.txt'), 'B'.repeat(5000));
|
|
1404
1393
|
writeSettings(sb, 'CODEX_REVIEW_MAX_TOTAL_BYTES=100\n');
|
|
1405
|
-
const r = run(sb);
|
|
1394
|
+
const r = await run(sb);
|
|
1406
1395
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1407
1396
|
assert.equal(r.status, 0, r.stderr);
|
|
1408
1397
|
assert.match(r.capStdin, /codex-review-diff\./, 'the tiny file cap must force the temp-file path');
|
|
1409
1398
|
assert.doesNotMatch(r.capStdin, /ASSEMBLED CHANGE SET:/, 'the payload must not ALSO ride inline');
|
|
1410
1399
|
});
|
|
1411
1400
|
|
|
1412
|
-
it('env overrides file: a large env cap keeps the payload inline', () => {
|
|
1401
|
+
it('env overrides file: a large env cap keeps the payload inline', async () => {
|
|
1413
1402
|
const sb = makeSandbox();
|
|
1414
1403
|
writeFileSync(join(sb.repo, 'big.txt'), 'B'.repeat(5000));
|
|
1415
1404
|
writeSettings(sb, 'CODEX_REVIEW_MAX_TOTAL_BYTES=100\n');
|
|
1416
|
-
const r = run(sb, { env: { CODEX_REVIEW_MAX_TOTAL_BYTES: '5000000' } });
|
|
1405
|
+
const r = await run(sb, { env: { CODEX_REVIEW_MAX_TOTAL_BYTES: '5000000' } });
|
|
1417
1406
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1418
1407
|
assert.equal(r.status, 0, r.stderr);
|
|
1419
1408
|
assert.match(r.capStdin, /ASSEMBLED CHANGE SET:/, 'the env cap (large) must win over the file cap (100)');
|
|
1420
1409
|
assert.doesNotMatch(r.capStdin, /codex-review-diff\./);
|
|
1421
1410
|
});
|
|
1422
1411
|
|
|
1423
|
-
it('duplicate key → the LAST occurrence wins (100 then 5000000 → inline)', () => {
|
|
1412
|
+
it('duplicate key → the LAST occurrence wins (100 then 5000000 → inline)', async () => {
|
|
1424
1413
|
const sb = makeSandbox();
|
|
1425
1414
|
writeFileSync(join(sb.repo, 'big.txt'), 'B'.repeat(5000));
|
|
1426
1415
|
writeSettings(sb, 'CODEX_REVIEW_MAX_TOTAL_BYTES=100\nCODEX_REVIEW_MAX_TOTAL_BYTES=5000000\n');
|
|
1427
|
-
const r = run(sb);
|
|
1416
|
+
const r = await run(sb);
|
|
1428
1417
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1429
1418
|
assert.equal(r.status, 0, r.stderr);
|
|
1430
1419
|
assert.match(r.capStdin, /ASSEMBLED CHANGE SET:/);
|
|
1431
1420
|
});
|
|
1432
1421
|
|
|
1433
|
-
it("another wrapper's / another bridge's valid key is skipped silently", () => {
|
|
1422
|
+
it("another wrapper's / another bridge's valid key is skipped silently", async () => {
|
|
1434
1423
|
const sb = makeSandbox();
|
|
1435
1424
|
writeSettings(sb, 'AGY_HARD_TIMEOUT=30m\nAGY_REVIEW_ALLOW_ADDDIR=1\n');
|
|
1436
|
-
const r = run(sb);
|
|
1425
|
+
const r = await run(sb);
|
|
1437
1426
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1438
1427
|
assert.equal(r.status, 0, r.stderr);
|
|
1439
1428
|
assert.doesNotMatch(r.stderr, /bridge settings/, 'a recognized non-applied key earns NO warning');
|
|
1440
1429
|
});
|
|
1441
1430
|
|
|
1442
|
-
it('a truly unknown key warns ONCE naming the file; the review is unaffected', () => {
|
|
1431
|
+
it('a truly unknown key warns ONCE naming the file; the review is unaffected', async () => {
|
|
1443
1432
|
const sb = makeSandbox();
|
|
1444
1433
|
writeSettings(sb, 'TOTALLY_UNKNOWN=1\nTOTALLY_UNKNOWN=2\n');
|
|
1445
|
-
const r = run(sb);
|
|
1434
|
+
const r = await run(sb);
|
|
1446
1435
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1447
1436
|
assert.equal(r.status, 0, r.stderr);
|
|
1448
1437
|
const warns = r.stderr.match(/unknown key 'TOTALLY_UNKNOWN'/g) ?? [];
|
|
@@ -1450,10 +1439,10 @@ describe('codex-review.sh — bridge settings file semantics (bridges 2.3.0)', (
|
|
|
1450
1439
|
assert.match(r.stderr, /bridge-settings\.conf/, 'the warning must name the settings file');
|
|
1451
1440
|
});
|
|
1452
1441
|
|
|
1453
|
-
it('malformed lines warn and are ignored; comments and blank lines are silent', () => {
|
|
1442
|
+
it('malformed lines warn and are ignored; comments and blank lines are silent', async () => {
|
|
1454
1443
|
const sb = makeSandbox();
|
|
1455
1444
|
writeSettings(sb, '# a comment\n\nNOT A KEY VALUE LINE\nCODEX_SERVICE_TIER=priority\n');
|
|
1456
|
-
const r = run(sb);
|
|
1445
|
+
const r = await run(sb);
|
|
1457
1446
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1458
1447
|
assert.equal(r.status, 0, r.stderr);
|
|
1459
1448
|
const malformed = r.stderr.match(/malformed line/g) ?? [];
|
|
@@ -1461,7 +1450,7 @@ describe('codex-review.sh — bridge settings file semantics (bridges 2.3.0)', (
|
|
|
1461
1450
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/, 'valid lines still apply');
|
|
1462
1451
|
});
|
|
1463
1452
|
|
|
1464
|
-
it('an existing-but-unreadable file warns loudly and falls back to built-ins', { skip: isRoot }, () => {
|
|
1453
|
+
it('an existing-but-unreadable file warns loudly and falls back to built-ins', { skip: isRoot }, async () => {
|
|
1465
1454
|
// The settings file goes OUTSIDE the repo (XDG_CONFIG_HOME): an unreadable file INSIDE the
|
|
1466
1455
|
// work tree would fail the review-payload assembly itself (untracked contents are cat'ed),
|
|
1467
1456
|
// which is pre-existing behaviour unrelated to the settings reader.
|
|
@@ -1471,14 +1460,14 @@ describe('codex-review.sh — bridge settings file semantics (bridges 2.3.0)', (
|
|
|
1471
1460
|
const file = join(xdg, 'agent-workflow', 'bridge-settings.conf');
|
|
1472
1461
|
writeFileSync(file, 'CODEX_SERVICE_TIER=priority\n');
|
|
1473
1462
|
chmodSync(file, 0o000);
|
|
1474
|
-
const r = run(sb, { env: { XDG_CONFIG_HOME: xdg } });
|
|
1463
|
+
const r = await run(sb, { env: { XDG_CONFIG_HOME: xdg } });
|
|
1475
1464
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1476
1465
|
assert.equal(r.status, 0, r.stderr);
|
|
1477
1466
|
assert.match(r.stderr, /unreadable/);
|
|
1478
1467
|
assert.doesNotMatch(r.argv, /service_tier/, 'an unreadable file must yield built-in defaults');
|
|
1479
1468
|
});
|
|
1480
1469
|
|
|
1481
|
-
it('a settings line can NEVER execute code (command-substitution payload inert)', () => {
|
|
1470
|
+
it('a settings line can NEVER execute code (command-substitution payload inert)', async () => {
|
|
1482
1471
|
const sb = makeSandbox();
|
|
1483
1472
|
const pwned = join(sb.repo, 'pwned');
|
|
1484
1473
|
const pwned2 = join(sb.repo, 'pwned2');
|
|
@@ -1486,7 +1475,7 @@ describe('codex-review.sh — bridge settings file semantics (bridges 2.3.0)', (
|
|
|
1486
1475
|
sb,
|
|
1487
1476
|
`CODEX_SERVICE_TIER=$(touch ${pwned})\nEVIL_KEY=\`touch ${pwned2}\`\n`,
|
|
1488
1477
|
);
|
|
1489
|
-
const r = run(sb);
|
|
1478
|
+
const r = await run(sb);
|
|
1490
1479
|
const executed = existsSync(pwned) || existsSync(pwned2);
|
|
1491
1480
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1492
1481
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1494,13 +1483,13 @@ describe('codex-review.sh — bridge settings file semantics (bridges 2.3.0)', (
|
|
|
1494
1483
|
assert.doesNotMatch(r.argv, /service_tier/, 'the payload value must fail validation');
|
|
1495
1484
|
});
|
|
1496
1485
|
|
|
1497
|
-
it('a DIRECTORY at the settings path warns loudly and falls back to built-ins (no crash)', () => {
|
|
1486
|
+
it('a DIRECTORY at the settings path warns loudly and falls back to built-ins (no crash)', async () => {
|
|
1498
1487
|
// Outside the repo (XDG) — an unreadable path INSIDE the work tree would fail the
|
|
1499
1488
|
// review-payload assembly itself (pre-existing behaviour, unrelated to the reader).
|
|
1500
1489
|
const sb = makeSandbox();
|
|
1501
1490
|
const xdg = join(sb.root, 'xdg');
|
|
1502
1491
|
mkdirSync(join(xdg, 'agent-workflow', 'bridge-settings.conf'), { recursive: true });
|
|
1503
|
-
const r = run(sb, { env: { XDG_CONFIG_HOME: xdg } });
|
|
1492
|
+
const r = await run(sb, { env: { XDG_CONFIG_HOME: xdg } });
|
|
1504
1493
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1505
1494
|
assert.equal(r.status, 0, `a directory must degrade honestly, not kill the run: ${r.stderr}`);
|
|
1506
1495
|
assert.match(r.stderr, /unreadable or not a regular file/);
|
|
@@ -1514,8 +1503,8 @@ const SIBLING_MANIFEST = JSON.parse(readFileSync(join(HERE, '..', '..', 'antigra
|
|
|
1514
1503
|
const ALL_SETTINGS = [...(MANIFEST.settings ?? []), ...(SIBLING_MANIFEST.settings ?? [])];
|
|
1515
1504
|
const SETTINGS_CMD = 'codex-review';
|
|
1516
1505
|
|
|
1517
|
-
describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)', () => {
|
|
1518
|
-
it('--help Settings section keys set-EQUAL the manifest appliesTo subset', () => {
|
|
1506
|
+
describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)', { concurrency: 2 }, () => {
|
|
1507
|
+
it('--help Settings section keys set-EQUAL the manifest appliesTo subset', async () => {
|
|
1519
1508
|
const help = runHelp('--help').stdout;
|
|
1520
1509
|
const section = helpSection(help, SETTINGS_HEADER);
|
|
1521
1510
|
const got = section.filter((l) => /^[A-Z][A-Z0-9_]+ —/.test(l)).map((l) => l.split(' ')[0]);
|
|
@@ -1527,14 +1516,14 @@ describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned
|
|
|
1527
1516
|
|
|
1528
1517
|
const source = readFileSync(WRAPPER, 'utf8');
|
|
1529
1518
|
|
|
1530
|
-
it('aw_settings_known carries exactly the UNION of both bridges settings keys', () => {
|
|
1519
|
+
it('aw_settings_known carries exactly the UNION of both bridges settings keys', async () => {
|
|
1531
1520
|
const m = source.match(/aw_settings_known\(\) \{\n case " ([^"]+) " in/);
|
|
1532
1521
|
assert.ok(m, 'aw_settings_known registry case not found');
|
|
1533
1522
|
assert.ok(ALL_SETTINGS.length >= 5, 'both manifests must contribute settings');
|
|
1534
1523
|
setEq(m[1].trim().split(/\s+/), ALL_SETTINGS.map((s) => s.key), 'shell registry ⟷ manifest union');
|
|
1535
1524
|
});
|
|
1536
1525
|
|
|
1537
|
-
it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', () => {
|
|
1526
|
+
it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', async () => {
|
|
1538
1527
|
const m = source.match(/^AW_SETTINGS_APPLIED="([^"]*)"$/m);
|
|
1539
1528
|
assert.ok(m, 'AW_SETTINGS_APPLIED not found');
|
|
1540
1529
|
const want = ALL_SETTINGS.filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
|
|
@@ -1542,7 +1531,7 @@ describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned
|
|
|
1542
1531
|
setEq(m[1].trim().split(/\s+/), want, 'applied subset ⟷ manifest appliesTo');
|
|
1543
1532
|
});
|
|
1544
1533
|
|
|
1545
|
-
it('aw_settings_valid arms carry the manifest typed constants per key', () => {
|
|
1534
|
+
it('aw_settings_valid arms carry the manifest typed constants per key', async () => {
|
|
1546
1535
|
const body = source.match(/aw_settings_valid\(\) \{[\s\S]*?\n\}/);
|
|
1547
1536
|
assert.ok(body, 'aw_settings_valid not found');
|
|
1548
1537
|
const armKeys = [...body[0].matchAll(/^ ([A-Z][A-Z0-9_]*)\)/gm)].map((x) => x[1]);
|
|
@@ -1566,10 +1555,10 @@ describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned
|
|
|
1566
1555
|
});
|
|
1567
1556
|
|
|
1568
1557
|
// ── strip-the-kit Phase 4: wrapper honesty (D4) + dispatch-posture labeling (D5) ────────────────
|
|
1569
|
-
describe('codex-review.sh — wrapper honesty: a verdict-less run is a FAILED review (D4)', () => {
|
|
1570
|
-
it('a VERDICT-LESS final message: non-zero exit, NO receipt, the stated re-run recovery', () => {
|
|
1558
|
+
describe('codex-review.sh — wrapper honesty: a verdict-less run is a FAILED review (D4)', { concurrency: 2 }, () => {
|
|
1559
|
+
it('a VERDICT-LESS final message: non-zero exit, NO receipt, the stated re-run recovery', async () => {
|
|
1571
1560
|
const sb = makeSandbox();
|
|
1572
|
-
const r = run(sb, { env: { CODEX_FAKE_FINAL: 'prose without the mandated verdict line' } });
|
|
1561
|
+
const r = await run(sb, { env: { CODEX_FAKE_FINAL: 'prose without the mandated verdict line' } });
|
|
1573
1562
|
const receipts = readReceipts(sb.repo);
|
|
1574
1563
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1575
1564
|
assert.notEqual(r.status, 0, 'a verdict-less review never exits 0');
|
|
@@ -1578,18 +1567,18 @@ describe('codex-review.sh — wrapper honesty: a verdict-less run is a FAILED re
|
|
|
1578
1567
|
assert.match(r.stderr, /re-run/i, 'documented as a failed review — re-run, never fatal');
|
|
1579
1568
|
});
|
|
1580
1569
|
|
|
1581
|
-
it('an EMPTY final message is the same failed run (non-zero, no receipt)', () => {
|
|
1570
|
+
it('an EMPTY final message is the same failed run (non-zero, no receipt)', async () => {
|
|
1582
1571
|
const sb = makeSandbox();
|
|
1583
|
-
const r = run(sb, { env: { CODEX_FAKE_FINAL: '' } });
|
|
1572
|
+
const r = await run(sb, { env: { CODEX_FAKE_FINAL: '' } });
|
|
1584
1573
|
const receipts = readReceipts(sb.repo);
|
|
1585
1574
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1586
1575
|
assert.notEqual(r.status, 0);
|
|
1587
1576
|
assert.equal(receipts.length, 0);
|
|
1588
1577
|
});
|
|
1589
1578
|
|
|
1590
|
-
it('a MISSING final-message file is the same failed run', () => {
|
|
1579
|
+
it('a MISSING final-message file is the same failed run', async () => {
|
|
1591
1580
|
const sb = makeSandbox();
|
|
1592
|
-
const r = run(sb, { env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
1581
|
+
const r = await run(sb, { env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
1593
1582
|
const receipts = readReceipts(sb.repo);
|
|
1594
1583
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1595
1584
|
assert.notEqual(r.status, 0);
|
|
@@ -1597,10 +1586,10 @@ describe('codex-review.sh — wrapper honesty: a verdict-less run is a FAILED re
|
|
|
1597
1586
|
});
|
|
1598
1587
|
});
|
|
1599
1588
|
|
|
1600
|
-
describe('codex-review.sh — dispatch-posture labeling (D5)', () => {
|
|
1601
|
-
it('ONE banner line carries the ACTUAL {model, effort, tier} and the receipt carries the SAME posture', () => {
|
|
1589
|
+
describe('codex-review.sh — dispatch-posture labeling (D5)', { concurrency: 2 }, () => {
|
|
1590
|
+
it('ONE banner line carries the ACTUAL {model, effort, tier} and the receipt carries the SAME posture', async () => {
|
|
1602
1591
|
const sb = makeSandbox();
|
|
1603
|
-
const r = run(sb, {});
|
|
1592
|
+
const r = await run(sb, {});
|
|
1604
1593
|
const receipts = readReceipts(sb.repo);
|
|
1605
1594
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1606
1595
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1609,9 +1598,9 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
1609
1598
|
assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
|
|
1610
1599
|
});
|
|
1611
1600
|
|
|
1612
|
-
it('an ARMED Fast tier rides both surfaces (banner tier=priority; receipt tier "priority")', () => {
|
|
1601
|
+
it('an ARMED Fast tier rides both surfaces (banner tier=priority; receipt tier "priority")', async () => {
|
|
1613
1602
|
const sb = makeSandbox();
|
|
1614
|
-
const r = run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
1603
|
+
const r = await run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
1615
1604
|
const receipts = readReceipts(sb.repo);
|
|
1616
1605
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1617
1606
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1619,10 +1608,10 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
1619
1608
|
assert.equal(receipts[0].posture.tier, 'priority');
|
|
1620
1609
|
});
|
|
1621
1610
|
|
|
1622
|
-
it('a HOSTILE model string (quotes + backslash) rides the receipt strictly JSON-encoded (probe lane)', () => {
|
|
1611
|
+
it('a HOSTILE model string (quotes + backslash) rides the receipt strictly JSON-encoded (probe lane)', async () => {
|
|
1623
1612
|
const hostile = 'we"ird \\ mo"del';
|
|
1624
1613
|
const sb = makeSandbox();
|
|
1625
|
-
const r = run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: hostile } });
|
|
1614
|
+
const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: hostile } });
|
|
1626
1615
|
const receipts = readReceipts(sb.repo); // JSON.parse throwing here IS the encoding failure
|
|
1627
1616
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1628
1617
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1630,9 +1619,9 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
1630
1619
|
assert.equal(receipts[0].probe, true, 'an off-pinned model runs only on the probe lane');
|
|
1631
1620
|
});
|
|
1632
1621
|
|
|
1633
|
-
it('a posture value carrying CONTROL BYTES refuses pre-spend, BEFORE the
|
|
1622
|
+
it('a posture value carrying CONTROL BYTES refuses pre-spend, BEFORE the pinned-model guard', async () => {
|
|
1634
1623
|
const sb = makeSandbox();
|
|
1635
|
-
const r = run(sb, { env: { CODEX_MODEL: `gpt-5.6-sol${String.fromCharCode(1)}` } });
|
|
1624
|
+
const r = await run(sb, { env: { CODEX_MODEL: `gpt-5.6-sol${String.fromCharCode(1)}` } });
|
|
1636
1625
|
const receipts = readReceipts(sb.repo);
|
|
1637
1626
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1638
1627
|
assert.notEqual(r.status, 0);
|
|
@@ -1641,9 +1630,9 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
1641
1630
|
assert.match(r.stderr, /control/i, 'named as the control-byte class, not a policy refusal');
|
|
1642
1631
|
});
|
|
1643
1632
|
|
|
1644
|
-
it('the banner appends the RESOLVED hard timeout — banner-only, never in the receipt (AD-061)', () => {
|
|
1633
|
+
it('the banner appends the RESOLVED hard timeout — banner-only, never in the receipt (AD-061)', async () => {
|
|
1645
1634
|
const sb = makeSandbox();
|
|
1646
|
-
const r = run(sb, {});
|
|
1635
|
+
const r = await run(sb, {});
|
|
1647
1636
|
const receipts = readReceipts(sb.repo);
|
|
1648
1637
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1649
1638
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1651,18 +1640,18 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
1651
1640
|
assert.deepEqual(Object.keys(receipts[0].posture), ['model', 'effort', 'tier'], 'timeout never enters the receipt posture');
|
|
1652
1641
|
});
|
|
1653
1642
|
|
|
1654
|
-
it('an INVALID effective CODEX_HARD_TIMEOUT (env — the closed aw_settings_valid bypass) warns and the banner prints the default', () => {
|
|
1643
|
+
it('an INVALID effective CODEX_HARD_TIMEOUT (env — the closed aw_settings_valid bypass) warns and the banner prints the default', async () => {
|
|
1655
1644
|
const sb = makeSandbox();
|
|
1656
|
-
const r = run(sb, { env: { CODEX_HARD_TIMEOUT: 'nonsense' } });
|
|
1645
|
+
const r = await run(sb, { env: { CODEX_HARD_TIMEOUT: 'nonsense' } });
|
|
1657
1646
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1658
1647
|
assert.equal(r.status, 0, r.stderr);
|
|
1659
1648
|
assert.match(r.stderr, /invalid value 'nonsense' for CODEX_HARD_TIMEOUT/, 'the fallback is loud');
|
|
1660
1649
|
assert.match(r.stderr, /^review posture: .* timeout=1800s$/m, 'the banner prints the built-in default');
|
|
1661
1650
|
});
|
|
1662
1651
|
|
|
1663
|
-
it('a CODEX_HARD_TIMEOUT carrying CONTROL BYTES refuses pre-spend (the banner-field screen)', () => {
|
|
1652
|
+
it('a CODEX_HARD_TIMEOUT carrying CONTROL BYTES refuses pre-spend (the banner-field screen)', async () => {
|
|
1664
1653
|
const sb = makeSandbox();
|
|
1665
|
-
const r = run(sb, { env: { CODEX_HARD_TIMEOUT: `1800${String.fromCharCode(1)}` } });
|
|
1654
|
+
const r = await run(sb, { env: { CODEX_HARD_TIMEOUT: `1800${String.fromCharCode(1)}` } });
|
|
1666
1655
|
const receipts = readReceipts(sb.repo);
|
|
1667
1656
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1668
1657
|
assert.notEqual(r.status, 0);
|
|
@@ -1671,9 +1660,9 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
1671
1660
|
assert.match(r.stderr, /control/i);
|
|
1672
1661
|
});
|
|
1673
1662
|
|
|
1674
|
-
it('a DEL (0x7f) byte in a banner field refuses pre-spend like the C0 range', () => {
|
|
1663
|
+
it('a DEL (0x7f) byte in a banner field refuses pre-spend like the C0 range', async () => {
|
|
1675
1664
|
const sb = makeSandbox();
|
|
1676
|
-
const r = run(sb, { env: { CODEX_MODEL: `gpt-5.6-sol${String.fromCharCode(127)}` } });
|
|
1665
|
+
const r = await run(sb, { env: { CODEX_MODEL: `gpt-5.6-sol${String.fromCharCode(127)}` } });
|
|
1677
1666
|
const receipts = readReceipts(sb.repo);
|
|
1678
1667
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1679
1668
|
assert.notEqual(r.status, 0);
|