fix(mcp): make explore guidance advisory (#1504) (#1805)

Apply upstream commit 1572d90d7174f0c65019e768c8f65728fae668c5 from
PR #1570 by @rongbc.

Clarify that suggested explore call counts are advisory and extra calls
remain available. Sync the MCP initialization guidance and eval probe,
retain the upstream regression tests, close the footer's bold formatting,
and credit the fix under Unreleased in the changelog.

Validation: npm run build; focused Vitest (3 files, 53 tests); source and
dist wording greps; git diff --check.

Fixes #1504

Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
Colby Mchenry
2026-09-08 18:28:36 -05:00
committed by GitHub
co-authored by Colby McHenry
parent 1f4379d95f
commit e79d5df448
6 changed files with 40 additions and 11 deletions
+21 -2
View File
@@ -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 (5004,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 () => {
+4 -3
View File
@@ -108,11 +108,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!);
expectExploreAlwaysLoad(got);
});