mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
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:
co-authored by
Claude Fable 5
parent
dedb8a1df2
commit
fa1177ed9c
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user