mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-21 08:47:04 +08:00
fix(mcp): reword explore guidance as advisory, not a quota
The trailing explore note said 'Explore budget: N calls ... spend your remaining calls ... Synthesize once you've used N' — agents read it as a hard quota, stopped exploring early, and fell back to grep+Read (more tokens, less complete). Reword it as explicit advisory guidance: the server never rejects or rate-limits extra explore calls, and exploring further is encouraged when the response doesn't cover the flow.
The tool schema description carried the same quota language ('Budget: make at most N calls for this project') — the higher-salience text the agent reads before ever calling. Reword it identically: advisory only, NOT a quota.
Also sync the eval probe string (probe-suite-envelope.mjs) and pin the new wording in tests with anti-regression assertions (medium tier must contain 'NOT a quota' and never 'remaining calls'/'Synthesize once'/'make at most').
This commit is contained in:
committed by
rongbaichuan
parent
c6aaa20358
commit
1572d90d71
@@ -6,7 +6,7 @@
|
||||
* grep+Read. These tests pin the per-tier budget shape so future tuning
|
||||
* doesn't silently drift the small-project case back into bloat.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
@@ -198,7 +198,26 @@ describe('codegraph_explore output respects the adaptive budget', () => {
|
||||
const text = result.content?.[0]?.text ?? '';
|
||||
expect(text).not.toContain('### Additional relevant files');
|
||||
expect(text).not.toContain('Complete source code is included above');
|
||||
expect(text).not.toContain('Explore budget:');
|
||||
expect(text).not.toContain('advisory only, NOT a quota');
|
||||
});
|
||||
|
||||
it('emits advisory-only exploration guidance on medium projects — never quota wording', async () => {
|
||||
// Medium tier (500–4,999 files) turns the guidance note on. The synthetic
|
||||
// project is tiny, so fake the stats to land in that tier — the note's
|
||||
// WORDING is what this test pins. Regression guard: quota phrasing
|
||||
// ("remaining calls" / "Synthesize once") must never come back — agents
|
||||
// read it as a hard cap, stop exploring early, and fall back to grep+Read.
|
||||
const spy = vi.spyOn(cg, 'getStats').mockReturnValue({ fileCount: 1000 } as ReturnType<CodeGraph['getStats']>);
|
||||
try {
|
||||
const result = await handler.execute('codegraph_explore', { query: 'Session method helper' });
|
||||
const text = result.content?.[0]?.text ?? '';
|
||||
expect(text).toContain('advisory only, NOT a quota');
|
||||
expect(text).toContain('extra calls are never rejected or rate-limited');
|
||||
expect(text).not.toContain('remaining calls');
|
||||
expect(text).not.toContain('Synthesize once');
|
||||
} finally {
|
||||
spy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('still includes the Relationships section — it is the cheapest structural signal', async () => {
|
||||
|
||||
@@ -95,11 +95,12 @@ describe('Live tool surface keeps annotations with a project open (#1018)', () =
|
||||
expect(got.length).toBeGreaterThan(0);
|
||||
for (const tool of got) expectReadOnly(tool);
|
||||
|
||||
// explore's description is regenerated with a per-repo budget suffix via
|
||||
// object spread; the annotation must survive that rewrite.
|
||||
// explore's description is regenerated with a per-repo advisory-guidance
|
||||
// suffix via object spread; the annotation must survive that rewrite.
|
||||
const explore = got.find((t) => t.name === 'codegraph_explore');
|
||||
expect(explore).toBeDefined();
|
||||
expect(explore!.description).toMatch(/Budget: make at most/);
|
||||
expect(explore!.description).toMatch(/advisory only, NOT a quota/);
|
||||
expect(explore!.description).not.toMatch(/make at most/);
|
||||
expectReadOnly(explore!);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ try {
|
||||
epilogueCut: text.includes('omitted for size'),
|
||||
sectionCut: text.includes('output truncated to budget'),
|
||||
notShown: text.includes('Not shown above'),
|
||||
budgetNote: text.includes('**Explore budget:'),
|
||||
budgetNote: text.includes('advisory only, NOT a quota'),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
|
||||
+11
-4
@@ -207,7 +207,10 @@ export interface ExploreOutputBudget {
|
||||
includeAdditionalFiles: boolean;
|
||||
/** Include the "Complete source code is included above…" reminder. */
|
||||
includeCompletenessSignal: boolean;
|
||||
/** Include the explore-budget reminder at the end. */
|
||||
/**
|
||||
* Include the advisory exploration-guidance note at the end. Purely
|
||||
* advisory — the server NEVER rejects or rate-limits extra explore calls.
|
||||
*/
|
||||
includeBudgetNote: boolean;
|
||||
}
|
||||
|
||||
@@ -1490,7 +1493,7 @@ export class ToolHandler {
|
||||
if (tool.name === 'codegraph_explore') {
|
||||
return {
|
||||
...tool,
|
||||
description: `${tool.description} Budget: make at most ${budget} calls for this project (${stats.fileCount.toLocaleString()} files indexed).`,
|
||||
description: `${tool.description} Exploration guidance — advisory only, NOT a quota: ~${budget} focused calls usually cover this project (${stats.fileCount.toLocaleString()} files indexed), and extra calls are never rejected or rate-limited.`,
|
||||
};
|
||||
}
|
||||
return tool;
|
||||
@@ -5687,13 +5690,17 @@ export class ToolHandler {
|
||||
? ['', `> Some file sections were trimmed for size. For a specific symbol you still need, run another \`codegraph_explore\` (or \`codegraph_node\`) with its exact name — line-numbered source, cheaper and more complete than Read.`]
|
||||
: [];
|
||||
|
||||
// Explore budget note based on project size.
|
||||
// Advisory exploration-guidance note based on project size. Deliberately
|
||||
// phrased as guidance, NOT a quota: agents read "budget / remaining calls /
|
||||
// Synthesize once" as a hard cap and stop exploring early, falling back to
|
||||
// grep + Read (which costs more tokens). The server never rejects or
|
||||
// rate-limits extra explore calls, and the note says so explicitly.
|
||||
let budgetBlock: string[] = [];
|
||||
if (budget.includeBudgetNote) {
|
||||
try {
|
||||
const stats = cg.getStats();
|
||||
const callBudget = getExploreBudget(stats.fileCount);
|
||||
budgetBlock = ['', `> **Explore budget: ${callBudget} calls for this project (${stats.fileCount.toLocaleString()} files indexed).** Each call covers ~6 files; if your question spans more, spend your remaining calls on the uncovered area BEFORE falling back to Read — another explore is cheaper and more complete than reading those files. Synthesize once you've used ${callBudget}.`];
|
||||
budgetBlock = ['', `> **Exploration guidance — advisory only, NOT a quota: this project (~${stats.fileCount.toLocaleString()} files indexed) is usually covered in ≈${callBudget} focused explore calls, and extra calls are never rejected or rate-limited. If the response above does not fully cover your question, run another codegraph_explore on the uncovered symbols — it is cheaper and more complete than Read. Only stop exploring when the response actually covers the flow you asked about.`];
|
||||
} catch {
|
||||
// Stats unavailable — skip budget note
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user