Ship native subagent definitions for GitHub Copilot and Cursor

The github and cursor providers previously received only the generated
degraded/ inline fallbacks. Both harnesses support real custom subagents,
so the build now emits them from the same skill/agents/ source:

- GitHub Copilot: .github/agents/impeccable-<role>.agent.md with portable
  frontmatter only (name + description; omitting tools grants all tools,
  and Copilot has no documented model/effort/max-turns equivalents).
- Cursor: .cursor/agents/impeccable-<role>.md with name, description,
  model: inherit, is_background: false, and readonly derived from the
  agent's tool list (true only for the finish reviewer, which declares
  neither Write nor Edit). effort/max-turns are skipped because Cursor's
  effort option requires an explicit model id.

Agent bodies now also resolve {{scripts_path}} and strip rule markers in
the shared agentFormat pipeline, which fixes the previously unresolved
placeholder in the emitted Claude asset-producer agent.

The CLI installer places agents per scope: project installs write
<repo>/.github/agents/ and <repo>/.cursor/agents/; user-level installs
write ~/.copilot/agents/ (Copilot's user dir, not ~/.github/) and
~/.cursor/agents/, overwriting stale impeccable-* copies. Because
Copilot lets user-level agents shadow same-named project ones, a project
install warns when shadowing copies exist; Cursor gives project agents
precedence, so no warning there.

new-work.md and visualize.md extend their harness-naming clauses with
the Cursor and Copilot invocations. The degraded/ fallbacks keep
shipping for surfaces where the model still fails to delegate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-28 16:16:57 -07:00
co-authored by Claude Fable 5
parent dedb8a1df2
commit fa1177ed9c
7 changed files with 397 additions and 3 deletions
+57
View File
@@ -136,6 +136,46 @@ function buildClaudeAgent(agent, body) {
return `${generateYamlFrontmatter(frontmatter)}\n${body.trim()}\n`;
}
// GitHub Copilot custom agents are markdown files named `<name>.agent.md`
// (project scope: `.github/agents/`; user scope: `~/.copilot/agents/`). Only
// the portable frontmatter fields are emitted: `name` and `description`.
// `tools` is omitted deliberately -- omitting it grants access to all tools,
// and Copilot's tool vocabulary differs from ours -- and Copilot has no
// documented model/effort/max-turns equivalents. VS Code-specific fields
// (handoffs, argument-hint) are ignored elsewhere, so none are emitted.
function buildCopilotAgent(agent, body) {
const frontmatter = {
name: agent.name,
description: agent.description,
};
return `${generateYamlFrontmatter(frontmatter)}\n${body.trim()}\n`;
}
// Cursor subagents are plain markdown files with YAML frontmatter (project
// scope: `.cursor/agents/`; user scope: `~/.cursor/agents/`). Fields: name,
// description (drives auto-delegation), model (`inherit` maps directly to our
// value), readonly, is_background. `readonly` is derived from the agent's own
// tool list: a role that declares tools but neither Write nor Edit is a
// reader, and Cursor can enforce that. effort/max-turns are skipped: Cursor's
// effort option requires an explicit model id, incompatible with `inherit`.
function buildCursorAgent(agent, body) {
const frontmatter = {
name: agent.name,
description: agent.description,
model: agent.model || 'inherit',
};
const tools = String(agent.tools || '').split(',').map(t => t.trim()).filter(Boolean);
if (tools.length > 0 && !tools.includes('Write') && !tools.includes('Edit')) {
frontmatter.readonly = true;
}
// The parent thread waits on each role's return; none of these run detached.
frontmatter.is_background = false;
return `${generateYamlFrontmatter(frontmatter)}\n${body.trim()}\n`;
}
function buildAgentFile(config, agent, body) {
if (config.agentFormat === 'codex-toml') {
return {
@@ -151,6 +191,20 @@ function buildAgentFile(config, agent, body) {
};
}
if (config.agentFormat === 'copilot-agent-md') {
return {
filename: `${agent.name}.agent.md`,
content: buildCopilotAgent(agent, body),
};
}
if (config.agentFormat === 'cursor-md') {
return {
filename: `${agent.name}.md`,
content: buildCursorAgent(agent, body),
};
}
return null;
}
@@ -316,12 +370,15 @@ export function createTransformer(config) {
if (config.agentFormat) {
const agentsDir = path.join(providerDir, `${configDir}/agents`);
for (const skill of skills) {
const scriptsPath = `${configDir}/skills/${skill.name}/scripts`;
for (const agent of skill.agents || []) {
// Agents can declare `providers: <list>` to limit which harnesses
// they emit to. Default (no field) ships everywhere with agentFormat.
if (agent.providers && !agent.providers.includes(provider)) continue;
let body = compileProviderBlocks(agent.body, providerTags);
body = replacePlaceholders(body, placeholderKey, [], allSkillNames);
body = stripRuleMarkers(body);
body = body.replace(/\{\{scripts_path\}\}/g, scriptsPath);
const agentFile = buildAgentFile(config, agent, body);
if (!agentFile) continue;
ensureDir(agentsDir);
+9
View File
@@ -16,6 +16,10 @@ export const PROVIDERS = {
configDir: '.cursor',
displayName: 'Cursor',
frontmatterFields: ['license', 'compatibility', 'metadata'],
// Cursor subagents: `.cursor/agents/<name>.md` at repo level,
// `~/.cursor/agents/` at user level. Project agents take precedence over
// user ones, so installs simply overwrite on update.
agentFormat: 'cursor-md',
emitHooks: 'cursor',
// Cursor reads `.cursor/hooks.json`, not `.cursor/hooks/hooks.json`.
hooksManifestRel: 'hooks.json',
@@ -68,6 +72,11 @@ export const PROVIDERS = {
displayName: 'GitHub Copilot',
placeholderProvider: 'agents',
frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata'],
// Copilot custom agents: `.github/agents/<name>.agent.md` at repo level,
// `~/.copilot/agents/` at user level (the CLI installer handles placement).
// The degraded/ fallbacks still ship for Copilot surfaces where the model
// fails to delegate; the .agent.md files are the real subagent path.
agentFormat: 'copilot-agent-md',
emitHooks: 'github',
// GitHub Copilot discovers repo-level hooks under `.github/hooks/*.json`.
hooksManifestRel: 'hooks/impeccable.json',