fix(#1284): grant mcp__perplexity__* to researcher agents + dispatch-table parity guard (#1288)

Adds mcp__perplexity__* to both researcher profiles (generated source-of-truth) and regenerates the agents; adds a generative dispatch-table↔tools parity guard so future provider drift fails CI. Regenerates the agent-size baseline for the +20-byte frontmatter growth.

Fixes #1284

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-15 17:20:08 -04:00
committed by GitHub
parent 645601a10d
commit b108f101b0
6 changed files with 64 additions and 6 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1288
---
**Researcher agents can now invoke Perplexity** — `gsd-phase-researcher` and `gsd-project-researcher` referenced `mcp__perplexity__*` in their provider dispatch tables but never granted it in their `tools:` allowlist, so Perplexity web research silently fell through to the next provider. The grant is now generated from the researcher profiles, with a parity guard that fails if a future dispatch-table provider is added without its tool grant. (#1284)

View File

@@ -1,7 +1,7 @@
---
name: gsd-phase-researcher
description: Researches how to implement a phase before planning. Produces RESEARCH.md consumed by gsd-planner. Spawned by /gsd:plan-phase orchestrator.
tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*
tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__*
color: cyan
# hooks:
# PostToolUse:

View File

@@ -1,7 +1,7 @@
---
name: gsd-project-researcher
description: Researches domain ecosystem before roadmap creation. Produces files in .planning/research/ consumed during roadmap creation. Spawned by /gsd:new-project or /gsd:new-milestone orchestrators.
tools: Read, Write, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*
tools: Read, Write, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__*
color: cyan
# hooks:
# PostToolUse:

View File

@@ -24,7 +24,7 @@ const PROFILES = [
'Researches domain ecosystem before roadmap creation. Produces files in .planning/research/ consumed during roadmap creation. Spawned by /gsd:new-project or /gsd:new-milestone orchestrators.',
color: 'cyan',
tools:
'Read, Write, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
'Read, Write, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__*',
requiredIncludes: [
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
'@~/.claude/gsd-core/references/research-philosophy.md',
@@ -46,7 +46,7 @@ const PROFILES = [
'Researches how to implement a phase before planning. Produces RESEARCH.md consumed by gsd-planner. Spawned by /gsd:plan-phase orchestrator.',
color: 'cyan',
tools:
'Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
'Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__*',
requiredIncludes: [
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
'@~/.claude/gsd-core/references/research-philosophy.md',

View File

@@ -21,10 +21,10 @@
"gsd-mempalace-curator.md": 4160,
"gsd-nyquist-auditor.md": 7255,
"gsd-pattern-mapper.md": 12487,
"gsd-phase-researcher.md": 40618,
"gsd-phase-researcher.md": 40638,
"gsd-plan-checker.md": 42003,
"gsd-planner.md": 48892,
"gsd-project-researcher.md": 21994,
"gsd-project-researcher.md": 22014,
"gsd-research-synthesizer.md": 13653,
"gsd-roadmapper.md": 21781,
"gsd-security-auditor.md": 6226,

View File

@@ -116,3 +116,56 @@ describe('Researcher agent MCP tool set parity: new tools match exa/firecrawl pa
});
}
});
// --- Regression (#1284): every MCP-backed provider named in the Step-C
// dispatch table must be granted in the agent's frontmatter `tools:` line.
// Guards against a provider being added to the waterfall + dispatch table
// without the matching mcp__<server>__* grant (the perplexity drift). ---
describe('researcher Step-C dispatch ↔ tools frontmatter parity (#1284)', () => {
const RESEARCHERS = ['gsd-phase-researcher', 'gsd-project-researcher'];
function mcpServersIn(text) {
const servers = new Set();
const re = /mcp__([a-z0-9]+)__/gi;
let m;
while ((m = re.exec(text)) !== null) servers.add(m[1].toLowerCase());
return servers;
}
function readAgent(name) {
return fs.readFileSync(path.join(__dirname, '..', 'agents', `${name}.md`), 'utf8');
}
function toolsLine(content) {
const m = content.match(/^tools:\s*(.+)$/m);
assert.ok(m, 'agent frontmatter must have a tools: line');
return m[1];
}
function stepCTableRows(content) {
const start = content.indexOf('### Step C');
assert.ok(start !== -1, 'agent must have a "### Step C" dispatch section');
const rest = content.slice(start + 1);
const nextHeading = rest.indexOf('\n### ');
const section = nextHeading === -1 ? rest : rest.slice(0, nextHeading);
// Only the markdown dispatch-table rows (| provider | mcp tool |) define
// provider->tool mappings. Generic fallback prose (e.g. `mcp__<provider>__*`)
// is intentionally excluded so it cannot create false positives.
return section
.split('\n')
.filter((line) => line.trimStart().startsWith('|'))
.join('\n');
}
for (const name of RESEARCHERS) {
test(`${name}: grants mcp__<server>__* for every MCP provider in its Step-C table`, () => {
const content = readAgent(name);
const granted = mcpServersIn(toolsLine(content));
const referenced = mcpServersIn(stepCTableRows(content));
assert.ok(referenced.size > 0,
`${name} Step-C table should reference at least one mcp__ provider`);
const missing = [...referenced].filter((srv) => !granted.has(srv));
assert.deepStrictEqual(missing, [],
`${name}: Step-C references MCP provider(s) not granted in tools: frontmatter: ` +
`${missing.join(', ')}. Add mcp__<server>__* to the profile in ` +
`scripts/research-profiles.cjs and regenerate.`);
});
}
});