diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdf61e657..d0ce36e09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: branches: [main] pull_request: workflow_dispatch: + # Nightly pollution-detector run (see test-one-process below). + schedule: + - cron: "17 7 * * *" concurrency: group: ci-${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }} @@ -78,31 +81,29 @@ jobs: run: bun run build # The suite is sharded so the slowest slice, not the whole suite, sets the - # wall clock. The old ./src leg (387 files, ~88s local) is split into three - # path-disjoint shards measured at ~30s (src-a: tui, 146 files), ~46s - # (src-b: agent + subagent, 90 files), and ~45s (src-c: everything else in - # src, 153 files); the ./tests leg stays whole because ./evals ./scripts - # finish in ~1s and a leg of their own would be all setup overhead. - # Every shard still goes through check:projects-dir-guard: the - # guard forwards these path filters to the suite it wraps, and the union of - # the shards' filters is exactly ./src ./tests ./evals ./scripts, so the gate covers - # the same tests as before, all of them sandboxed. + # wall clock. Four time-balanced shards split the same + # ./src ./tests ./evals ./scripts union via bun's --shard=k/4, balanced by + # the checked-in per-file durations in scripts/ci-timings.json (--timings). + # Every shard still goes through check:projects-dir-guard: the guard + # forwards the path union plus the shard flags to the suite it wraps, so the + # gate covers the same tests as before, all of them sandboxed. + # + # Timings refresh policy: regenerate scripts/ci-timings.json by running the + # full union locally with --update-timings (same seeded flags as `test`): + # bun run check:projects-dir-guard ./src ./tests ./evals ./scripts \ + # --timings=./scripts/ci-timings.json --update-timings + # Regen when the slowest shard's Test step skews more than ~20% above a + # quarter of the one-process suite time (shards drifting apart means the + # timings no longer describe the suite), or proactively whenever slow files + # land. A scheduled refresh artifact is a future option, not current setup. test: runs-on: ubuntu-latest strategy: - # A red shard must not cancel the others; every result is the signal. + # A red shard must not cancel the others; all results are the signal. fail-fast: false matrix: - shard: - - name: src-a - paths: ./src/tui - - name: src-b - paths: ./src/agent ./src/subagent - - name: src-c - paths: ./src/auth ./src/changelog ./src/config ./src/cost ./src/crash ./src/exec ./src/logging ./src/mcp ./src/perf ./src/permission ./src/plugins ./src/provider ./src/session ./src/shell ./src/telemetry ./src/tools ./src/trust ./src/upgrade ./src/util ./src/web ./src/config.test.ts ./src/context-compactor.test.ts ./src/director.test.ts ./src/inference-abort.test.ts ./src/inference-error-message.test.ts ./src/inference-gateway-error.test.ts ./src/list-dir.test.ts ./src/pricing-fetcher.test.ts ./src/pricing-metadata.test.ts ./src/profiles.test.ts ./src/prompts.test.ts ./src/renderer.test.ts ./src/settings.test.ts ./src/state.test.ts - - name: tests-evals-and-scripts - paths: ./tests ./evals ./scripts - name: test (${{ matrix.shard.name }}) + shard: ["1/4", "2/4", "3/4", "4/4"] + name: test (${{ matrix.shard }}) steps: - name: Checkout uses: actions/checkout@v4 @@ -131,19 +132,67 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - # The same script the local `bun run check` gate runs, with the shard's - # path filters forwarded through the guard to the suite. The guard - # routes a filtered run through test:paths, which carries the same - # seeded flags as the `test` script; bun test filters are additive, so - # appending filters to `bun run test` could not narrow it. Randomized - # order catches tests that only pass in the default file order (shared - # module-level state, an unrestored global mock, a leaked env var). + # The same script the local `bun run check` gate runs, with the full + # path union plus the shard's --shard/--timings flags forwarded through + # the guard to the suite. The guard routes a filtered run through + # test:paths, which carries the same seeded flags as the `test` script; + # bun test filters are additive, so appending filters to `bun run test` + # could not narrow it. --shard splits by file (balanced by --timings), + # so every shard covers the same union and the four shards together + # cover the whole suite. Randomized order catches tests that only pass + # in the default file order (shared module-level state, an unrestored + # global mock, a leaked env var). # The seed stays 424242 in every shard rather than varying per shard: # the shards already run disjoint file sets, and a fixed seed keeps # any failure reproducible locally with the same - # `bun run test:paths `. + # `bun run test:paths --shard=k/4 + # --timings=./scripts/ci-timings.json`. - name: Test - run: bun run check:projects-dir-guard ${{ matrix.shard.paths }} + run: bun run check:projects-dir-guard ./src ./tests ./evals ./scripts --shard=${{ matrix.shard }} --timings=./scripts/ci-timings.json + + # Cross-shard pollution detector: the shards above split the path union, + # but the union is not the isolation domain — a mock.module leak across + # files fails in the one-process suite yet passes when the files land in + # different shards (CL-6967). This job reruns the whole union in one + # process with no filters, exactly like the local `bun run check` gate. + # Nightly (plus manual workflow_dispatch), not per-PR: the one-process + # suite takes ~2 minutes on CI and would put that back on the PR wall + # clock this sharding removes. Non-blocking (continue-on-error) so a slow + # or flaky full run cannot hold the gate; a real pollution failure still + # shows up red for triage. + test-one-process: + name: test (one-process pollution detector) + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + continue-on-error: true + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.14" + + - name: Install ripgrep + run: sudo apt-get install -y ripgrep + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: node_modules + key: bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Test + run: bun run check:projects-dir-guard # protect-main still requires the pre-restructure check names. These jobs # exist only to publish those contexts after the real work succeeds. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 448caeccf..52e004584 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,8 +30,15 @@ bun run build bun run test ``` -These match the local development loop. CI shards the same path union via -`test:paths` rather than running the one-process `bun run test` suite. +These match the local development loop. CI splits the same path union into +four time-balanced `--shard=k/4` slices via `test:paths` (balanced by the +checked-in per-file durations in `scripts/ci-timings.json`) rather than +running the one-process `bun run test` suite. Regenerate that file with +`bun run check:projects-dir-guard ./src ./tests ./evals ./scripts +--timings=./scripts/ci-timings.json --update-timings` when the slowest +shard skews more than ~20% above a quarter of the one-process suite time, +or proactively whenever slow files land — see `.github/workflows/ci.yml` +for the full policy. Run `bun run check` (lint, typecheck, build, and the guarded test suite) before opening a PR — `bun run test` alone skips the projects-dir sandbox guard, which only runs diff --git a/scripts/ci-timings.json b/scripts/ci-timings.json new file mode 100644 index 000000000..782abe35b --- /dev/null +++ b/scripts/ci-timings.json @@ -0,0 +1,516 @@ +{ + "version": 1, + "files": { + "scripts/test-parallel.test.ts": 14167, + "src/permission/permission.test.ts": 9258, + "tests/integration/exec-shutdown-reap.test.ts": 7188, + "src/plugins/shell-guard-plugin.test.ts": 7135, + "tests/integration/exec-signal-finalize.test.ts": 6267, + "tests/integration/compaction-baseline.test.ts": 5056, + "tests/integration/crash-finalize.test.ts": 4321, + "src/tui/runtime-channels.test.ts": 4140, + "src/auth/store.test.ts": 3607, + "tests/integration/signal-finalize.test.ts": 3581, + "src/subagent/spawn-agent-worktree.test.ts": 3152, + "tests/integration/rawmode-sigint.test.ts": 3097, + "src/tools/web-fetch.test.ts": 3082, + "src/subagent/run-resolved-provider-failure.test.ts": 3079, + "src/tui/transcript-layout.test.ts": 2841, + "src/session/list-sessions.test.ts": 2614, + "src/tui/tool-execution-watchdog.test.ts": 2584, + "src/tui/product-host.test.ts": 2467, + "tests/integration/git-push-scoped.test.ts": 2232, + "src/tui/transcript-panels.test.ts": 2153, + "src/shell/background-shell.test.ts": 1970, + "tests/integration/subagent-permission.test.ts": 1774, + "src/permission/gate.test.ts": 1466, + "src/session/project-key.test.ts": 1374, + "src/subagent/agent-fleet.test.ts": 1364, + "src/tui/collapse.test.ts": 1311, + "src/agent/background-shell-tool.test.ts": 1192, + "src/tui/markdown-rows.test.ts": 1176, + "src/tui/slash-popup-gate.test.ts": 1165, + "src/tui/command-surfaces.test.ts": 1161, + "src/tui/provider-setup.test.ts": 1155, + "src/tui/transcript-long-log-scroll.test.ts": 1120, + "src/tui/url-click.test.ts": 1060, + "tests/unit/verify-corbits-only-scope.test.ts": 1043, + "tests/unit/workflow-host.test.ts": 1043, + "tests/unit/prepare-homebrew-tap-release.test.ts": 1021, + "src/tui/image-attachments.test.ts": 1011, + "src/agent/tool-search.test.ts": 1006, + "src/session/run-liveness.test.ts": 871, + "src/agent/environment.test.ts": 869, + "src/tui/mcp-copy-failure.test.ts": 862, + "src/session/optimized-context-store.test.ts": 852, + "src/config.test.ts": 708, + "src/tui/runtime-bridge.test.ts": 690, + "src/tui/shell.test.ts": 686, + "tests/integration/reactor-approval-suspend.test.ts": 662, + "src/session/session-dir.test.ts": 636, + "tests/helpers/temporary-git-repo.test.ts": 624, + "tests/unit/telemetry.test.ts": 605, + "src/tui/gate-wire.test.ts": 556, + "tests/unit/tui/at-mention-resolution.test.ts": 496, + "src/tui/wave6.test.ts": 480, + "tests/unit/exec/runner.test.ts": 470, + "src/state.test.ts": 424, + "src/permission/worktree-roots.test.ts": 423, + "scripts/eval-capability.test.ts": 402, + "src/mcp/auth-store.test.ts": 401, + "src/permission/classify-security.test.ts": 401, + "src/agent/exa-web-fetch-alias.test.ts": 396, + "tests/unit/oxlint-no-bare-mock-module.test.ts": 373, + "tests/integration/reactor-permission-multi-turn.test.ts": 362, + "src/tui/request-approval.test.ts": 355, + "tests/unit/session/run-state-e2e.test.ts": 346, + "src/tui/keybindings.test.ts": 345, + "src/session/sent-messages.test.ts": 343, + "src/tui/landing.test.ts": 334, + "src/agent/fleet-verbs-mount.test.ts": 325, + "src/plugins/permission-plugin.test.ts": 324, + "src/tui/transcript-anchor.test.ts": 316, + "src/session/rename-session.test.ts": 304, + "tests/unit/workflows-state.test.ts": 292, + "tests/unit/telemetry-toggle.test.ts": 289, + "src/subagent/run-authority.test.ts": 277, + "src/plugins/secret-guard-symlink.test.ts": 274, + "src/permission/project-approvals-trust.test.ts": 265, + "src/agent/posix-tool-plugins.test.ts": 251, + "src/permission/reactor-authorize.test.ts": 250, + "src/subagent/run-skill-scope.test.ts": 238, + "src/tui/prompt-slash-exit.test.ts": 225, + "tests/integration/compaction-atomicity.test.ts": 223, + "src/tui/runner-host.test.ts": 218, + "src/session/hooks.test.ts": 212, + "src/subagent/run-persist-close.test.ts": 198, + "src/perf/permission-subagent-spans.test.ts": 190, + "src/tui/palette-paint.test.ts": 181, + "src/permission/queue.test.ts": 180, + "tests/integration/reactor-events-guards.test.ts": 179, + "src/mcp/oauth-provider.test.ts": 168, + "src/tui/turn-monitor.test.ts": 166, + "src/session/runtime-assembly.test.ts": 163, + "src/tui/overlays.test.ts": 163, + "src/permission/grant-scope.test.ts": 159, + "tests/unit/hooks.test.ts": 155, + "src/subagent/index.test.ts": 154, + "tests/unit/ripgrep-plugin.test.ts": 147, + "src/agent/tools-mcp-disconnect.test.ts": 141, + "src/tui/wave7.test.ts": 139, + "src/session/state.test.ts": 136, + "src/tui/mention-popup.test.ts": 136, + "src/subagent/followup-live-agent.test.ts": 135, + "src/agent/apply-patch-diff.test.ts": 131, + "src/tui/prompt-box.test.ts": 125, + "tests/integration/mcp-late-dispatch.test.ts": 125, + "tests/unit/telemetry-product-events.test.ts": 124, + "tests/unit/mcp.test.ts": 119, + "src/permission/path-restriction.test.ts": 117, + "src/permission/workspace-containment.test.ts": 117, + "src/tui/focus-routing.test.ts": 116, + "src/tui/observe-live.test.ts": 113, + "src/tui/diff-rows.test.ts": 112, + "src/tui/row-click.test.ts": 110, + "src/subagent/lifecycle-tools.test.ts": 109, + "src/settings.test.ts": 108, + "src/tui/prompt-features.test.ts": 108, + "tests/unit/session/resume-interrupted.test.ts": 107, + "src/director.test.ts": 106, + "src/tui/runner/exit.test.ts": 105, + "src/tui/agent-ask-wake.test.ts": 101, + "tests/unit/project-trust-plugins.test.ts": 101, + "src/permission/approval-log.test.ts": 99, + "src/tui/list-modal.test.ts": 96, + "tests/integration/vendored-carry.test.ts": 95, + "src/tui/overlay-paint.test.ts": 94, + "src/session/stream-journal.test.ts": 92, + "src/session/session-label.test.ts": 89, + "src/tui/components/at-mention/list.test.ts": 87, + "src/plugins/read-file-guard-plugin.test.ts": 85, + "src/subagent/run-settlement.test.ts": 82, + "src/mcp/plugin.test.ts": 81, + "src/tui/runner/wiring.ask-wake.test.ts": 80, + "src/trust/project-trust.test.ts": 79, + "src/tui/approval-prompt-visibility.test.ts": 79, + "src/tui/gutter-labels.test.ts": 79, + "src/subagent/session-store.test.ts": 72, + "src/plugins/secret-guard-plugin.test.ts": 70, + "src/tui/steer-worker-invariant.test.ts": 69, + "src/plugins/delete-file-plugin.test.ts": 68, + "src/tui/run-snapshot-kind.test.ts": 68, + "src/tui/prompt-chrome.test.ts": 66, + "tests/integration/reactor-empty-turn.test.ts": 65, + "src/mcp/client-auth-reauth-cap.test.ts": 62, + "src/subagent/retain-salvage.test.ts": 62, + "src/mcp/add-server.test.ts": 61, + "src/session/incremental-jsonl.test.ts": 61, + "src/tui/overlay-overflow.test.ts": 61, + "tests/unit/data-only-agent.test.ts": 61, + "tests/unit/index.test.ts": 61, + "src/subagent/run-submit-result-rotation.test.ts": 60, + "tests/unit/permission/cross-commit-composition.test.ts": 59, + "src/plugins/verify-plugin.test.ts": 58, + "src/tui/tool-rows.test.ts": 57, + "tests/unit/project-trust.test.ts": 57, + "src/tui/queued-delivery-hop.test.ts": 56, + "src/permission/critique-grep-file-env.test.ts": 55, + "src/tui/copy-wire.test.ts": 55, + "src/mcp/callback-server.test.ts": 54, + "src/tui/provider-setup-submit.test.ts": 54, + "src/session/compaction-archive.test.ts": 53, + "tests/unit/path-plugin-trust.test.ts": 53, + "src/permission/store.test.ts": 51, + "src/tui/mcp-view.test.ts": 51, + "src/tui/onboarding.test.ts": 51, + "src/tui/welcome.test.ts": 51, + "src/subagent/intervention-log.test.ts": 50, + "src/subagent/run-audit-store.test.ts": 46, + "src/tui/plugin-diagnostics-sink.test.ts": 46, + "src/session/live-model-switch.test.ts": 44, + "src/plugins/change-diff.test.ts": 43, + "tests/unit/config.test.ts": 43, + "src/plugins/claude-plugins.test.ts": 42, + "tests/unit/tui/agent-tools.test.ts": 42, + "src/tui/row-update-perf.test.ts": 41, + "src/plugins/secret-guard-shell-symlink.test.ts": 40, + "tests/unit/vendor-patch-ledger.test.ts": 40, + "tests/unit/path-trust.test.ts": 39, + "tests/unit/plugin-repo-locator.test.ts": 38, + "tests/unit/skills.test.ts": 38, + "src/plugins/edit-file-diagnostics-plugin.test.ts": 35, + "src/tui/overlay-primary-state.test.ts": 35, + "tests/unit/workflows-runtime-persistence.test.ts": 35, + "src/plugins/bounded-grep-fallback.test.ts": 34, + "src/tui/prompt-highlight.test.ts": 34, + "src/agent/product-mutation-tools.test.ts": 33, + "src/tools/web-search.test.ts": 33, + "tests/unit/approval-resume.test.ts": 33, + "tests/unit/codex-callback-server.test.ts": 32, + "src/tui/overlay-empty-state.test.ts": 30, + "src/plugins/evidence-archive-search-plugin.test.ts": 29, + "src/provider/validate-connection.test.ts": 29, + "src/tui/reasoning-fold.test.ts": 29, + "src/shell/persistent-shell-cwd.test.ts": 28, + "tests/unit/codex-auth.test.ts": 28, + "tests/unit/skill-commands.test.ts": 28, + "src/subagent/trace-reader.test.ts": 27, + "src/tui/margins.test.ts": 27, + "src/tui/overlay-key-routing.test.ts": 26, + "tests/unit/data-only-commands.test.ts": 26, + "src/tui/startup-transcript.test.ts": 25, + "src/pricing-metadata.test.ts": 24, + "src/profiles.test.ts": 24, + "src/tui/runtime-shutdown.test.ts": 24, + "src/plugins/uninstall.test.ts": 23, + "src/tui/overlay-body.test.ts": 23, + "src/tui/ramp-paint.test.ts": 23, + "src/crash/report.test.ts": 22, + "src/plugins/secret-guard-credential-surface.test.ts": 22, + "src/auth/xai/callback-server.test.ts": 21, + "tests/unit/plugin-marketplace.test.ts": 21, + "src/tui/markdown-parser.test.ts": 20, + "src/tui/description-zone.test.ts": 18, + "src/tui/overlay-float-reset.test.ts": 18, + "tests/unit/check-gate.test.ts": 18, + "src/tui/chrome-repaint.test.ts": 17, + "tests/unit/corbits-skills-catalog.test.ts": 17, + "src/tui/overlay-min-geometry.test.ts": 16, + "src/tui/thinking-reveal.test.ts": 16, + "tests/unit/generate-homebrew-tap.test.ts": 16, + "src/list-dir.test.ts": 15, + "src/renderer.test.ts": 15, + "src/subagent/nudge-director.test.ts": 15, + "src/tui/render-loop.test.ts": 15, + "src/tui/runtime-bridge-coalesce.test.ts": 15, + "tests/unit/workflows-runtime.test.ts": 15, + "src/tui/overlay-fixture-fallback.test.ts": 14, + "tests/unit/codex-session.test.ts": 14, + "src/plugins/loader.test.ts": 13, + "src/subagent/submit-result.test.ts": 13, + "evals/capability/lib.test.ts": 12, + "src/perf/rollup.test.ts": 12, + "src/subagent/fleet-report.test.ts": 12, + "src/tui/overlay-body-cache-staleness.test.ts": 12, + "src/agent/prompt-sizes.test.ts": 11, + "src/mcp/tool-permissions.test.ts": 11, + "src/perf/index.test.ts": 11, + "src/plugins/path-escape-plugin.test.ts": 10, + "src/subagent/run-source.test.ts": 10, + "tests/unit/lexicon-skill.test.ts": 10, + "tests/unit/xai-session.test.ts": 10, + "src/plugins/result-truncation-plugin.test.ts": 9, + "src/subagent/trace-tool.test.ts": 9, + "src/tui/provider-connect.test.ts": 9, + "tests/unit/plugin-loader-path.test.ts": 9, + "src/agent/directors/testsmith/package.test.ts": 8, + "src/context-compactor.test.ts": 8, + "src/pricing-fetcher.test.ts": 8, + "src/tui/mouse-reporting-disabled.test.ts": 8, + "src/tui/width-columns.test.ts": 8, + "tests/unit/compactor-pairing.test.ts": 8, + "src/auth/credential-surface-coverage.test.ts": 7, + "src/plugins/authz-plugin.test.ts": 7, + "src/plugins/edit-file-line-range.test.ts": 7, + "src/session/commit-signer.test.ts": 7, + "src/shell/run-shell-authz.test.ts": 7, + "src/tui/row-retext.test.ts": 7, + "src/tui/runner/wiring.stall-poll.test.ts": 7, + "evals/capability/locked-fixtures.test.ts": 6, + "src/agent/director.test.ts": 6, + "src/agent/directors/draper/package.test.ts": 6, + "src/permission/auto-shell-policy.test.ts": 6, + "src/plugins/tool-output-uri-plugin.test.ts": 6, + "src/prompts.test.ts": 6, + "src/tui/decision-truncation.test.ts": 6, + "src/tui/log-sink.test.ts": 6, + "src/tui/overlay-view.test.ts": 6, + "src/tui/teardown.test.ts": 6, + "tests/unit/summarizer.test.ts": 6, + "src/agent/codex-tool-mount.test.ts": 5, + "src/plugins/diagnostics.test.ts": 5, + "src/provider/identity-divergence.test.ts": 5, + "src/subagent/fleet-dry-drive.test.ts": 5, + "src/tui/session-chrome.test.ts": 5, + "src/tui/tool-formatter.test.ts": 5, + "src/tui/turn-state.test.ts": 5, + "tests/unit/pricing-fetcher.test.ts": 5, + "tests/unit/tui/onboarded-persistence.test.ts": 5, + "tests/unit/workflows-definitions.test.ts": 5, + "src/agent/codex-tool-proxies.test.ts": 4, + "src/auth/oauth-scope-check.test.ts": 4, + "src/perf/attribution-report.test.ts": 4, + "src/perf/otel-sink.test.ts": 4, + "src/provider/opencode-go-models.test.ts": 4, + "src/provider/zen-models.test.ts": 4, + "src/subagent/lifecycle.test.ts": 4, + "src/subagent/mailbox-mail-drive.test.ts": 4, + "src/telemetry/ai-observability.test.ts": 4, + "src/tui/chrome-state.test.ts": 4, + "src/tui/diff.test.ts": 4, + "src/tui/mark-anim.test.ts": 4, + "src/tui/stream-event-map.test.ts": 4, + "tests/unit/agent-context-extensions.test.ts": 4, + "tests/unit/codex-sse-fixtures.test.ts": 4, + "tests/unit/inference-response-kind.test.ts": 4, + "tests/unit/workflows-director.test.ts": 4, + "evals/capability/behaviors.test.ts": 3, + "evals/compaction/metrics.test.ts": 3, + "src/agent/compaction.test.ts": 3, + "src/agent/directors/emil/package.test.ts": 3, + "src/agent/directors/registry.test.ts": 3, + "src/agent/directors/skywalker/package.test.ts": 3, + "src/agent/lsp-availability.test.ts": 3, + "src/agent/prompts.test.ts": 3, + "src/agent/retry-policy.test.ts": 3, + "src/agent/skill-search.test.ts": 3, + "src/agent/use-skill.test.ts": 3, + "src/changelog/index.test.ts": 3, + "src/mcp/client-auth-policy.test.ts": 3, + "src/perf/reactor-spans.test.ts": 3, + "src/plugins/agent-plugins.test.ts": 3, + "src/plugins/data-only-agent.test.ts": 3, + "src/plugins/rg-run.test.ts": 3, + "src/provider/codex-responses.test.ts": 3, + "src/provider/context-window.test.ts": 3, + "src/provider/opencode-go-adapter.test.ts": 3, + "src/provider/reasoning-effort.test.ts": 3, + "src/session/assemble-runtime.test.ts": 3, + "src/subagent/admission.test.ts": 3, + "src/subagent/ask-director.test.ts": 3, + "src/tools/ssrf-guard.test.ts": 3, + "src/tui/components/session-header.test.ts": 3, + "src/tui/geometry.test.ts": 3, + "src/tui/harness.test.ts": 3, + "src/tui/overlay-list.test.ts": 3, + "src/tui/overlay-reshape-selection.test.ts": 3, + "src/tui/prompt-border.test.ts": 3, + "src/tui/runtime-notices.test.ts": 3, + "src/tui/stream.test.ts": 3, + "tests/unit/subagent-session-store.test.ts": 3, + "tests/unit/telemetry-first-run.test.ts": 3, + "tests/unit/tui/approval-reload-during-suspend.test.ts": 3, + "tests/unit/tui/run-sink.test.ts": 3, + "tests/unit/tui/runner.test.ts": 3, + "tests/unit/workflows-registry.test.ts": 3, + "scripts/eval-public-swe-one.test.ts": 2, + "src/agent/agent-search.test.ts": 2, + "src/agent/codex-apply-patch.test.ts": 2, + "src/agent/directors/builder/package.test.ts": 2, + "src/agent/directors/critic/package.test.ts": 2, + "src/agent/directors/gauntlet/package.test.ts": 2, + "src/agent/lazy-blob-reader.test.ts": 2, + "src/agent/tool-schema-normalize.test.ts": 2, + "src/auth/callback-page.test.ts": 2, + "src/auth/codex/usage-limit-error.test.ts": 2, + "src/config/inference-sources.test.ts": 2, + "src/config/oauth-catalog.test.ts": 2, + "src/config/providers.test.ts": 2, + "src/inference-error-message.test.ts": 2, + "src/inference-gateway-error.test.ts": 2, + "src/logging/sink.test.ts": 2, + "src/perf/assert-spans.test.ts": 2, + "src/plugins/tool-result-secret-scrub.test.ts": 2, + "src/provider/anthropic-cache-breakpoint.test.ts": 2, + "src/provider/grok-responses.test.ts": 2, + "src/provider/ollama.test.ts": 2, + "src/provider/replay-sanitizer.test.ts": 2, + "src/session/approval-resume.test.ts": 2, + "src/session/run-sink.test.ts": 2, + "src/session/stream-consumer.test.ts": 2, + "src/session/summary-excerpt.test.ts": 2, + "src/subagent/poll-exempt.test.ts": 2, + "src/subagent/shell-evidence.test.ts": 2, + "src/subagent/thrash.test.ts": 2, + "src/subagent/worktree.test.ts": 2, + "src/tui/command-display.test.ts": 2, + "src/tui/command-registry-setup.test.ts": 2, + "src/tui/commands/built-in.test.ts": 2, + "src/tui/commands/registry.test.ts": 2, + "src/tui/focus/focus-state.test.ts": 2, + "src/tui/history-hydrate.test.ts": 2, + "src/tui/lockup.test.ts": 2, + "src/tui/model-catalog.test.ts": 2, + "src/tui/pick-session.test.ts": 2, + "src/tui/prompt-attachments.test.ts": 2, + "src/tui/prompt-recognition.test.ts": 2, + "src/tui/queued-delivery.test.ts": 2, + "src/tui/ramp.test.ts": 2, + "src/tui/selection-copy.test.ts": 2, + "src/tui/session-queue.test.ts": 2, + "src/tui/session-start.test.ts": 2, + "src/tui/submit-handler.test.ts": 2, + "src/tui/syntax-highlight.test.ts": 2, + "src/tui/turns-to-blocks.test.ts": 2, + "src/tui/view/registry.test.ts": 2, + "src/upgrade/index.test.ts": 2, + "src/web/plugin-provider.test.ts": 2, + "tests/unit/agent-tools.test.ts": 2, + "tests/unit/agent/tasks.test.ts": 2, + "tests/unit/example-agent-plugin.test.ts": 2, + "tests/unit/inference-sources.test.ts": 2, + "tests/unit/tui/view-render.test.ts": 2, + "tests/unit/tui/view-spec.test.ts": 2, + "src/agent/context-estimate.test.ts": 1, + "src/agent/directors/bruckheimer/package.test.ts": 1, + "src/agent/directors/explorer/package.test.ts": 1, + "src/agent/directors/gaasbot/package.test.ts": 1, + "src/agent/directors/greybeard/package.test.ts": 1, + "src/agent/directors/identity.test.ts": 1, + "src/agent/directors/intern/package.test.ts": 1, + "src/agent/directors/migrator/package.test.ts": 1, + "src/agent/directors/neckbeard/package.test.ts": 1, + "src/agent/directors/prober/package.test.ts": 1, + "src/agent/directors/rand/package.test.ts": 1, + "src/agent/directors/shakespeare/package.test.ts": 1, + "src/agent/directors/tester/package.test.ts": 1, + "src/agent/directors/tool-sets.test.ts": 1, + "src/agent/directors/warden/package.test.ts": 1, + "src/agent/doom-loop-note.test.ts": 1, + "src/agent/live-tool-dispatch.test.ts": 1, + "src/agent/model-family-policy.test.ts": 1, + "src/agent/reactor-events.test.ts": 1, + "src/agent/tool-classification.test.ts": 1, + "src/auth/credential-surface.test.ts": 1, + "src/config/oauth-providers.test.ts": 1, + "src/config/session-mode.test.ts": 1, + "src/config/xai-providers.test.ts": 1, + "src/cost/cost-summary.test.ts": 1, + "src/cost/cost-visibility.test.ts": 1, + "src/cost/session-cost.test.ts": 1, + "src/exec/runner.test.ts": 1, + "src/mcp/client-auth-retry.test.ts": 1, + "src/mcp/tool-name.test.ts": 1, + "src/perf/otel-config.test.ts": 1, + "src/permission/command.test.ts": 1, + "src/plugins/evidence-archive-path-guard.test.ts": 1, + "src/plugins/rg-output.test.ts": 1, + "src/plugins/tool-plugins.test.ts": 1, + "src/plugins/tool-result-materialize.test.ts": 1, + "src/plugins/tool-time-budget.test.ts": 1, + "src/provider/bifrost-adapter.test.ts": 1, + "src/provider/billing-product.test.ts": 1, + "src/provider/openai-compatible-adapter.test.ts": 1, + "src/provider/openai-responses.test.ts": 1, + "src/provider/opencode-go-anthropic-adapter.test.ts": 1, + "src/session/active-host.test.ts": 1, + "src/session/attachment-store.test.ts": 1, + "src/session/shell-output-feed.test.ts": 1, + "src/subagent/authority.test.ts": 1, + "src/subagent/inference-auth-failure.test.ts": 1, + "src/subagent/refresh-inference-source.test.ts": 1, + "src/subagent/run-codex-proxy.test.ts": 1, + "src/subagent/run-suspended-send.test.ts": 1, + "src/subagent/tool-preview.test.ts": 1, + "src/telemetry/feedback.test.ts": 1, + "src/tools/eval-http-env.test.ts": 1, + "src/tools/html-convert.test.ts": 1, + "src/tui/agent-progress.test.ts": 1, + "src/tui/command-catalog.test.ts": 1, + "src/tui/components/at-mention/parse.test.ts": 1, + "src/tui/components/prompt-action-bar-label.test.ts": 1, + "src/tui/copy-path.test.ts": 1, + "src/tui/correlation-acceptance.test.ts": 1, + "src/tui/deliver-agent-message.test.ts": 1, + "src/tui/dynamic-tool-runner.test.ts": 1, + "src/tui/live-session-port.test.ts": 1, + "src/tui/mcp-catalog.test.ts": 1, + "src/tui/mcp-list.test.ts": 1, + "src/tui/mention-filter.test.ts": 1, + "src/tui/pending-column.test.ts": 1, + "src/tui/prompt-kill-ring.test.ts": 1, + "src/tui/prompt-rows.test.ts": 1, + "src/tui/provider-failure-attempt.test.ts": 1, + "src/tui/quota-retry.test.ts": 1, + "src/tui/resume-seed.test.ts": 1, + "src/tui/runner-exit-code.test.ts": 1, + "src/tui/sent-message-history.test.ts": 1, + "src/tui/session-operation-queue.test.ts": 1, + "src/tui/system-clipboard.test.ts": 1, + "src/tui/tool-subject.test.ts": 1, + "src/tui/view/height.test.ts": 1, + "src/tui/width-contract.test.ts": 1, + "src/tui/workspace-watch.test.ts": 1, + "src/util/control-char-strip.test.ts": 1, + "src/util/tool-output-uri.test.ts": 1, + "tests/helpers/defined.test.ts": 1, + "tests/unit/codex-usage.test.ts": 1, + "tests/unit/context-window.test.ts": 1, + "tests/unit/director.test.ts": 1, + "tests/unit/faremeter.test.ts": 1, + "tests/unit/grok-responses-adapter.test.ts": 1, + "tests/unit/mcp-client-unwrap.test.ts": 1, + "tests/unit/mcp-tool-name.test.ts": 1, + "tests/unit/mcp-tool-permissions.test.ts": 1, + "tests/unit/openai-responses-adapter.test.ts": 1, + "tests/unit/plugin-register.test.ts": 1, + "tests/unit/resolve-inference-spec.test.ts": 1, + "tests/unit/telemetry-singleton.test.ts": 1, + "tests/unit/tui/agent-source-sync.test.ts": 1, + "tests/unit/tui/mcp-result-format.test.ts": 1, + "tests/unit/tui/theme.test.ts": 1, + "tests/unit/tui/tool-formatter-web-brand.test.ts": 1, + "tests/unit/tui/url-links.test.ts": 1, + "tests/unit/workflow-kickoff.test.ts": 1, + "tests/unit/workflows-capabilities.test.ts": 1, + "src/agent/directors/counsel/package.test.ts": 0, + "src/inference-abort.test.ts": 0, + "src/plugins/origin-marker.test.ts": 0, + "src/session/active-run.test.ts": 0, + "src/session/archive-uri.test.ts": 0, + "src/subagent/fleet-report.ask-wake.test.ts": 0, + "src/subagent/provider-family.test.ts": 0, + "src/tui/exit-command.test.ts": 0, + "src/tui/notice-line.test.ts": 0, + "src/tui/plugin-surface.test.ts": 0, + "src/tui/stall-watchdog.test.ts": 0, + "src/web/secret-scrub.test.ts": 0, + "tests/unit/codex-providers.test.ts": 0, + "tests/unit/inference-abort.test.ts": 0, + "tests/unit/mcp-stdio-env.test.ts": 0, + "tests/unit/provider-protocol-flags.test.ts": 0, + "tests/unit/run-agent.test.ts": 0, + "tests/unit/session/run-sink-exec-status.test.ts": 0, + "tests/unit/xai-usage.test.ts": 0 + } +} diff --git a/tests/unit/check-gate.test.ts b/tests/unit/check-gate.test.ts index 882e22c9f..54a869a5a 100644 --- a/tests/unit/check-gate.test.ts +++ b/tests/unit/check-gate.test.ts @@ -86,20 +86,31 @@ describe("check gate", () => { }); test("CI test shards cover exactly the suite's paths", () => { + // Time-balanced --shard slices each run the full union (bun partitions + // files across the slices, balanced by --timings), so coverage holds + // when the matrix has all four slices and the shard command template + // carries the suite's paths plus the interpolated --shard flag. // Sharding must never silently drop (or double-run) part of the suite: - // expanding the matrix shards' filters to test files has to equal the - // unsharded `test` script's paths expanded the same way. Subdirectory - // shards (src-a/b/c) can never equal the literal ./src string, so this - // compares sorted file sets; a file covered twice fails the equality - // through the duplicate entry. - const shardFilters = [...ci.matchAll(/^\s+paths: (.+)$/gm)].flatMap( - (match) => match[1]?.trim().split(/\s+/) ?? [], - ); + // expanding the template's filters to test files has to equal the + // unsharded `test` script's paths expanded the same way. + for (const shard of ["1/4", "2/4", "3/4", "4/4"]) { + expect(ci).toContain(`"${shard}"`); + } + const runArgs = [ + ...ci.matchAll(/^\s*run: bun run check:projects-dir-guard(.+)$/gm), + ] + .map((match) => match[1] ?? "") + .find((args) => args.includes("--shard=${{ matrix.shard }}")); + expect(runArgs).toBeDefined(); + const shardFilters = (runArgs ?? "") + .split(/\s+/) + .filter((part) => part.startsWith("./")); const suiteFilters = TEST_SUITE.split(" ").filter((part) => part.startsWith("./"), ); expect(expandToTestFiles(shardFilters)).toEqual( expandToTestFiles(suiteFilters), ); + expect(runArgs).toContain("--timings=./scripts/ci-timings.json"); }); });