Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<div align="center">

**English** · [简体中文](docs/readme/README_zh.md) · [繁體中文](docs/readme/README_zh-TW.md) · [日本語](docs/readme/README_ja.md) · [한국어](docs/readme/README_ko.md) · [Türkçe](docs/readme/README_tr.md) · [Русский](docs/readme/README_ru.md) · [Tiếng Việt](docs/readme/README_vi.md) · [ไทย](docs/readme/README_th.md) · [Deutsch](docs/readme/README_de.md) · [Español](docs/readme/README_es.md) · [Français](docs/readme/README_fr.md) · [Українська](docs/readme/README_uk.md) · [Polski](docs/readme/README_pl.md) · [Português (Brasil)](docs/readme/README_pt-BR.md) · [العربية](docs/readme/README_ar.md) · [فارسی](docs/readme/README_fa.md) · [Bahasa Indonesia](docs/readme/README_id.md) · [Italiano](docs/readme/README_it.md)
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md) · [Roman Urdu](docs/readme/README_ur-Latn.md) · [Română](docs/readme/README_ro.md)
**English**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 README translations lose their links

This removes every language link from the main README, although the translated READMEs still exist. Readers arriving at the repository can no longer choose their language from its landing page. Restore the language switcher; this removal is unrelated to adding evaluation clients.

Prompt To Fix With AI
This is a comment left during a code review.
Path: README.md
Line: 3

Comment:
**README translations lose their links**

This removes every language link from the main README, although the translated READMEs still exist. Readers arriving at the repository can no longer choose their language from its landing page. Restore the language switcher; this removal is unrelated to adding evaluation clients.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.


# REA: Reverse Engineer Anything

Expand Down
27 changes: 15 additions & 12 deletions docs/client-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ Chrome CI include this verifier. See [module relationships](website-module-trace
## Agent evaluation and conformance records

Evaluate native, JavaScript, managed and browser investigation tasks through a
real local Codex CLI with:
real local agent CLI with:

```bash
npm run verify:agent
Expand All @@ -486,11 +486,11 @@ are development verification code under `tests/evaluation/model-evals/`. The
source-owned evaluator runs alongside the compiled REA tools; it is excluded
from the shipped runtime package.

The lane runs scenarios sequentially in standalone Codex (`--no-daemon`) with a
disposable HOME and CODEX_HOME under `~/.cache/rea-agent-evaluations/`. Set
`REA_AGENT_EVAL_ROOT` to choose another parent directory outside the OS temporary
directory: current Codex refuses helper aliases under `/tmp`, preventing shell
commands and skill reads even if MCP works. Each owned run directory is removed
The lane runs scenarios sequentially in a disposable client home under
`~/.cache/rea-agent-evaluations/`. Codex remains the default; set
`REA_AGENT_EVAL_CLIENT=claude-code` or `REA_AGENT_EVAL_CLIENT=gemini-cli` to
select Claude Code or Gemini CLI. Set `REA_AGENT_EVAL_ROOT` to choose another
parent directory outside the OS temporary directory. Each owned run directory is removed
after verification unless fixture retention is requested. Commands share the
owned-process supervisor: native ownership preparation has its own bounded
deadline before each short command budget, and timeouts join bounded process-tree
Expand All @@ -502,11 +502,12 @@ exceeding these diagnostic budgets fails explicitly. Scenario JSON events stream
to the evaluator without retaining a second raw stdout copy. Cleanup attempts the
remaining run tree even if auth removal fails, and reports unresolved cleanup
together with the original verification failure. The lane
installs the packaged skill and MCP registration through public `rea setup`,
installs the selected client's packaged skill and MCP registration through public `rea setup`,
rather than copying a skill into a project that also inherits the caller's
skills and configuration. Only authentication is copied, when present, and it
is removed after confirmed process cleanup even with
`REA_AGENT_EVAL_KEEP_FIXTURES=true`. No user configuration,
skills and configuration. Only Codex's authentication file is copied, when
present; API-key authentication for Claude Code and Gemini CLI remains in the
caller environment. Copied authentication is removed after confirmed process
cleanup even with `REA_AGENT_EVAL_KEEP_FIXTURES=true`. No user configuration,
rules, plugins or history is copied; shell snapshots and additional agents are
disabled. Existing API-key environment authentication remains available.
The native provider scenario preapproves only `open_binary` and
Expand All @@ -525,7 +526,8 @@ scenario IDs. `asar-zh` exercises the packaged desktop rubric from a Chinese
request. `javascript-module-view` exercises summary analysis, module paging and
item inspection in a tree with unrelated modules; its routing and text checks
remain heuristic, so review the retained transcript for selector choice and
coverage claims. Set `REA_CODEX_CLI` to an installed Codex executable and
coverage claims. Set `REA_CODEX_CLI`, `REA_AGENT_EVAL_CLAUDE_COMMAND`, or
`REA_AGENT_EVAL_GEMINI_COMMAND` to the selected executable and
`REA_AGENT_EVAL_MODEL` to an account-supported model. Use an external resource
limit for the whole process tree when testing on a constrained host; a Node
heap limit alone does not bound client and child-process memory.
Expand Down Expand Up @@ -593,7 +595,8 @@ presence. These metrics can still accept fabricated prose and must not be read
as factual assessments. Set `REA_AGENT_EVAL_TRANSCRIPT_DIR` to retain complete
tool results and final answers for review.

Report schema version 4 adds negative routing scenarios with nullable
Report schema version 4 adds the top-level `client` and `clientVersion` fields
for cross-client comparison; Codex remains the default. It also adds negative routing scenarios with nullable
`expectedFirstTool` and a `targetClarificationPassed` outcome. Consumers must
allow an expected absence of REA calls; this does not make an analysis scenario
pass without its required tools. Native provider records additionally report
Expand Down
9 changes: 6 additions & 3 deletions docs/testing-lanes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1221,9 +1221,12 @@ package entrypoint as the script's first argument for package coverage.
## Agent evaluation and conformance records

`npm run verify:agent` evaluates investigation scenarios through a real local
Codex CLI. The lane runs sequentially; select scenarios with
`REA_AGENT_EVAL_SCENARIOS` and choose the installed client/model with
`REA_CODEX_CLI` and `REA_AGENT_EVAL_MODEL`. The detailed
agent CLI. Codex is the default; set `REA_AGENT_EVAL_CLIENT=claude-code` or
`REA_AGENT_EVAL_CLIENT=gemini-cli` to run the same scenarios and grading against
Claude Code or Gemini CLI. The lane runs sequentially; select scenarios with
`REA_AGENT_EVAL_SCENARIOS`, commands with
`REA_CODEX_CLI`/`REA_AGENT_EVAL_CLAUDE_COMMAND`/`REA_AGENT_EVAL_GEMINI_COMMAND`,
and the model with `REA_AGENT_EVAL_MODEL`. The detailed
[agent evaluation reference](client-verification.md#agent-evaluation-and-conformance-records)
contains isolation, authentication cleanup, grading, and conformance boundaries.

Expand Down
195 changes: 143 additions & 52 deletions scripts/lib/agent-evaluation-commands.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { retainOwnedCommandCleanup } from "../../dist/providers/OwnedCommandClea
/** Keep command ownership through failure and join cleanup before removing a run. */
export function createAgentEvaluationCommands({
codex,
claude = process.env.REA_AGENT_EVAL_CLAUDE_COMMAND ?? "claude",
gemini = process.env.REA_AGENT_EVAL_GEMINI_COMMAND ?? "gemini",
evaluationRoot,
evaluationEnvironment,
optionalModel,
Expand All @@ -32,6 +34,10 @@ export function createAgentEvaluationCommands({
run: (work) => resources.run(work),
};
const diagnosticBytes = 32_768;
const launchGemini = (arguments_) =>
/\.(?:c|m)?js$/iu.test(gemini)
? [process.execPath, [gemini, ...arguments_]]
: [gemini, arguments_];
Comment on lines +37 to +40

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Relative Gemini paths break

A relative JavaScript path in REA_AGENT_EVAL_GEMINI_COMMAND resolves from two different folders. readAgentVersion runs from repositoryRoot, but runGemini runs from evaluationRoot. A path such as node_modules/@google/gemini-cli/bundle/gemini.js can pass the version check and then fail with a missing-module error for every scenario.

Resolve the selected entry point once before changing the working directory, as verify-gemini-client.mjs already does.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/lib/agent-evaluation-commands.mjs
Line: 37-40

Comment:
**Relative Gemini paths break**

A relative JavaScript path in `REA_AGENT_EVAL_GEMINI_COMMAND` resolves from two different folders. `readAgentVersion` runs from `repositoryRoot`, but `runGemini` runs from `evaluationRoot`. A path such as `node_modules/@google/gemini-cli/bundle/gemini.js` can pass the version check and then fail with a missing-module error for every scenario.

Resolve the selected entry point once before changing the working directory, as `verify-gemini-client.mjs` already does.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.


async function execute(
command,
Expand Down Expand Up @@ -73,6 +79,64 @@ export function createAgentEvaluationCommands({
});
}

async function executeJsonLines(
command,
arguments_,
cwd,
env,
timeoutMessage,
) {
const events = [];
const nonJson = [];
let nonJsonBytes = 0;
const stdout = new PassThrough();
const lines = createInterface({ input: stdout });
const linesClosed = new Promise((resolve) =>
lines.once("close", resolve),
);
let lineError;
lines.on("error", (cause) => {
lineError = cause;
lines.close();
});
lines.on("line", (line) => {
try {
events.push(JSON.parse(line));
} catch {
if (nonJsonBytes >= diagnosticBytes) return;
const bytes = Buffer.from(
`non-json stdout: ${line.slice(0, diagnosticBytes - nonJsonBytes)}`,
);
const retained = bytes.subarray(0, diagnosticBytes - nonJsonBytes);
if (retained.length > 0) nonJson.push(retained);
nonJsonBytes += retained.length;
}
});
try {
const result = await execute(
command,
arguments_,
cwd,
env,
{ timeoutMs, stdout },
timeoutMessage,
);
await linesClosed;
if (lineError !== undefined) throw lineError;
return {
events,
exitCode: result.exitCode ?? 1,
stderr: Buffer.concat([...nonJson, Buffer.from(result.stderr.text)])
.subarray(0, diagnosticBytes)
.toString("utf8"),
};
} finally {
stdout.destroy();
lines.close();
await linesClosed;
}
}

return {
async runCodex(prompt, approvedTools) {
const arguments_ = [
Expand All @@ -96,57 +160,61 @@ export function createAgentEvaluationCommands({
...(optionalModel === undefined ? [] : ["--model", optionalModel]),
prompt,
];
const events = [];
const nonJson = [];
let nonJsonBytes = 0;
const stdout = new PassThrough();
const lines = createInterface({ input: stdout });
const linesClosed = new Promise((resolve) =>
lines.once("close", resolve),
return executeJsonLines(
codex,
arguments_,
evaluationRoot,
evaluationEnvironment,
`Codex evaluation timed out after ${String(timeoutMs)}ms`,
);
},

async runClaude(prompt) {
return executeJsonLines(
claude,
[
"--print",
"--output-format",
"stream-json",
"--verbose",
"--no-session-persistence",
"--permission-mode",
"dontAsk",
"--settings",
JSON.stringify({
permissions: {
allow: ["Skill", "WaitForMcpServers", "mcp__rea__*"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Evaluations can change host files

runClaude and runGemini ignore scenario.approvedTools and approve every REA tool. This includes capture_process_scenario, which runs a chosen command with the user's permissions. If an agent tries to execute a fixture or repair a failed analysis, it can change files outside evaluationRoot without asking. Changing HOME does not prevent this.

Use one shared safe-tool policy with explicit scenario exceptions, and restrict Gemini's shell and write tools.

How this was verified: Both launchers approve the full REA catalog, and capture_process_scenario forwards the chosen command to the host without restricting it to the evaluation folder.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/lib/agent-evaluation-commands.mjs
Line: 186

Comment:
**Evaluations can change host files**

`runClaude` and `runGemini` ignore `scenario.approvedTools` and approve every REA tool. This includes `capture_process_scenario`, which runs a chosen command with the user's permissions. If an agent tries to execute a fixture or repair a failed analysis, it can change files outside `evaluationRoot` without asking. Changing `HOME` does not prevent this.

Use one shared safe-tool policy with explicit scenario exceptions, and restrict Gemini's shell and write tools.

> **How this was verified:** Both launchers approve the full REA catalog, and `capture_process_scenario` forwards the chosen command to the host without restricting it to the evaluation folder.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

},
}),
...(optionalModel === undefined ? [] : ["--model", optionalModel]),
"--",
prompt,
],
evaluationRoot,
evaluationEnvironment,
`Claude Code evaluation timed out after ${String(timeoutMs)}ms`,
);
},

async runGemini(prompt) {
const [command, arguments_] = launchGemini([
"--approval-mode",
"yolo",
"--allowed-mcp-server-names",
"rea",
"--output-format",
"stream-json",
...(optionalModel === undefined ? [] : ["--model", optionalModel]),
"-p",
prompt,
]);
return executeJsonLines(
command,
arguments_,
evaluationRoot,
evaluationEnvironment,
`Gemini CLI evaluation timed out after ${String(timeoutMs)}ms`,
);
let lineError;
lines.on("error", (cause) => {
// The owned pipeline reports stream failure or cancellation. Join this
// consumer too, without turning its forwarded error into a process crash.
lineError = cause;
lines.close();
});
lines.on("line", (line) => {
try {
events.push(JSON.parse(line));
} catch {
if (nonJsonBytes >= diagnosticBytes) return;
const bytes = Buffer.from(
`non-json stdout: ${line.slice(0, diagnosticBytes - nonJsonBytes)}`,
);
const retained = bytes.subarray(0, diagnosticBytes - nonJsonBytes);
if (retained.length > 0) nonJson.push(retained);
nonJsonBytes += retained.length;
}
});
try {
const result = await execute(
codex,
arguments_,
evaluationRoot,
evaluationEnvironment,
{ timeoutMs, stdout },
`Codex evaluation timed out after ${String(timeoutMs)}ms`,
);
await linesClosed;
if (lineError !== undefined) throw lineError;
return {
events,
exitCode: result.exitCode ?? 1,
stderr: Buffer.concat([...nonJson, Buffer.from(result.stderr.text)])
.subarray(0, diagnosticBytes)
.toString("utf8"),
};
} finally {
stdout.destroy();
lines.close();
await linesClosed;
}
},

async runProcess(command, arguments_, cwd, timeout, env = process.env) {
Expand All @@ -167,12 +235,12 @@ export function createAgentEvaluationCommands({
);
},

async readCommandVersion(command) {
async readCommandVersion(command, env = process.env) {
const result = await execute(
command,
["--version"],
repositoryRoot,
process.env,
env,
{ timeoutMs: 10_000, diagnosticBytes: 8192 },
`${command} --version timed out`,
);
Expand All @@ -186,6 +254,29 @@ export function createAgentEvaluationCommands({
return version;
},

async readAgentVersion(client, env = evaluationEnvironment) {
const [command, arguments_] =
client === "gemini-cli"
? launchGemini(["--version"])
: [client === "claude-code" ? claude : codex, ["--version"]];
const result = await execute(
command,
arguments_,
repositoryRoot,
env,
{ timeoutMs: 10_000, diagnosticBytes: 8192 },
`${client} --version timed out`,
Comment on lines +257 to +268

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Version checks have two owners

readAgentVersion duplicates the execution, timeout, exit-code, and empty-output checks in readCommandVersion. The runner now uses the new function, while the existing boundary tests still exercise only the old one. Fixes can drift between the two copies without those tests catching it.

AGENTS.md requires removing superseded representations after migrating their consumers. Before merging, keep one version-reading implementation and move the existing checks onto the path the runner actually uses.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/lib/agent-evaluation-commands.mjs
Line: 257-268

Comment:
**Version checks have two owners**

`readAgentVersion` duplicates the execution, timeout, exit-code, and empty-output checks in `readCommandVersion`. The runner now uses the new function, while the existing boundary tests still exercise only the old one. Fixes can drift between the two copies without those tests catching it.

`AGENTS.md` requires removing superseded representations after migrating their consumers. Before merging, keep one version-reading implementation and move the existing checks onto the path the runner actually uses.

**Context Used:** AGENTS.md ([source](https://github.com/morluto/rea/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

);
if (result.exitCode !== 0)
throw new Error(
`${client} --version failed with ${String(result.exitCode)}: ${result.stderr.text}`,
);
const version = result.stdout.text.trim();
if (version.length === 0)
throw new Error(`${client} --version returned empty output`);
return version;
},

close: () => resources.close(),
};
}
Loading
Loading