diff --git a/docs/skills/ce-code-review.md b/docs/skills/ce-code-review.md index da2abdc74..c76a5b5a1 100644 --- a/docs/skills/ce-code-review.md +++ b/docs/skills/ce-code-review.md @@ -116,7 +116,7 @@ Compound-engineering pipeline artifacts (`docs/brainstorms/*`, `docs/plans/*.md` You invoke `/ce-code-review` on a feature branch with a Rails auth change that includes a database migration. -The skill detects you're on a feature branch (no PR yet), resolves the base via `scripts/resolve-base.sh`, and computes the diff. Stage 2 reads commit messages and writes a 2-3 line intent summary. Stage 2b auto-discovers the plan in `docs/plans/` from the branch name and reads its Requirements (R1-R8, U1-U6). +The skill detects you're on a feature branch (no PR yet), resolves the base from `origin/HEAD` (or PR metadata when an open PR exists), and computes the diff. Stage 2 reads commit messages and writes a 2-3 line intent summary. Stage 2b auto-discovers the plan in `docs/plans/` from the branch name and reads its Requirements (R1-R8, U1-U6). Stage 3 selects reviewers: the 6 always-on, plus security (auth touched), reliability (background job for token cleanup), data migrations (migration file present), kieran-rails + dhh-rails (stack), schema-drift detector and deployment-verification agent (CE migration conditionals). Ten reviewers total, dispatched in parallel. @@ -175,7 +175,7 @@ Concurrent use note: `mode:report-only` is the only mode safe to run alongside b | Argument | Effect | |----------|--------| -| _(empty)_ | Reviews current branch (uses `scripts/resolve-base.sh` to detect base) | +| _(empty)_ | Reviews current branch (detects base from `origin/HEAD` or PR metadata) | | `` | Reviews that PR (checks out, fetches metadata, reviews against PR base) | | `` | Checks out and reviews against detected base | | `base:` | Skips scope detection; reviews current checkout against that ref | diff --git a/plugins/compound-engineering/skills/ce-code-review/SKILL.md b/plugins/compound-engineering/skills/ce-code-review/SKILL.md index 14b78405d..bc7780420 100644 --- a/plugins/compound-engineering/skills/ce-code-review/SKILL.md +++ b/plugins/compound-engineering/skills/ce-code-review/SKILL.md @@ -292,15 +292,13 @@ If the output is non-empty, inform the user: "You have uncommitted changes on th git checkout ``` -Then detect the review base branch and compute the merge-base. Run the `scripts/resolve-base.sh` script, which handles fork-safe remote resolution with multi-fallback detection (PR metadata -> `origin/HEAD` -> `gh repo view` -> common branch names): +Then detect the review base branch and compute the merge-base. -``` -RESOLVE_OUT=$(bash scripts/resolve-base.sh) || { echo "ERROR: resolve-base.sh failed"; exit 1; } -if [ -z "$RESOLVE_OUT" ] || echo "$RESOLVE_OUT" | grep -q '^ERROR:'; then echo "${RESOLVE_OUT:-ERROR: resolve-base.sh produced no output}"; exit 1; fi -BASE=$(echo "$RESOLVE_OUT" | sed 's/^BASE://') -``` +**If a PR exists for ``** (check with `gh pr view --json baseRefName,url`): reuse PR mode's `PR_BASE_REMOTE` block above. Use `baseRefName` as `` and derive `` from the PR URL (e.g., `EveryInc/foo` from `https://github.com/EveryInc/foo/pull/123`). The block already sets `$BASE` to the merge-base SHA — `origin` may point at the user's fork, which is why naive `origin/` is unsafe and the fork-safe block is required. + +**If no PR exists**: derive the default branch. Primary source is `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's#^origin/##'`; fall back to `gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'`, then to the first of `main`/`master`/`develop`/`trunk` that exists as `origin/` or bare `` locally. Compute `BASE=$(git merge-base HEAD )`, where `` is `origin/` when available, otherwise the bare local `` (covers single-branch clones, missing origin remote, and unfetched defaults). If `BASE` is empty and the clone is shallow (`git rev-parse --is-shallow-repository`), run `git fetch --unshallow origin` and retry. -If the script outputs an error, stop instead of falling back to `git diff HEAD`; a branch review without the base branch would only show uncommitted changes and silently miss all committed work. +If no base can be resolved, **stop**. Do not fall back to `git diff HEAD` — a branch review without the base would only show uncommitted changes and silently miss all committed work. On success, produce the diff: @@ -312,15 +310,9 @@ You may still fetch additional PR metadata with `gh pr view` for title, body, li **If no argument (standalone on current branch):** -Detect the review base branch and compute the merge-base using the same `scripts/resolve-base.sh` script as branch mode: - -``` -RESOLVE_OUT=$(bash scripts/resolve-base.sh) || { echo "ERROR: resolve-base.sh failed"; exit 1; } -if [ -z "$RESOLVE_OUT" ] || echo "$RESOLVE_OUT" | grep -q '^ERROR:'; then echo "${RESOLVE_OUT:-ERROR: resolve-base.sh produced no output}"; exit 1; fi -BASE=$(echo "$RESOLVE_OUT" | sed 's/^BASE://') -``` +Apply the same base-detection logic as branch mode above, using the current branch (i.e., `gh pr view --json baseRefName,url` with no argument defaults to the current branch). -If the script outputs an error, stop instead of falling back to `git diff HEAD`; a standalone review without the base branch would only show uncommitted changes and silently miss all committed work on the branch. +If no base can be resolved, **stop**. Do not fall back to `git diff HEAD` — a standalone review without the base would only show uncommitted changes and silently miss all committed work on the branch. On success, produce the diff: diff --git a/plugins/compound-engineering/skills/ce-code-review/scripts/resolve-base.sh b/plugins/compound-engineering/skills/ce-code-review/scripts/resolve-base.sh deleted file mode 100644 index f5836cf89..000000000 --- a/plugins/compound-engineering/skills/ce-code-review/scripts/resolve-base.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash -# Resolve the review base branch and compute the merge-base for ce-code-review. -# Handles fork-safe remote resolution, PR metadata, and multi-fallback detection. -# -# Usage: bash scripts/resolve-base.sh -# Output: BASE: on success, ERROR: on failure. -# -# Detects the base branch from (in priority order): -# 1. PR metadata (base ref + base repo for fork safety) -# 2. origin/HEAD symbolic ref -# 3. gh repo view defaultBranchRef -# 4. Common branch names: main, master, develop, trunk - -set -euo pipefail - -REVIEW_BASE_BRANCH="" -PR_BASE_REPO="" -PR_BASE_REMOTE="" -BASE_REF="" - -# Step 1: Try PR metadata (handles fork workflows) -if command -v gh >/dev/null 2>&1; then - PR_META=$(gh pr view --json baseRefName,url 2>/dev/null || true) - if [ -n "$PR_META" ]; then - REVIEW_BASE_BRANCH=$(echo "$PR_META" | jq -r '.baseRefName // empty' 2>/dev/null || true) - PR_BASE_REPO=$(echo "$PR_META" | jq -r '.url // empty' 2>/dev/null | sed -n 's#https://github.com/\([^/]*/[^/]*\)/pull/.*#\1#p' || true) - fi -fi - -# Step 2: Fall back to origin/HEAD -if [ -z "$REVIEW_BASE_BRANCH" ]; then - REVIEW_BASE_BRANCH=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##' || true) -fi - -# Step 3: Fall back to gh repo view -if [ -z "$REVIEW_BASE_BRANCH" ] && command -v gh >/dev/null 2>&1; then - REVIEW_BASE_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name' 2>/dev/null || true) -fi - -# Step 4: Fall back to common branch names -if [ -z "$REVIEW_BASE_BRANCH" ]; then - for candidate in main master develop trunk; do - if git rev-parse --verify "origin/$candidate" >/dev/null 2>&1 || git rev-parse --verify "$candidate" >/dev/null 2>&1; then - REVIEW_BASE_BRANCH="$candidate" - break - fi - done -fi - -# Resolve the base ref from the correct remote (fork-safe) -if [ -n "$REVIEW_BASE_BRANCH" ]; then - if [ -n "$PR_BASE_REPO" ]; then - PR_BASE_REMOTE=$(git remote -v | awk "index(\$2, \"github.com:$PR_BASE_REPO\") || index(\$2, \"github.com/$PR_BASE_REPO\") {print \$1; exit}") - if [ -n "$PR_BASE_REMOTE" ]; then - BASE_REF=$(git rev-parse --verify "$PR_BASE_REMOTE/$REVIEW_BASE_BRANCH" 2>/dev/null || true) - if [ -z "$BASE_REF" ]; then - git fetch --no-tags "$PR_BASE_REMOTE" "$REVIEW_BASE_BRANCH:refs/remotes/$PR_BASE_REMOTE/$REVIEW_BASE_BRANCH" 2>/dev/null || git fetch --no-tags "$PR_BASE_REMOTE" "$REVIEW_BASE_BRANCH" 2>/dev/null || true - BASE_REF=$(git rev-parse --verify "$PR_BASE_REMOTE/$REVIEW_BASE_BRANCH" 2>/dev/null || true) - fi - fi - fi - if [ -z "$BASE_REF" ]; then - # Only try origin if it exists as a remote; otherwise skip to avoid - # confusing errors in fork setups where origin points at the user's fork. - if git remote get-url origin >/dev/null 2>&1; then - BASE_REF=$(git rev-parse --verify "origin/$REVIEW_BASE_BRANCH" 2>/dev/null || true) - if [ -z "$BASE_REF" ]; then - git fetch --no-tags origin "$REVIEW_BASE_BRANCH:refs/remotes/origin/$REVIEW_BASE_BRANCH" 2>/dev/null || git fetch --no-tags origin "$REVIEW_BASE_BRANCH" 2>/dev/null || true - BASE_REF=$(git rev-parse --verify "origin/$REVIEW_BASE_BRANCH" 2>/dev/null || true) - fi - fi - # Fall back to a bare local ref only if remote resolution failed - if [ -z "$BASE_REF" ]; then - BASE_REF=$(git rev-parse --verify "$REVIEW_BASE_BRANCH" 2>/dev/null || true) - fi - fi -fi - -# Compute merge-base -if [ -n "$BASE_REF" ]; then - BASE=$(git merge-base HEAD "$BASE_REF" 2>/dev/null) || BASE="" - if [ -z "$BASE" ] && [ "$(git rev-parse --is-shallow-repository 2>/dev/null || echo false)" = "true" ]; then - if git remote get-url origin >/dev/null 2>&1; then - git fetch --no-tags --unshallow origin 2>/dev/null || true - BASE=$(git merge-base HEAD "$BASE_REF" 2>/dev/null) || BASE="" - fi - if [ -z "$BASE" ] && [ -n "$PR_BASE_REMOTE" ] && [ "$PR_BASE_REMOTE" != "origin" ]; then - git fetch --no-tags --unshallow "$PR_BASE_REMOTE" 2>/dev/null || true - BASE=$(git merge-base HEAD "$BASE_REF" 2>/dev/null) || BASE="" - fi - fi -else - BASE="" -fi - -if [ -n "$BASE" ]; then - echo "BASE:$BASE" -else - echo "ERROR:Unable to resolve review base branch locally. Fetch the base branch and rerun, or provide a PR number so the review scope can be determined from PR metadata." -fi diff --git a/tests/resolve-base-script.test.ts b/tests/resolve-base-script.test.ts deleted file mode 100644 index 9efcdfa4d..000000000 --- a/tests/resolve-base-script.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { promises as fs } from "fs" -import os from "os" -import path from "path" -import { pathToFileURL } from "url" - -const gitEnv = { - ...process.env, - GIT_AUTHOR_NAME: "Test", - GIT_AUTHOR_EMAIL: "test@example.com", - GIT_COMMITTER_NAME: "Test", - GIT_COMMITTER_EMAIL: "test@example.com", -} - -const resolveBaseScript = path.join( - import.meta.dir, - "..", - "plugins", - "compound-engineering", - "skills", - "ce-code-review", - "scripts", - "resolve-base.sh", -) - -type RunResult = { - exitCode: number - stderr: string - stdout: string -} - -async function runCommand( - cmd: string[], - cwd: string, - env?: NodeJS.ProcessEnv, -): Promise { - const proc = Bun.spawn(cmd, { - cwd, - env: env ?? process.env, - stderr: "pipe", - stdout: "pipe", - }) - - const [exitCode, stdout, stderr] = await Promise.all([ - proc.exited, - new Response(proc.stdout).text(), - new Response(proc.stderr).text(), - ]) - - return { exitCode, stderr, stdout } -} - -async function runGit(args: string[], cwd: string, env?: NodeJS.ProcessEnv): Promise { - const result = await runCommand(["git", ...args], cwd, env ?? gitEnv) - if (result.exitCode !== 0) { - throw new Error( - `git ${args.join(" ")} failed (exit ${result.exitCode}).\nstdout: ${result.stdout}\nstderr: ${result.stderr}`, - ) - } - - return result.stdout.trim() -} - -async function initRepo(initialBranch = "main"): Promise { - const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), "resolve-base-repo-")) - await runGit(["init", "-b", initialBranch], repoRoot) - return repoRoot -} - -async function commitFile( - repoRoot: string, - relativePath: string, - content: string, - message: string, -): Promise { - const filePath = path.join(repoRoot, relativePath) - await fs.mkdir(path.dirname(filePath), { recursive: true }) - await fs.writeFile(filePath, content) - await runGit(["add", relativePath], repoRoot) - await runGit(["commit", "-m", message], repoRoot) - return runGit(["rev-parse", "HEAD"], repoRoot) -} - -async function writeExecutable(filePath: string, content: string): Promise { - await fs.writeFile(filePath, content) - await fs.chmod(filePath, 0o755) -} - -async function createStubBin(mode: "gh-fails" | "pr-metadata"): Promise { - const binDir = await fs.mkdtemp(path.join(os.tmpdir(), "resolve-base-bin-")) - - if (mode === "gh-fails") { - await writeExecutable(path.join(binDir, "gh"), "#!/usr/bin/env bash\nexit 1\n") - return binDir - } - - await writeExecutable( - path.join(binDir, "gh"), - `#!/usr/bin/env bash -set -euo pipefail -if [ "$#" -ge 2 ] && [ "$1" = "pr" ] && [ "$2" = "view" ]; then - printf '%s' '{"baseRefName":"main","url":"https://github.com/EveryInc/compound-engineering-plugin/pull/123"}' - exit 0 -fi -exit 1 -`, - ) - - await writeExecutable( - path.join(binDir, "jq"), - `#!/usr/bin/env bun -const args = process.argv.slice(2).filter((arg) => arg !== "-r") -const query = args[args.length - 1] ?? "" -const input = await new Response(Bun.stdin.stream()).text() -const data = input.trim() ? JSON.parse(input) : {} - -let output = "" -if (query === ".baseRefName // empty") { - output = data.baseRefName ?? "" -} else if (query === ".url // empty") { - output = data.url ?? "" -} else if (query === ".defaultBranchRef.name") { - output = data.defaultBranchRef?.name ?? "" -} else { - console.error(\`unsupported jq query: \${query}\`) - process.exit(1) -} - -process.stdout.write(String(output)) -`, - ) - - return binDir -} - -async function runResolveBase( - repoRoot: string, - stubBin: string, - extraEnv?: NodeJS.ProcessEnv, -): Promise { - return runCommand(["bash", resolveBaseScript], repoRoot, { - ...gitEnv, - PATH: `${stubBin}:${process.env.PATH ?? ""}`, - ...extraEnv, - }) -} - -describe("resolve-base.sh", () => { - test("prefers the PR base remote from gh metadata over origin", async () => { - const repoRoot = await initRepo() - const initialSha = await commitFile(repoRoot, "history.txt", "a\n", "initial") - const upstreamMainSha = await commitFile(repoRoot, "history.txt", "b\n", "main advance") - - await runGit(["checkout", "-b", "feature"], repoRoot) - await commitFile(repoRoot, "feature.txt", "feature\n", "feature change") - - await runGit(["checkout", "-b", "fork-main", initialSha], repoRoot) - const forkMainSha = await commitFile(repoRoot, "fork.txt", "fork\n", "fork main diverges") - await runGit(["checkout", "feature"], repoRoot) - - await runGit(["remote", "add", "origin", "git@github.com:someone/fork.git"], repoRoot) - await runGit( - ["remote", "add", "upstream", "git@github.com:EveryInc/compound-engineering-plugin.git"], - repoRoot, - ) - await runGit(["update-ref", "refs/remotes/origin/main", forkMainSha], repoRoot) - await runGit(["update-ref", "refs/remotes/upstream/main", upstreamMainSha], repoRoot) - - const stubBin = await createStubBin("pr-metadata") - const result = await runResolveBase(repoRoot, stubBin) - - expect(result.exitCode).toBe(0) - expect(result.stdout.trim()).toBe(`BASE:${upstreamMainSha}`) - }) - - test("falls back to a local base branch when origin is absent", async () => { - const repoRoot = await initRepo() - await commitFile(repoRoot, "history.txt", "a\n", "initial") - const mainSha = await commitFile(repoRoot, "history.txt", "b\n", "main advance") - - await runGit(["checkout", "-b", "feature"], repoRoot) - await commitFile(repoRoot, "feature.txt", "feature\n", "feature change") - - const stubBin = await createStubBin("gh-fails") - const result = await runResolveBase(repoRoot, stubBin) - - expect(result.exitCode).toBe(0) - expect(result.stdout.trim()).toBe(`BASE:${mainSha}`) - }) - - test("resolves against origin/HEAD in a detached shallow checkout", async () => { - const seedRepo = await initRepo() - await commitFile(seedRepo, "history.txt", "a\n", "initial") - const mainSha = await commitFile(seedRepo, "history.txt", "b\n", "main advance") - - await runGit(["checkout", "-b", "feature"], seedRepo) - const featureSha = await commitFile(seedRepo, "feature.txt", "feature\n", "feature change") - await runGit(["checkout", "main"], seedRepo) - - const bareRepo = await fs.mkdtemp(path.join(os.tmpdir(), "resolve-base-remote-")) - await runGit(["init", "--bare", bareRepo], seedRepo) - const bareUrl = pathToFileURL(bareRepo).toString() - await runGit(["remote", "add", "origin", bareUrl], seedRepo) - await runGit(["push", "origin", "main", "feature"], seedRepo) - - const checkoutRoot = await fs.mkdtemp(path.join(os.tmpdir(), "resolve-base-checkout-")) - await runCommand(["git", "clone", "--depth", "1", bareUrl, checkoutRoot], os.tmpdir(), gitEnv) - await runGit(["config", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"], checkoutRoot) - await runGit( - ["fetch", "--depth", "1", "origin", "main:refs/remotes/origin/main", "feature:refs/remotes/origin/feature"], - checkoutRoot, - ) - await runGit(["symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/main"], checkoutRoot) - await runGit(["checkout", "--detach", "origin/feature"], checkoutRoot) - - const originMain = await runGit(["rev-parse", "--verify", "origin/main"], checkoutRoot) - expect(originMain).toBe(mainSha) - - const originFeature = await runGit(["rev-parse", "--verify", "origin/feature"], checkoutRoot) - expect(originFeature).toBe(featureSha) - - const originHead = await runGit( - ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], - checkoutRoot, - ) - expect(originHead).toBe("origin/main") - - const stubBin = await createStubBin("gh-fails") - const result = await runResolveBase(checkoutRoot, stubBin) - - expect(result.exitCode).toBe(0) - expect(result.stdout.trim()).toBe(`BASE:${mainSha}`) - }) - - test("unshallows the PR base remote in a detached shallow checkout", async () => { - const upstreamSeed = await initRepo() - const initialSha = await commitFile(upstreamSeed, "history.txt", "a\n", "initial") - const upstreamMainSha = await commitFile(upstreamSeed, "history.txt", "b\n", "upstream main") - - await runGit(["checkout", "-b", "feature"], upstreamSeed) - const featureSha = await commitFile(upstreamSeed, "feature.txt", "feature\n", "feature change") - - const forkSeed = await initRepo() - await commitFile(forkSeed, "history.txt", "a\n", "initial") - const forkMainSha = await commitFile(forkSeed, "fork.txt", "fork\n", "fork main diverges") - - const remotesRoot = await fs.mkdtemp(path.join(os.tmpdir(), "resolve-base-remotes-")) - const upstreamBare = path.join( - remotesRoot, - "github.com", - "EveryInc", - "compound-engineering-plugin.git", - ) - await fs.mkdir(path.dirname(upstreamBare), { recursive: true }) - await runGit(["init", "--bare", upstreamBare], upstreamSeed) - const upstreamUrl = pathToFileURL(upstreamBare).toString() - await runGit(["remote", "add", "origin", upstreamUrl], upstreamSeed) - await runGit(["push", "origin", "main", "feature"], upstreamSeed) - - const forkBare = path.join(remotesRoot, "github.com", "someone", "fork.git") - await fs.mkdir(path.dirname(forkBare), { recursive: true }) - await runGit(["init", "--bare", forkBare], forkSeed) - const forkUrl = pathToFileURL(forkBare).toString() - await runGit(["remote", "add", "origin", forkUrl], forkSeed) - await runGit(["push", "origin", "main"], forkSeed) - - const checkoutParent = await fs.mkdtemp(path.join(os.tmpdir(), "resolve-base-pr-shallow-")) - const checkoutRoot = path.join(checkoutParent, "checkout") - await runCommand( - ["git", "clone", "--depth", "1", "--branch", "feature", upstreamUrl, checkoutRoot], - os.tmpdir(), - gitEnv, - ) - await runGit(["checkout", "--detach", featureSha], checkoutRoot) - await runGit(["remote", "rename", "origin", "upstream"], checkoutRoot) - await runGit(["remote", "add", "origin", forkUrl], checkoutRoot) - await runGit(["fetch", "--depth", "1", "origin", "main"], checkoutRoot) - await runGit(["update-ref", "refs/remotes/origin/main", forkMainSha], checkoutRoot) - await runGit(["branch", "-D", "feature"], checkoutRoot) - - const stubBin = await createStubBin("pr-metadata") - const result = await runResolveBase(checkoutRoot, stubBin) - - expect(result.exitCode).toBe(0) - expect(result.stdout.trim()).toBe(`BASE:${upstreamMainSha}`) - }) - - test("emits ERROR output when no base branch can be resolved", async () => { - const repoRoot = await initRepo("scratch") - await commitFile(repoRoot, "history.txt", "a\n", "initial") - - const stubBin = await createStubBin("gh-fails") - const result = await runResolveBase(repoRoot, stubBin) - - expect(result.exitCode).toBe(0) - expect(result.stdout.trim()).toBe( - "ERROR:Unable to resolve review base branch locally. Fetch the base branch and rerun, or provide a PR number so the review scope can be determined from PR metadata.", - ) - }) -}) diff --git a/tests/review-skill-contract.test.ts b/tests/review-skill-contract.test.ts index b638b3534..951b786eb 100644 --- a/tests/review-skill-contract.test.ts +++ b/tests/review-skill-contract.test.ts @@ -641,18 +641,10 @@ describe("ce-code-review contract", () => { // PR mode still has an inline error for unresolved base expect(content).toContain('echo "ERROR: Unable to resolve PR base branch') - // Branch and standalone modes delegate to resolve-base.sh and check its ERROR: output. - // The script itself emits ERROR: when the base is unresolved. - expect(content).toContain("scripts/resolve-base.sh") - const resolveScript = await readRepoFile( - "plugins/compound-engineering/skills/ce-code-review/scripts/resolve-base.sh", - ) - expect(resolveScript).toContain("ERROR:") - - // Branch and standalone modes must stop on script error, not fall back - expect(content).toContain( - "If the script outputs an error, stop instead of falling back to `git diff HEAD`", - ) + // Branch and standalone modes must stop when no base can be resolved, not fall back to + // `git diff HEAD`. The guard phrase appears once per mode (branch + standalone). + const stopGuardMatches = content.match(/Do not fall back to `git diff HEAD`/g) + expect(stopGuardMatches?.length).toBeGreaterThanOrEqual(2) }) test("orchestration callers pass explicit mode flags", async () => {