Skip to content

Commit 3f17932

Browse files
committed
Reduce token usage when run through LLMs.
1 parent fafabc3 commit 3f17932

File tree

10 files changed

+139
-4
lines changed

10 files changed

+139
-4
lines changed

e2e/runJest.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,20 @@ function spawnJest(
9393
}
9494
const env: NodeJS.ProcessEnv = {
9595
...process.env,
96+
// Prevent AI agent detection from leaking into child processes, which
97+
// would activate the AgentReporter and break output-dependent e2e tests.
98+
AI_AGENT: undefined,
99+
AUGMENT_AGENT: undefined,
100+
CLAUDECODE: undefined,
101+
CLAUDE_CODE: undefined,
102+
CODEX_SANDBOX: undefined,
103+
CODEX_THREAD_ID: undefined,
104+
CURSOR_AGENT: undefined,
96105
FORCE_COLOR: '0',
106+
GEMINI_CLI: undefined,
107+
GOOSE_PROVIDER: undefined,
97108
NO_COLOR: '1',
109+
OPENCODE: undefined,
98110
...options.env,
99111
};
100112

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"jest": {
3+
"setupFilesAfterEnv": [
4+
"./setupHooksIntoRunner.js"
5+
]
6+
}
7+
}

jest.config.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ export default {
7474
],
7575
testTimeout: 70_000,
7676
transform: {
77-
'\\.[jt]sx?$': require.resolve('babel-jest'),
77+
'\\.[cm]?[jt]sx?$': require.resolve('babel-jest'),
7878
},
79+
transformIgnorePatterns: ['/node_modules/(?!std-env/)'],
7980
watchPathIgnorePatterns: [
8081
'coverage',
8182
'<rootDir>/packages/jest-worker/src/workers/__tests__/__temp__',

packages/jest-config/src/normalize.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,9 @@ const normalizeReporters = ({
377377
normalizedReporterConfig[0],
378378
);
379379

380-
if (!['default', 'github-actions', 'summary'].includes(reporterPath)) {
380+
if (
381+
!['agent', 'default', 'github-actions', 'summary'].includes(reporterPath)
382+
) {
381383
const reporter = Resolver.findNodeModule(reporterPath, {
382384
basedir: rootDir,
383385
});

packages/jest-core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"jest-validate": "workspace:*",
4141
"jest-watcher": "workspace:*",
4242
"pretty-format": "workspace:*",
43-
"slash": "^3.0.0"
43+
"slash": "^3.0.0",
44+
"std-env": "^4.0.0-rc.1"
4445
},
4546
"devDependencies": {
4647
"@jest/test-sequencer": "workspace:*",

packages/jest-core/src/TestScheduler.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import chalk from 'chalk';
99
import {GITHUB_ACTIONS} from 'ci-info';
1010
import exit from 'exit-x';
11+
import {isAgent} from 'std-env';
1112
import {
13+
AgentReporter,
1214
CoverageReporter,
1315
DefaultReporter,
1416
GitHubActionsReporter,
@@ -350,11 +352,17 @@ class TestScheduler {
350352

351353
async _setupReporters() {
352354
const {collectCoverage: coverage, notify, verbose} = this._globalConfig;
353-
const reporters = this._globalConfig.reporters || [['default', {}]];
355+
const reporters = this._globalConfig.reporters || [
356+
[isAgent ? 'agent' : 'default', {}],
357+
];
354358
let summaryOptions: SummaryReporterOptions | null = null;
355359

356360
for (const [reporter, options] of reporters) {
357361
switch (reporter) {
362+
case 'agent':
363+
summaryOptions = options;
364+
this.addReporter(new AgentReporter(this._globalConfig));
365+
break;
358366
case 'default':
359367
summaryOptions = options;
360368
this.addReporter(

packages/jest-core/src/__tests__/TestScheduler.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {
10+
AgentReporter,
1011
CoverageReporter,
1112
DefaultReporter,
1213
GitHubActionsReporter,
@@ -22,6 +23,11 @@ import * as runGlobalHook from '../runGlobalHook';
2223

2324
jest
2425
.mock('ci-info', () => ({GITHUB_ACTIONS: true}))
26+
.mock('std-env', () => ({
27+
get isAgent() {
28+
return globalThis.__TEST_IS_AGENT__ || false;
29+
},
30+
}))
2531
.mock('@jest/reporters')
2632
.mock(
2733
'/custom-reporter.js',
@@ -88,6 +94,25 @@ describe('reporters', () => {
8894
expect(SummaryReporter).toHaveBeenCalledTimes(1);
8995
});
9096

97+
test('uses agent reporter when AI agent is detected', async () => {
98+
globalThis.__TEST_IS_AGENT__ = true;
99+
try {
100+
await createTestScheduler(
101+
makeGlobalConfig({
102+
reporters: undefined,
103+
}),
104+
{},
105+
{},
106+
);
107+
108+
expect(AgentReporter).toHaveBeenCalledTimes(1);
109+
expect(DefaultReporter).toHaveBeenCalledTimes(0);
110+
expect(SummaryReporter).toHaveBeenCalledTimes(1);
111+
} finally {
112+
globalThis.__TEST_IS_AGENT__ = false;
113+
}
114+
});
115+
91116
test('does not enable any reporters, if empty list is passed', async () => {
92117
await createTestScheduler(
93118
makeGlobalConfig({
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import type {
9+
AggregatedResult,
10+
Test,
11+
TestCaseResult,
12+
TestResult,
13+
} from '@jest/test-result';
14+
import BaseReporter from './BaseReporter';
15+
import DefaultReporter from './DefaultReporter';
16+
import type {ReporterOnStartOptions} from './types';
17+
18+
/**
19+
* A reporter optimized for AI coding agents that reduces token usage by only
20+
* printing failing tests and the final summary. Automatically activated when
21+
* an AI agent environment is detected (via the AI_AGENT env var or std-env).
22+
*/
23+
export default class AgentReporter extends DefaultReporter {
24+
static override readonly filename = __filename;
25+
26+
/* eslint-disable @typescript-eslint/no-empty-function */
27+
protected override __wrapStdio(): void {}
28+
protected override __clearStatus(): void {}
29+
protected override __printStatus(): void {}
30+
override onTestStart(_test: Test): void {}
31+
override onTestCaseResult(
32+
_test: Test,
33+
_testCaseResult: TestCaseResult,
34+
): void {}
35+
/* eslint-enable */
36+
37+
override onRunStart(
38+
aggregatedResults: AggregatedResult,
39+
options: ReporterOnStartOptions,
40+
): void {
41+
BaseReporter.prototype.onRunStart.call(this, aggregatedResults, options);
42+
}
43+
44+
override onTestResult(
45+
test: Test,
46+
testResult: TestResult,
47+
aggregatedResults: AggregatedResult,
48+
): void {
49+
this.testFinished(test.context.config, testResult, aggregatedResults);
50+
51+
// Only print output for test files that have failures.
52+
if (
53+
!testResult.skipped &&
54+
(testResult.numFailingTests > 0 || testResult.testExecError)
55+
) {
56+
this.printTestFileHeader(
57+
testResult.testFilePath,
58+
test.context.config,
59+
testResult,
60+
);
61+
this.printTestFileFailureMessage(
62+
testResult.testFilePath,
63+
test.context.config,
64+
testResult,
65+
);
66+
}
67+
68+
this.forceFlushBufferedOutput();
69+
}
70+
}

packages/jest-reporters/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type {
2323
TestResult,
2424
} from '@jest/test-result';
2525
export type {Config} from '@jest/types';
26+
export {default as AgentReporter} from './AgentReporter';
2627
export {default as BaseReporter} from './BaseReporter';
2728
export {default as CoverageReporter} from './CoverageReporter';
2829
export {default as DefaultReporter} from './DefaultReporter';

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4281,6 +4281,7 @@ __metadata:
42814281
jest-watcher: "workspace:*"
42824282
pretty-format: "workspace:*"
42834283
slash: "npm:^3.0.0"
4284+
std-env: "npm:^4.0.0-rc.1"
42844285
peerDependencies:
42854286
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
42864287
peerDependenciesMeta:
@@ -20669,6 +20670,13 @@ __metadata:
2066920670
languageName: node
2067020671
linkType: hard
2067120672

20673+
"std-env@npm:^4.0.0-rc.1":
20674+
version: 4.0.0-rc.1
20675+
resolution: "std-env@npm:4.0.0-rc.1"
20676+
checksum: 10/e3bdb5ca4a2d17e9ef402524b0f781557dd5ad3aa917676249a3b8bf961b75dfeaf287017a0826f1049eb8550ed87f7135d26e06cdca09ba50bc3959b1b70211
20677+
languageName: node
20678+
linkType: hard
20679+
2067220680
"stop-iteration-iterator@npm:^1.1.0":
2067320681
version: 1.1.0
2067420682
resolution: "stop-iteration-iterator@npm:1.1.0"

0 commit comments

Comments
 (0)