qwen-code/packages/core
Shaojin Wen 7c1a93477c
perf(review): scope Agent 7's build/test to the workspaces the diff changed (#6955)
* perf(review): scope Agent 7's build/test to the workspaces the diff changed

Agent 7's brief runs `npm run build` then `npm test` with a 120s deadline. On this
repo a cold full build is 125s, so the mandated command cannot finish inside the
mandated deadline — measured across the harness's own transcripts, 71 `npm run
build` timeouts, each verifying nothing before the model spent more turns ruling
the timeout environmental and improvising a narrower command.

`qwen review build-test` replaces it. It installs if needed, then builds only the
workspaces the diff touches (from the plan's files[] and the root package.json
workspaces) plus their dependents and dependencies, deps-first — for a leaf PR, a
handful of packages instead of all of them. When a compile fails on `TS2307:
Cannot find module '@scope/pkg'` naming a workspace, it adds that package and
retries, so a tsconfig `paths` edge into another package's sources (which
package.json never declares) self-corrects rather than being modelled. A command
that runs out of time is reported as infrastructure, never as a Critical against
the PR; a build failure in a file the diff did not touch is reported as
pre-existing.

Agent 7's brief (in lib/agent-briefs.ts) now leads with build-test and falls back
to the maven/gradle/cargo/go/pytest precedence only when it reports
`toolchain: unsupported`, so non-npm repos are unchanged. buildRoleBrief injects
the concrete command with absolute --plan/--worktree paths, beside the
test-efficacy probe it already injects.

The install moves out of Step 1 (nothing before Agent 7 needs node_modules, and
running it there blocked the whole fan-out) into build-test, with
QWEN_SKIP_PREPARE=1 so `npm ci` does not trigger this repo's prepare hook — which
builds and bundles every workspace, ~190s, wasted before a scoped build. npm ci
drops from ~161s to ~27s. And todo_write is banned during a review: this document
is the plan, and each call is a whole model turn (measured at 179-377s per run of
restating steps already written down).

`isWorkspaceMember` moves from test-efficacy.ts to the shared lib/workspaces.ts
(re-exported to keep its callers), since build-test needs the same glob walk.

* test(review): add build-test to the expected subcommand roster

* fix(review): guard the build-test prompt and treat every timeout as infrastructure

Addresses the /review findings on this PR:

- agent-prompt.ts: the build-test command block is now guarded on `pr !== undefined`,
  matching the adjacent test-efficacy block. Without it, `report.prNumber` being
  absent resolved `--out` to `qwen-review-pr-undefined-build-test.json` — a report the
  agent writes and downstream never finds.
- build-test.ts: a timed-out `npm ci` now aborts instead of building against the
  partial `node_modules` a timeout leaves behind (distinct from a `prepare` failure,
  which leaves a complete tree). A timed-out test command now produces an
  infrastructure note rather than "correlate this failure with the diff — a failure is
  a Critical", which contradicted the brief. The handler's triple `argv` cast is
  consolidated.

Tests: absolute-path + no-`undefined` assertions for the build-test command block, and
install-timeout / test-timeout infrastructure-note coverage.

* fix(review): survive the tool timeout, partial installs, local mode, and Windows

Addresses the review on this PR:

- **Tool timeout (High).** build-test runs install + builds + tests in one process;
  the agent's default 120s `run_shell_command` timeout would kill it — the failure
  this PR fixes, one level up. The welded command block now tells the agent to
  invoke it with `timeout: 600000` (the shell tool's max).
- **Partial `node_modules` (Medium).** The install gate is now npm's completeness
  marker (`node_modules/.package-lock.json`), not bare directory existence, so a
  partial tree left by a timeout (here or from the outer kill) triggers a reinstall
  instead of a build against half a tree. A timed-out install also removes the
  partial tree before aborting.
- **Local reviews (Medium).** Agent 7 runs on local/file reviews too, which have no
  worktree or PR number. The build-test block now emits there as well, scoped to the
  project root, so the brief's "run build-test, below" is no longer a mandate with no
  command.
- **Windows merge queue (Medium).** The real-spawn fixtures used POSIX
  `touch`/`test -f`/`sleep`, which the Windows `test_windows` job (cmd.exe) fails on;
  rewritten with portable `node -e`, plus explicit per-test timeouts.

Smaller points: an unmodeled workspace glob (`**`, inner `*`, `foo-*`) now returns
`toolchain: "unsupported"` instead of a false "nothing to build"; a widened-away
command is dropped from `timedOut` as well as `build[]`; the `ok` docstring is
corrected; timeout detection prefers spawnSync's authoritative `ETIMEDOUT`.

* fix(review): abort widening on a build timeout, and close the round-2 review gaps

Addresses the second review pass:

- **Widening loop ignored a timeout (Critical).** A build killed at the deadline
  leaves partial output that can contain a `Cannot find module` line; the loop read
  that as a too-small build set and retried under another full deadline, up to the
  attempt cap — 4× the wall clock for what is infrastructure, not a graph gap. A
  timed-out build now aborts at once, the way the install path already does.
- **Widening scanned trimmed output.** `unresolvedWorkspaceDeps` reads the per-command
  output, which was head+tail only; a `Cannot find module` in the omitted middle
  (a long tsc log) ended widening early and surfaced a real gap as a false build
  failure. `trimOutput` now rescues module-resolution error lines from the middle, so
  the report stays bounded and the widening signal survives.
- **Unquoted `--workspace`.** A workspace dir with a space or shell metacharacter would
  split under `shell: true`; the build and test commands now quote it.
- **Handler had no error boundary.** A missing/invalid plan threw a raw stack trace as
  the whole of Agent 7's result; the handler now prints the descriptive message and
  exits non-zero.

Coverage the review asked for: the widening attempt-cap exhaustion, a build timeout
mid-widening, negated-workspace exclusion from the build set, the `changedFilesFrom`
error path, and `readWorkspaceGlobs`' object form.

* test(review): drive build-test through the exec seam, not real npm

The new build/test scoping tests spawned real `npm run` processes. Under the full
suite's parallelism that hung CI (each npm start is slow, and a `node -e`
setTimeout fixture leaked past the process kill), and the fixtures were POSIX-only.

These tests are about which packages get built, in what order, and how a result is
classified — not about npm's own workspace resolution. Driving them through the
injectable `exec` seam makes them deterministic, instant, leak-free and
platform-independent. No production code changes; the suite drops from a hang to
~6s.

* fix(review): support single-package and non-npm repos in build-test

Addresses the follow-up review's findings.

- **Single-package npm repos (no `workspaces`) had no build/test path (regression).**
  build-test now treats a workspace-less `package.json` with a build/test script as a
  single root package: it installs, builds `npm run build` and tests `npm test` (no
  `--workspace`), keeping the deadline and timeout-as-data for the most common repo
  shape. Only when the root has no build/test script does it report `unsupported` —
  and the brief's fallback now installs dependencies first, since build-test's own
  install runs only on the npm path.

- **yarn/bun workspace repos got a false "install failed".** `workspaces` is also
  yarn/bun syntax, but they write no `package-lock.json`, so the completeness marker
  was never present and `npm ci` fail-fasted over a usable tree. Install now runs only
  when a root `package-lock.json` exists; a non-npm tree that is already present is
  trusted (the build is the authoritative signal).

- **Per-command deadline lowered 600s → 300s**, kept strictly below the 600 000 ms tool
  timeout the brief welds, so a single hung command's own deadline fires — and the
  report lands — before the outer shell kill would discard it. (A giant PR whose
  commands sum past the tool ceiling remains an acknowledged follow-up; a dynamic
  cumulative budget is the full fix.)

Smaller points: the install-timeout `rmSync` is wrapped (best-effort, `maxRetries`) so
a race with orphaned grandchildren can't replace the report; the local-mode `.`
worktree fallback is gated on `pr === undefined` so a PR-mode report never builds the
user's checkout (help text corrected); the now-unreachable widen-path `timedOut` filter
is removed; and `changedFilesFrom` rejects a non-object plan with a descriptive error.

* fix(review): close the generic-repo false-green and false-Critical gaps

The verification review found three ways the generic (non-this-repo) surface
re-opened the two failure modes this command is built to prevent. All three now
hand off or self-correct instead.

- **F1 (false green).** A changed dir the workspace globs map to a non-package — a
  nested package listed before a `*` that also claims its parent segment, or a loose
  file under a `packages/*` base — was dropped from the build set silently: zero
  commands, `ok: true`, "Everything passed". Any affected dir not in the package map
  now trips the `unsupported` handoff (naming the dir), never a bare green.

- **F2 (false Critical).** A review worktree is cold. A yarn/bun/pnpm repo (same
  `workspaces` field, no `package-lock.json`) got no install on any path — build-test's
  install is npm-gated and the brief's fallback never fired — so the build failed with
  `Cannot find module` inside the PR's own files, steered toward a Critical. A non-npm
  repo with no installed tree now hands off, naming the tool (`yarn install
  --frozen-lockfile`, etc.) to install with first.

- **F3 (mis-order terminal-fail).** When both the needer and an undeclared-needed
  package are changed and the alphabet orders the needer first, the compiler named an
  in-set package, `missing` came out empty, and the run terminal-failed though the
  corrected order builds green. The widening filter is now `!built.has(dir)` rather
  than `!set.includes(dir)`, so an in-set-but-unbuilt package re-seeds into
  `alsoBuild` (which sorts first) and fixes the order; the attempt cap still bounds it.

Tests for all three, matching the review's runnable repros. The non-blocking notes
(cumulative-budget overrun, incremental report write, array-args spawn) remain for the
dynamic-budget follow-up.

* test(review): tighten the widening-cap assertions

Per review: assert exactly three widenings (`toBe(3)`, not `>= 3`) so an
over-widening regression is caught, and assert `rep.test` is empty — the
exhaustion branch returns before the test loop, so a refactor that reordered the
test loop above that return would otherwise pass undetected.
2026-07-16 00:58:11 +00:00
..
scripts Fix: Improve ripgrep binary detection and cross-platform compatibility (#1060) 2025-11-18 19:38:30 +08:00
src perf(review): scope Agent 7's build/test to the workspaces the diff changed (#6955) 2026-07-16 00:58:11 +00:00
vendor feat test tool permissions 2026-03-10 16:30:22 +08:00
index.ts fix: Remove remaining ClearcutLogger export from packages/core/index.ts 2026-02-01 14:52:14 +08:00
package.json chore(release): v0.19.10 (#6855) 2026-07-14 05:12:22 +00:00
test-setup.ts feat(memory): managed auto-memory and auto-dream system (#3087) 2026-04-16 20:05:45 +08:00
tsconfig.json fix: upgrade @lydell/node-pty to 1.2.0-beta.10 to fix PTY FD leak 2026-04-01 07:55:56 +08:00
vitest.config.ts Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00