Skip to content
195 changes: 195 additions & 0 deletions packages/cli/src/commands/review/agent-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
findingsSection,
agentPromptCommand,
} from './agent-prompt.js';
import { BRIEFS } from './lib/agent-briefs.js';
import {
readRecordedPrompts,
briefPath,
Expand Down Expand Up @@ -3665,3 +3666,197 @@ describe('per-chunk retirement — cold territories stop costing a round', () =>
expect(msg).toContain('CONVERGED');
});
});

describe('the tool budget in the briefs', () => {
// The untyped literal exists so tests can spread it (`as never` cannot be
// spread); `budgetPlan` is the cast the builders take.
const budgetPlanObj = {
...PLAN,
// Role 0 refuses to build without a PR to check issues against.
prNumber: '6771',
ownerRepo: 'QwenLM/qwen-code',
files: [
{
path: 'big.ts',
kind: 'source',
heavy: true,
addedLines: 300,
removedLines: 100,
},
],
budget: {
inlineAngles: 4,
sweep: true,
specialistCap: 2,
verifyShard: 8,
agentToolBudget: 42,
},
};
const budgetPlan = budgetPlanObj as never;

it('scopes a chunk agent to its own territory, not the whole plan', () => {
// Chunk 13 is 217 lines / 9,000 chars: allowance min(plan 42, 30+217/20
// = 40) = 40, plus its reading list (brief + one diff page). Handing it
// the whole-diff number instead keeps exactly the wandering headroom the
// budget exists to cut.
expect(buildChunkAgentPrompt(budgetPlan, 13)).toContain(
'About **42 tool calls**',
);
// Chunk 14's 40,000 chars take two reads to page through: brief + two
// pages ride on top of its 38-call allowance.
expect(buildChunkAgentPrompt(budgetPlan, 14)).toContain(
'About **41 tool calls**',
);
});

it('an UNCOVERABLE chunk gets no budget block at all', () => {
// Chunk 15's instruction is to return the exact `Uncoverable:` line and
// stop. A budget block telling it to "write your findings from the
// evidence in hand" beside that is two contradicting masters — and an
// agent following the budget's format never matches the uncoverable
// parser, turning a disclosed gap into a hard coverage failure.
const p = buildChunkAgentPrompt(budgetPlan, 15);
expect(p).toContain('Uncoverable: chunk 15');
expect(p).not.toContain('Tool budget');
});

it('gives a whole-diff role the plan allowance plus its reading list', () => {
// 42 from the plan + its brief + one read per chunk (3).
for (const role of ['1a', '2', '6b', 'reverse-audit'] as const) {
expect(buildRoleBrief(budgetPlan, role)).toContain(
'About **46 tool calls**',
);
}
});

it('a chunk-scoped reverse auditor gets its chunk, not the diff', () => {
// Chunk 13's 40-call allowance + brief + one diff page + the cumulative
// findings list its brief orders read in full (measured 65-82 KB).
expect(
buildRoleBrief(budgetPlan, 'reverse-audit', { chunk: 13 }),
).toContain('About **45 tool calls**');
});

it('an invariant agent budgets on its file, reads scaled by its size', () => {
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
// 300 added + 100 removed lines: territory allowance min(42, 30+400/20
// = 50) = 42, plus reads max(4, 2 + ceil(300/500)) = 4. The reads floor
// at the old flat 4 and grow with the added lines a heavy rewrite pages
// through — a flat count once told a 400 KB file's agent its mandatory
// paging was already overspending.
expect(
buildRoleBrief(budgetPlan, 'invariant-a', { file: 'big.ts' }),
).toContain('About **46 tool calls**');
});

it('an Agent 8 specialist is budgeted like any other whole-diff finder', () => {
// Specialists launch through buildWholeDiffBlock (its one consumer);
// without this they were the one launch class that could still wander
// unbudgeted. Its domain brief is appended inline, so its reading list
// is the diff pages alone.
expect(buildWholeDiffBlock(budgetPlan)).toContain(
'About **45 tool calls**',
);
});

it('budgets every role in BRIEFS except the ones declaring budgetExempt', () => {
// Walked from the runtime roster, not a hand-copied list: a role added
// later must DECLARE its exemption at its brief, where the reason lives,
// or it gets the ceiling — it cannot silently join the exempt set, and
// the exempt set itself is pinned below.
const roles = Object.keys(BRIEFS) as Array<keyof typeof BRIEFS>;
const budgeted = Object.fromEntries(
roles.map((role) => {
const opts = String(role).startsWith('invariant-')
? { file: 'big.ts' }
: {};
return [
role,
buildRoleBrief(budgetPlan, role, opts).includes('Tool budget'),
];
}),
);
expect(budgeted).toEqual(
Object.fromEntries(
roles.map((role) => [role, !BRIEFS[role].budgetExempt]),
),
);
const exempt = roles.filter((r) => BRIEFS[r].budgetExempt).sort();
expect(exempt).toEqual(['0', '7', 'verify']);
});

it.each([
['zero', 0],
['negative', -5],
['NaN', Number.NaN],
['Infinity', Number.POSITIVE_INFINITY],
['a string', '42'],
])('a plan whose ceiling is %s gets no ceiling at all', (_name, value) => {
// The plan is parsed off disk with an unchecked cast; a garbled field
// must fall back exactly like an absent one — toward more coverage —
// not render `About **NaN tool calls**` into a brief.
const garbled = {
...PLAN,
budget: { agentToolBudget: value },
} as never;
expect(buildChunkAgentPrompt(garbled, 13)).not.toContain('Tool budget');
expect(buildRoleBrief(garbled, '1a')).not.toContain('Tool budget');
});

it.each([
// A version-skewed or hand-edited plan: a positive-but-absurd value is
// clamped into the budget's own band, in both directions — 0.5 must not
// become a three-call brief, 100000 must not remove the ceiling.
['a fraction', 0.5, 34],
['oversized', 100_000, 64],
])(
'a plan whose ceiling is %s is clamped, not obeyed',
(_name, value, expected) => {
const skewed = {
...budgetPlanObj,
budget: { agentToolBudget: value },
} as never;
expect(buildRoleBrief(skewed, '1a')).toContain(
`About **${expected} tool calls**`,
);
},
);

it('a chunk entry missing lines and chars still renders finite numbers', () => {
// `chunkFrom` validates only startLine/endLine; the twin guard at the
// role-brief call site existed and this one did not — a malformed chunk
// must degrade to the scoped floor, never to `About **NaN tool calls**`
// and never to inheriting the whole-diff headroom.
const garbledChunk = {
...budgetPlanObj,
chunks: [
{
id: 16,
startLine: 1,
endLine: 2,
files: [{ path: 'a.ts', newStart: 1, newEnd: 2 }],
},
],
} as never;
const p = buildChunkAgentPrompt(garbledChunk, 16);
expect(p).not.toContain('NaN');
// Floor allowance 30 + brief + one page = 32 — not the whole-diff 42.
expect(p).toContain('About **32 tool calls**');
});

it('a plan without the field falls back to no ceiling — more coverage, never less', () => {
expect(buildChunkAgentPrompt(PLAN as never, 13)).not.toContain(
'Tool budget',
);
expect(buildRoleBrief(PLAN as never, '1a')).not.toContain('Tool budget');
});

it('restates the recall rule and fixes the disclosure format', () => {
// Self-contained on purpose — a chunk brief has no RECALL section, so
// the sentence must carry the rule instead of citing it; and without
// the fixed format, check-coverage has nothing to parse.
const brief = buildRoleBrief(budgetPlan, '1a');
expect(brief).toContain('never suppresses a finding');
expect(brief).toContain('Budget gap: <the check>');
expect(brief).not.toContain('as the recall rule requires');
});
});
Loading
Loading