Browse documentation
DocsReference

Targets

Choose your tools in targets: in agnostic-ai.yaml. Use the capability matrix to check a spec kind, then the per-target output sections for exact paths and options. Target selection covers defaults and filters; global output covers personal configuration.

Directory-specific instructions

Rules with scope use native file conditions or nested instruction documents on 19 targets. Six targets skip them because native scope or its serialized format is unverified. See the scope matrix and compatibility rules before combining tools. The per-target layouts below describe ordinary rules unless a scoped case is stated.

Entry-point files

sync writes .agnostic-ai/AGNOSTIC_AI.md plus a root entry-point file per enabled target. All entry-point files share the same canonical pointer body. Editing one in place is a no-op (overwritten on next sync), and switching tools never surfaces inconsistent conventions.

TargetEntry-point file
claudeCLAUDE.md
codexAGENTS.md
ampAGENTS.md
warpAGENTS.md
clineAGENTS.md
windsurfAGENTS.md
junieAGENTS.md
kiroAGENTS.md
crushAGENTS.md
traeAGENTS.md
julesAGENTS.md
gooseAGENTS.md
augmentAGENTS.md
qoderAGENTS.md
openhandsAGENTS.md
factoryAGENTS.md
kiloAGENTS.md
opencodeAGENTS.md
geminiGEMINI.md
aiderCONVENTIONS.md
copilot.github/copilot-instructions.md
zed.rules
antigravity.agent/AGENTS.md

Targets sharing a path (codex, amp, warp, cline, windsurf, junie, kiro, crush, trae, jules, goose, augment, qoder, openhands, factory, kilo, and opencode at AGENTS.md) write it once; dedup is automatic. Targets absent from the table above (cursor, continue) have no root entry-point: they emit only per-file artifacts under their own directory.

Windsurf joined that group in the 2026-09-03 target audit (#645). Devin CLI's supported-file-names table rows AGENTS.md as "Recommended" and the page states "Devin CLI reads this file automatically" (docs.devin.ai/cli/extensibility/rules); the Cascade side reads it too. Before this, a windsurf-only repo got no root entry-point at all and could not dedupe with the other AGENTS.md consumers, so an unscoped rule reached Devin through no path whatsoever once the scoped-rule fix (#628) landed.

OpenCode joined that group in the 2026-08-27 target audit (#623). It used to write .opencode/AGENTS.md, a path no OpenCode doc or code path names: the vendor's lookup is an upward walk from the current directory for files called exactly AGENTS.md (opencode.ai/docs/rules), which its own packages/core/src/instruction-context.ts on branch dev spells fs.up({ targets: ["AGENTS.md"] }). A managed leftover at the old path is swept on the next sync; a hand-authored one stays. import opencode still reads the old path when the root file is absent, so a project that has not re-synced keeps its rules.

Warp still reads its legacy WARP.md filename, and reads it first: "If both WARP.md and AGENTS.md exist in the same directory, WARP.md takes priority" (docs.warp.dev/agents/capabilities/rules, target-audit 2026-09-08, #691). sync renames a previously agnostic-generated WARP.md to WARP.md.bak on upgrade so AGENTS.md takes over, but a hand-authored WARP.md (no Generated by agnostic-ai marker) is left in place since it may hold real user content, and it still shadows the AGENTS.md sync just wrote. sync warns in that case instead of delivering the rules silently nowhere: rename or remove the file to restore delivery.

Junie is the one target with a second, preferred entry-point file. Its guidelines lookup is strict precedence, first match wins, not a merge: .junie/AGENTS.md, then the root AGENTS.md above, then the legacy .junie/guidelines.md / .junie/guidelines/. sync always writes .junie/AGENTS.md, so step 1 always matches and everything after it is unreachable in a synced project (target-audit 2026-08-08, #552). Rule bodies therefore inline directly into .junie/AGENTS.md, under a sentinel-marked block (see the Junie section below): the only file Junie itself ever reads. Agents no longer inline anywhere (target-audit 2026-08-11, #604): they emit to their own native .junie/agents/<name>.md file instead (see below), so with sync.target-overview off, .junie/AGENTS.md and the shared root AGENTS.md above now render byte-identical content whenever another AGENTS.md-family target is also enabled (same canonical pointer body, same inlined-rules block).

Zed is the other target off the shared path, for a different reason. Zed reads the first matching file from a fixed list and stops, no merge: .rules, .cursorrules, .windsurfrules, .clinerules, .github/copilot-instructions.md, AGENT.md, AGENTS.md, CLAUDE.md, GEMINI.md (zed.dev/docs/ai/instructions). Copilot's entry-point sits at rank 5 and carries the pointer body alone, since Copilot delivers its rules through .github/instructions/. AGENTS.md sits at rank 7. So a project syncing both targets handed Zed a file with no rule bodies in it, and every rule silently stopped applying (target-audit 2026-08-27, #624). sync writes Zed's entry-point to .rules instead. Rank 1 cannot be shadowed by anything agnostic-ai emits.

Targets with no native rules directory (codex, amp, warp, zed, gemini, aider, opencode, crush, jules, goose, openhands, factory, junie) inline unscoped rule bodies into their entry-point file under a sentinel-marked ## Rules block, after the pointer body. That file is the only always-on context surface these tools read, so the rule reaches them by default. The block is identical across targets that share a path, so the dedup still holds. import strips the block, keeping the AGNOSTIC_AI.md round-trip lossless. Junie and Zed are the two members of this list whose entry-point file (.junie/AGENTS.md and .rules) is not the shared path the others write. Augment now also has a native .augment/rules/<name>.md directory (see below) but keeps inlining into AGENTS.md too: the vendor does not cleanly establish the two surfaces' relative precedence, so this adapter does not assume the native directory makes the inline copy redundant. Kilo Code now also has a native .kilo/rules/<name>.md directory (see below), referenced from kilo.jsonc's instructions array, which outranks AGENTS.md in Kilo Code's own documented precedence order (agent prompt > project instructions > AGENTS.md > global); this adapter keeps inlining into AGENTS.md too since AGENTS.md is always loaded when present regardless, so the inline copy is a fallback layer, not dead weight. OpenHands now also emits a rule that carries globs/paths or a source-layout/frontmatter scope as a native path-triggered rule at .agents/skills/<name>/SKILL.md (see below); an always-on rule still inlines into AGENTS.md only, since path-triggering needs the glob to scope it.

Set outputs.<target>.rules-file: <path> to use the legacy concatenated rules layout instead. The adapter writes a single merged document at <path> and sync skips the pointer-body write for that target so they do not collide.

Set sync.target-overview: true to append a generated section to each entry-point file listing where that tool's generated artifacts live (rules dir, MCP file, ...). The canonical body stays identical across targets; only the appendix differs per file. See configuration.

Capability matrix

25 targets, 10 capabilities

NativeEmitted to a matching target surface by default.MappedEmitted through another native surface that preserves the content.Opt-inEmitted only after enabling a target output option.Source onlyKept as a portable spec but not emitted by default.NoThe target does not support this spec kind.
Portable spec support by target. Open a target name for exact paths and caveats.
TargetAgentsSkillsRulesHooksMCPCommandsSettingsReviewsEnvironmentsIgnore
Claude CodeclaudeNativeNativeNativeNativeNativeNativeNativeNoNoNo
CodexcodexNativeNativeNativeNativeNativeOpt-inNativeNoNoNo
Gemini CLIgeminiNativeNativeNativeNativeNativeNativeNoNoNoNative
CursorcursorNativeNativeNativeNativeNativeNativeNoNativeNativeNative
GitHub CopilotcopilotNativeNativeNativeNativeNativeNoNativeNoNoNo
AideraiderSource onlySource onlyNativeNoNoNoNoNoNoNative
ClineclineNativeNativeNativeNoNoNoNoNoNoNo
Windsurf / DevinwindsurfNativeNativeNativeNativeNativeNoNoNoNoNative
ContinuecontinueMappedMappedNativeNoNativeNoNoNoNoNo
AmpampOpt-inNativeNativeNoNativeNoNoNoNativeNo
ZedzedOpt-inNativeNativeOpt-inNativeNoNoNoNoNo
WarpwarpOpt-inNativeNativeNoNativeNoNoNoNoNo
OpenCodeopencodeNativeNativeNativeNoNativeNativeNativeNoNoNo
Google AntigravityantigravityNativeNativeNativeNativeNativeNoNoNoNoNo
JuniejunieNativeNativeNativeNoNativeNativeNativeNoNoNative
KirokiroNativeNativeNativeNativeNativeNoNoNoNoNative
CrushcrushNoNativeNativeNativeNativeNoNoNoNoNative
TraetraeNativeNativeNativeNativeNativeNativeNoNoNoNative
QoderqoderNativeNativeNativeNativeNativeNativeNativeNoNoNo
OpenHandsopenhandsNativeNativeNativeNativeNativeNoNoNoNativeNo
FactoryfactoryNativeNativeNativeNativeNativeNativeNoNoNoNo
Kilo CodekiloNativeNativeNativeNoNativeNativeNativeNoNoNative
JulesjulesNoNoNativeNoNoNoNoNoNoNo
GoosegooseNativeNativeNativeNativeNoNoNoNativeNoNo
AugmentaugmentNativeNativeNativeNativeNativeNativeNoNoNoNative

The matrix answers whether a portable spec reaches each target. Open a target name for exact paths, configuration keys, and caveats. Native means matching target output is emitted by default. Mapped uses another native surface, Opt-in requires an output option, and Source only keeps the portable spec without default target output. When an opt-in or source-only spec is present, sync prints a note: with the next step. See Coverage notes.

Cross-cutting kind notes:

  • Skills: Claude Code, Codex, Cursor, Amp, Zed, Crush, Gemini, OpenCode, Copilot, OpenHands, Antigravity, Cline, Windsurf, Trae, Augment, Junie, Kilo Code, Qoder, Warp, Factory, Goose, and Kiro execute skill folders natively (SKILL.md + bundled assets). Codex, Cursor, Warp, and OpenCode preserve a source-layout scope by placing the native skill tree under that directory; import restores the scope and bundled assets. Codex, Amp, Zed, Crush, OpenHands, Antigravity, Windsurf, Augment, Kilo Code, Warp, Factory, and Goose share one tree at .agents/skills/, which Cursor, Gemini, OpenCode, and Copilot also scan alongside their own dirs (.gemini/skills/, .opencode/skills/, .github/skills/); Cline, Trae, Junie, Qoder, and Kiro each read their own tree (.cline/skills/, .trae/skills/, .junie/skills/, .qoder/skills/, .kiro/skills/), and Augment additionally scans .claude/skills/ and .augment/skills/ directly, so the shared tree covers it without a third on-disk copy, though a hand-authored .augment/skills/<name> wins a same-name collision over the synced one (lowest of Augment's six precedence slots). Kilo Code also documents .agents/skills/ itself as a "loaded by default" compatibility path, which is why it defaults there directly instead of adding a second on-disk copy under .kilo/skills/; Qoder's doc does not list .agents/skills/ as a compatible path, so it keeps its own tree instead. Warp's docs list .agents/skills/ as the recommended one of ten scanned directories (.warp/skills/, .claude/skills/, .codex/skills/, .cursor/skills/, .gemini/skills/, .copilot/skills/, .factory/skills/, .github/skills/, and .opencode/skills/ also read; a WARP_SKILL_DIRS env var indexes further ones, but only for Cloud agents indexing skills outside the repo), so this adapter writes only the recommended, shared one; .opencode/skills/ is OpenCode's own default, so a project running both tools gets Warp's skill scan for free with no extra write. Factory also documents a second compatibility path, .agent/skills/, which this adapter does not additionally write since the shared tree already covers it. Goose's own docs call .agents/skills/ "the recommended standard", ahead of the .goose/skills/ and .claude/skills/ paths it also discovers. Kiro's own skills page documents only .kiro/skills/ (glob skill://.kiro/skills/*/SKILL.md), with no .agents/skills/ compatibility path, so it keeps its own tree too; a prior version of this adapter flattened skills into .kiro/steering/skill-<name>.md instead, dropping bundled assets entirely (#642), and a stale copy of that shape is swept for a current skill name. On Aider and Continue skills still flatten to rule-form files. With sync.shared-skills: true, byte-identical skill folders across these targets collapse into one canonical copy (.agents/skills/<name> preferred) plus per-skill symlinks; see sync.shared-skills.

  • Hooks: shell commands on lifecycle events (PreToolUse, PostToolUse, SessionStart, etc.). Native on Claude Code, Codex, Gemini, Cursor (.cursor/hooks.json; Cursor uses camelCase event names like beforeShellExecution), Kiro (.kiro/hooks/<name>.json, one file per hook; Kiro also has PreTaskExec/PostTaskExec/PostFileCreate/PostFileSave/PostFileDelete/AgentSpawn events the others do not), OpenHands (.openhands/hooks.json, six events, whose native snake_case keys and the Claude-style PascalCase form the vendor documents as interchangeable), Windsurf / Devin CLI (.devin/hooks.v1.json, eight events, the hooks object as the entire file with no wrapper key), Qoder (.qoder/settings.json, hooks key, 27 events, merged alongside mcpServers in the same file), Augment (.augment/settings.json, hooks key, five events, merged alongside mcpServers in one write; timeout is milliseconds, converted from the shared spec's seconds field, and command must be a path ending in .sh/.ps1/.cmd/.bat or it never runs, which still emits verbatim with a coverage note rather than a guessed rename), Crush (crush.json, hooks key, PreToolUse only, the vendor's own docs/hooks/README.md saying "Crush currently supports just one hook, PreToolUse, with plans to support the full gamut", in any of the five spellings that page calls legal, merged alongside mcp in the same file and the same write), Copilot (.github/hooks/agnostic-ai.json, {"version": 1, "hooks": {...}}, 14 events, command, HTTP, and sessionStart prompt handlers, timeoutSec), Trae (.trae/hooks.json, six events, the same integer version wrapper Copilot uses around Claude-shaped {matcher, hooks: [...]} groups, plus a loop_limit the Stop event reads), Factory (.factory/hooks.json, nine events, keyed directly by event with no wrapper key, timeout in seconds), Antigravity (.agents/hooks.json, five events, keyed by hook definition name rather than by event, with enabled: false disabling one definition), and Goose (.agents/plugins/agnostic-ai/hooks/hooks.json, 12 events, plus the required Open Plugins manifest). Qoder's PreToolUse/PostToolUse matcher is Claude Code's own tool-name vocabulary, so a Claude-authored matcher reaches it unchanged, unlike OpenHands, Windsurf, and Augment, whose own tool names diverge; Crush's are lowercase (bash, edit, write, mcp_<server>_<tool>) and Factory's Droid CLI spells them Execute/Create/FetchUrl where Claude says Bash/Write/WebFetch, so both join that divergent group too. Copilot documents event: in either PascalCase or camelCase as independently valid keys in the same file, so this adapter passes it through verbatim rather than picking one; the PascalCase form also carries Claude's own matcher semantics and tool names, so a Claude-authored matcher reaches it unchanged there too, same as Qoder, while the camelCase form answers only to Copilot's own lowercase tool names. Two Copilot events are camelCase-only, userPromptTransformed and subagentStart, so a spec spelling either in PascalCase emits a key that parses and never fires. Trae joins the divergent-vocabulary group for a subtler reason than the rest: its hook tool_name table is not its own subagent tools table. The terminal tool is RunCommand, not Bash, and there is no TodoWrite, so a Claude-style matcher parses and then matches nothing there too. Zed runs them via opt-in outputs.zed.tasks-file; WorktreeCreate becomes an automatic create_worktree task hook and other events remain on-demand tasks. Other targets skip with a warning.

    .claude/settings.json is turning into a cross-tool hook file, so syncing claude alongside another target can run the same hook twice. Four vendors read it today: Claude Code, which owns it, plus three that load it on top of their own hook file. Copilot ("Cross-tool .claude/settings.json and .claude/settings.local.json files in the repository are also read", with "all hook entries from all sources are run"), Cursor (rank 6 of a seven-rank merge, "All matching hooks from every source run"), and Trae ("TraeCode will read all enabled hook configurations and execute them in combination"). Only Copilot's read is on by default and ungated; Cursor's and Trae's each sit behind an off-by-default switch. The per-target sections below say which. Until a vendor offers a toggle, give a hook spec a single target: rather than two that both read this file.

  • MCP servers: propagate to every target with a project-scoped MCP file (21 of 25, see matrix). Aider, Cline, Jules, and Goose have no MCP surface and skip with a warning. On targets whose native schema uses a type field (claude, cursor, copilot, continue, opencode, crush, kilo, factory, qoder, augment), remote (HTTP / SSE) entries carry an explicit type and stdio entries omit it (crush and kilo tag stdio explicitly, with kilo also combining command+args into one array; the others infer it as the default). amp, gemini, zed, junie, kiro, antigravity, trae, and warp have no type field and infer the transport from the emitted keys (gemini via httpUrl vs url; antigravity via serverUrl vs command, and its doc is explicit that the legacy url / httpUrl names "are not supported"; the rest, trae and warp included, via url vs command). Windsurf also has an explicit field, but spelled transport rather than type: the file is Devin Local's, not Cascade's, "the MCP configuration [at Cascade's own doc page] applies to the legacy Cascade agent only" (docs.devin.ai/desktop/cascade/mcp), and its own schema page names the field transport with values http (default) or sse, so this adapter holds its own builder rather than reuse the shared type-keyed one. OpenHands has no type field either, but unlike that group it has no shared key shape to infer from: transport is implied entirely by which [mcp] array (stdio_servers, sse_servers, shttp_servers) a server lands in. Qoder wrote Claude Code's identical .mcp.json until #641 and now writes .qoder/settings.json, its own documented project-level location: the two targets' documented field sets diverged, and two adapters writing different bytes to one path is a hard error in the collision check rather than a dedupe. Augment's .augment/settings.json merges the mcpServers map alongside shell, startupScript, theme, and other Auggie CLI settings the user or the tool itself already manages there, rather than overwriting the file (docs.augmentcode.com/cli/config, target-audit 2026-08-27, #633). Trae's own docs cover only command/args/env and url/headers; a spec's description and roots are dropped there for the same no-guess reason, and Warp now holds the same line for the same three fields. Copilot emits twice, since Copilot CLI does not read VS Code's .vscode/mcp.json. See disabled support by target for which of these targets honor a spec's disabled: true (Antigravity, Codex, Crush, Factory, Kilo Code, Kiro, OpenCode, Qoder, Windsurf, and Zed do; Claude Code, Cursor, Copilot, Trae, Augment, and Warp do not).

  • Commands: slash-prompt files authored under commands/. Native on Claude Code (.claude/commands/<name>.md), Cursor (.cursor/commands/<name>.md), Gemini (.gemini/commands/<name>.toml), OpenCode (.opencode/commands/<name>.md), Trae (.trae/commands/<name>.md, name + description frontmatter only), Qoder (.qoder/commands/<name>.md, description-only frontmatter, required; name is documented too but is cosmetic, a TUI display label with no effect on invocation, so this adapter never writes it), and Junie (.junie/commands/<name>.md, description and allowPromptArgument the vendor-documented frontmatter fields, though every other key still passes through verbatim; the body may reference $argumentName placeholders Junie substitutes at invocation; target-audit 2026-08-11, #605), Kilo Code (.kilo/commands/<name>.md, with description, agent, model, variant, and subtask frontmatter; target-audit 2026-09-10, #630), Augment (.augment/commands/<scope>/<name>.md, where nested directories become namespaces), and Factory (.factory/commands/<name>.md, preserving $ARGUMENTS). Codex deprecated project prompts (its commands stay source-only unless outputs.codex.commands-dir opts into the legacy .codex/prompts/ layout). Amp has no file-based command surface at all: a full sweep of its docs finds no .agents/commands/ anywhere, commands register programmatically via amp.registerCommand(...) in plugin TypeScript, and the migration guidance is to delete the old command file rather than move it, so there is no path to opt into the way Codex's is. Other targets skip with a warning.

  • Ignore: gitignore-syntax exclusion files, native on ten targets: Cursor (.cursorignore), Gemini (.geminiignore), Aider (.aiderignore), Windsurf (.devinignore), Kiro (.kiroignore), Trae (.trae/.ignore), Junie (.aiignore), Crush (.crushignore), Kilo (.kilocodeignore), and Augment (.augmentignore). Every ignore spec concatenates into each of them under a # provenance header, preserving pattern order and whitespace. Before replacing a hand-authored file, sync requires every existing pattern to survive unchanged and in order, with no added negations. Extra exclusions are allowed. When preservation cannot be established, AAI-103 names the risk and leaves the file untouched (#761). Run agnostic-ai import <target> to copy its patterns into an ignore spec, then review any conflicting patterns before syncing. An unchanged import followed by sync needs no manual cleanup. Comment and blank lines do not block a sync. Already-generated files still regenerate normally. outputs.<target>.provenance-header: false removes the marker used to recognize generated output and disables this check. See overwrite behavior. Other targets skip with a warning.

Per-target output

Claude Code (claude)

CLAUDE.md                # canonical entry-point pointer body (written by sync)
.claude/
├── agents/<name>.md
├── skills/<name>/SKILL.md
├── rules/<name>.md
├── commands/<name>.md
└── settings.json
.mcp.json
  • Rules: one file per spec under .claude/rules/. Claude Code discovers every .md file under that directory (recursively) at session start, so the emitted rules load natively with no extra wiring. A spec with the cross-tool globs field (or a native paths list) emits paths: frontmatter, which scopes the rule to matching files. outputs.claude.rules-mode: import (appends a sentinel-marked block of @.claude/rules/<name>.md imports to the pointer body, round-trip-stripped on import) predates native rules loading; keep it only for Claude Code versions older than the .claude/rules/ rollout. The legacy alternative outputs.claude.rules-file: CLAUDE.md concatenates rule bodies into a single file and skips the pointer-body write for claude.
  • Commands: one file per spec under .claude/commands/. Spec deploy becomes /deploy. Frontmatter passes through; body is the prompt template.
  • Settings overlay: agnostic-ai import claude captures the non-hooks portion of .claude/settings.json (statusLine, enabledPlugins, any top-level key) into .agnostic-ai/overlays/claude.settings.json. sync -t claude layers the spec-derived hooks key on top, reproducing the full settings.json from a fresh checkout. Re-run import claude after editing settings.json by hand.
  • MCP: written into .mcp.json under the standard mcpServers map (stdio: command/args/env, no type; remote: type + url/headers). Every entry also accepts timeout (per-tool-call execution timeout in milliseconds; values under 1000 are ignored) and alwaysLoad (load the server's tools at session start instead of deferring them behind tool search, "available on all server types"). An http, sse, or ws entry additionally accepts headersHelper, a command run at connection time whose output merges into the connection headers, for "an authentication scheme other than OAuth, such as Kerberos, short-lived tokens, or an internal SSO", plus an oauth object taking clientId, callbackPort, authServerMetadataUrl, and a space-separated scopes string (code.claude.com/docs/en/mcp, target-audit 2026-08-27, #634). oauth.clientSecret is never written: the vendor keeps the secret in the system keychain, "not in your config". disabled: true has no effect here; see disabled support by target.
  • MCP import: import claude reads .mcp.json and writes one spec under <mcps>/<name>.yaml per mcpServers.<name>. The next sync distributes them to codex, copilot, cursor, continue, amp, zed, warp, gemini, opencode.
  • First-class settings: outputs.claude.settings.* declares model, outputStyle, statusLine, permissions, enabledPlugins, env, apiKeyHelper, cleanupPeriodDays, attribution, bashOutputMaxChars, and taskOutputMaxChars. The last two raise how much command and background-task output Claude Code takes inline before spilling it to a file, up to 128K characters, and need Claude Code v2.1.261 or later (#679). The deprecated includeCoAuthoredBy key remains available for older Claude Code versions. These settings merge above the captured overlay and below the spec-derived hooks. See Claude settings.

Hooks support command, http, mcp_tool, and prompt handlers. HTTP uses url, optional headers, and allowedEnvVars; MCP uses server, tool, and optional input; prompt uses prompt, optional model, and continueOnBlock. When continueOnBlock: true, a blocking prompt result returns its reason to Claude and the turn continues. import claude preserves these handlers alongside command hooks. Experimental agent handlers are not emitted. See hook fields.

The MCP file is managed as a whole document. Each sync replaces .mcp.json from MCP specs. Import hand-authored servers before syncing.

Config keys: outputs.claude.dir (default .claude), outputs.claude.rules-dir (default .claude/rules, auto-loaded by Claude Code), outputs.claude.rules-mode (unset; set to import to also wire .claude/rules/*.md into CLAUDE.md via @-imports, only needed on Claude Code versions without native rules loading), outputs.claude.rules-file (unset; switches to legacy concatenated single-file layout, typically CLAUDE.md), outputs.claude.commands-dir (default .claude/commands), outputs.claude.mcp-file (default .mcp.json), outputs.claude.settings (first-class settings block).

Verify with the real CLI:

  1. Install: npm install -g @anthropic-ai/claude-code (or the desktop app; both read the same files).
  2. Check the tree: ls CLAUDE.md .claude/agents/ .claude/skills/ .claude/rules/ .claude/commands/ .claude/settings.json .mcp.json and grep "Generated by agnostic-ai" .claude/agents/*.md .claude/rules/*.md .claude/commands/*.md for the provenance header (it sits after the YAML frontmatter, so head -1 would only show ---).
  3. Validate JSON: python -m json.tool .claude/settings.json > /dev/null && python -m json.tool .mcp.json > /dev/null.
  4. Launch claude from the project root. /agents, /skills, and the slash-command picker should list every entry. The MCP picker shows each .mcp.json server green.
  5. Trigger a matcher action (e.g. an Edit for PostToolUse/Edit); the hook command runs with no "schema mismatch" in the log.
  6. Confirm outputs.claude.settings.* keys under /config.

Codex (codex)

AGENTS.md                                    # canonical entry-point pointer body (written by sync)
.codex/agents/<name>.toml                    # one TOML per agent (Codex CLI's native path)
.agents/skills/<name>/SKILL.md               # one folder per skill (the path Codex CLI scans)
.agents/skills/<name>/agents/openai.yaml     # optional, when x-codex provides UI/policy/deps
.codex/config.toml                           # when settings or MCP entries exist
.codex/hooks.json                            # when hook entries exist
.codex/rules/default.rules                   # opt-in, from outputs.codex.exec-policies
.codex/prompts/<name>.md                     # opt-in via outputs.codex.commands-dir (deprecated by Codex)
  • Rules: unscoped rules inline into the root AGENTS.md. A rule with scope: services/payments instead reaches services/payments/AGENTS.md. A globs field alone does not create a directory scope. Remove legacy outputs.codex.rules-file overrides before using scoped rules. See scoped context for selector and runtime limits.

  • Agents: Codex custom agents use one TOML file per agent with name, description, and developer_instructions, plus optional session config such as model, model_reasoning_effort, and sandbox_mode. A generic tools: [Read, Bash, ...] list is not emitted because Codex defines tools as a config table, not a tool allowlist. Sync reports the dropped field. Use x-codex.tools for native settings such as web_search and view_image. Use a per-target model map when another CLI's model name, such as sonnet, must not reach Codex.

  • Skills: Codex skills layout, one folder per skill under .agents/skills/ (the directory Codex scans from the cwd up to the repo root) with a required SKILL.md (frontmatter name + description, plus body). A scoped source skill moves the native directory under that scope, for example skills/services/api/review/SKILL.md becomes services/api/.agents/skills/review/SKILL.md; import codex restores the scope and bundled assets. When the spec carries x-codex.interface, x-codex.policy, or x-codex.dependencies, an agents/openai.yaml is also written for UI customization and policy declarations. Amp reads the root path; identical emitted bytes dedupe, so enabling both targets is safe. A stale managed tree at the pre-v0.43 .codex/skills/ default is swept on sync.

  • Hooks: land in .codex/hooks.json (override via outputs.codex.hooks-file), routed by event frontmatter (SessionStart, SubagentStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, PreCompact, PostCompact, Stop, SubagentStop, SessionEnd, Interrupt) into per-event arrays with matcher and command. Optional timeout, statusMessage, commandWindows, additionalContextLimit, and async (run the hook in the background instead of blocking the session) pass through and survive import codex; an explicit additionalContextLimit: 0 is preserved because Codex uses it to pass complete hook context. import codex also reads hooks straight out of a hand-authored .codex/config.toml, in the vendor's own documented inline shape: [[hooks.<event>]] carries matcher alone, and a nested [[hooks.<event>.hooks]] array carries the command fields (learn.chatgpt.com/docs/hooks, "Equivalent inline TOML in config.toml"). A flat [[hooks.<event>]] table with matcher and command on the same table also still decodes, for configs written before this shape was added; the vendor has never documented that form (#669).

    A hook spec with type: mcp_tool calls a tool on an already-connected MCP server instead of running a shell command: learn.chatgpt.com/docs/hooks says it "sends structured arguments directly to the tool and uses the same trust review and output contract as a command hook." server and tool are required, input (an argument-template object) is optional, and it shares timeout/statusMessage with the command shape. It emits as {type, server, tool, input, timeout, statusMessage} in the same hooks.json, and import codex reads it back from there (#693).

  • Exec policies: opt-in. Set outputs.codex.exec-policies (inline list) or outputs.codex.exec-policies-file (external YAML) to write .codex/rules/default.rules in Codex's Starlark prefix_rule(...) form. Unset writes nothing.

  • MCP: lands in .codex/config.toml. Servers emit as [mcp_servers.<name>]: stdio uses command/args/env/cwd plus the mixed env_vars array, while HTTP/SSE uses url/bearer_token_env_var/http_headers/env_http_headers/auth (oauth or chatgpt)/http_headers_helper (a local command printing header JSON, documented for a locally connected HTTP server only). A server name that TOML cannot carry as a bare key is quoted; Codex CLI 0.152.0 widened the accepted server-name charset to include :, @, /, and . for package-style names such as npm:@modelcontextprotocol/server-sequential.thinking (openai/codex#41700), and this adapter quotes those too so one such name does not invalidate the whole file (#706). Import stores a slash-bearing name in one percent-encoded YAML filename and keeps the exact name inside the spec, so import followed by sync is lossless (#711). enabled_tools/disabled_tools carry no transport restriction and emit on either shape (learn.chatgpt.com/docs/config-file/config-reference, #661). required, startup_timeout_sec, startup_timeout_ms (the vendor's own millisecond alias for the same startup timeout; set one or the other, #735), tool_timeout_sec, default_tools_approval_mode, and experimental_environment also carry no transport restriction; scopes, oauth_resource, and an [mcp_servers.<name>.oauth] sub-table (client_id, callback_url, callback_port) authenticate to an MCP HTTP server and land on the http/sse shape alongside auth (#693). A tools map emits the vendor's per-tool sub-tables, [mcp_servers.<name>.tools.<tool>], whose keys pass through verbatim: output_token_limit ("Token budget for one MCP tool's output, before the standard 20% serialization allowance", shipped in Codex v0.153.0) and a per-tool approval override are the two the vendor documents today, and the table gains entries without warning, so nothing is mapped key by key. A tool name that TOML cannot carry as a bare key is quoted. These sub-tables are written last in the server's table, because a TOML sub-table header ends its parent: a server-level scalar emitted after one would be read as a key of the tool. The whole block was dropped in silence before #678. All of these fields survive import codex. The project-tier config.toml is managed (overwritten each sync); put unmanaged Codex config in ~/.codex/config.toml.

  • Settings: the last portable model value writes to .codex/config.toml. outputs.codex.config.model wins over the portable value. A captured .agnostic-ai/overlays/codex.config.toml remains the highest-precedence layer for backward compatibility, so an imported model there wins over both and is never duplicated.

  • Commands: not emitted by default. Codex loads custom prompts from ~/.codex/prompts/ only (no project-level discovery) and deprecates them in favor of skills, so a project-tier prompts tree would never be read; sync prints a coverage note instead and sweeps a stale managed .codex/prompts/ tree. Set outputs.codex.commands-dir to emit the legacy layout anyway.

  • Import: import codex captures .codex/config.toml minus hooks and mcp_servers into .agnostic-ai/overlays/codex.config.toml. sync -t codex prepends it before the spec-derived sections, so model, sandbox, approval_policy, notify, [history], [profiles.*], [model_providers.*], and any other key survive a .codex/ wipe. On conflict with outputs.codex.config.* the overlay wins and the first-class key is dropped to keep TOML valid. import codex also reads .codex/prompts/*.md and writes them byte-for-byte to <commands>/, so user-authored prompts round-trip.

Config keys: outputs.codex.agents-dir (default .codex/agents; override to .agents/agents for the community shared layout), outputs.codex.skills-dir (default .agents/skills, the path Codex scans), outputs.codex.shared-subagents (default true; emits the per-skill tree at skills-dir. Set false to skip codex skill emission), outputs.codex.commands-dir (unset; set to e.g. .codex/prompts to emit the deprecated project prompts layout), outputs.codex.mcp-file (default .codex/config.toml), outputs.codex.hooks-file (default .codex/hooks.json), outputs.codex.rules-file (unset; writes legacy concatenated rules and skips the pointer-body write), outputs.codex.exec-policies / outputs.codex.exec-policies-file (unset; write .codex/rules/default.rules).

Verify with the real CLI:

  1. Install: npm install -g @openai/codex (quickstart); codex --version to confirm PATH.
  2. Check the tree: agnostic-ai sync -t codex, then ls .codex/agents/ .agents/skills/, test -f .codex/config.toml && head -1 .codex/config.toml, test -f .codex/hooks.json && jq '.hooks | keys' .codex/hooks.json. First line of config.toml must be the # Generated by agnostic-ai provenance comment.
  3. Validate syntax: toml-test .codex/config.toml and jq empty .codex/hooks.json should both exit 0.
  4. codex run "list one rule from this project". Codex picks up AGENTS.md, the agents, and skill folders. Look for loaded N agents / loaded N skills.
  5. Trigger a hook by firing the targeted event (e.g. an Edit for a PostToolUse hook); the command appears in the hook log.
  6. codex mcp list shows every [mcp_servers.<name>]. Disabled servers appear with the disabled flag.

The audit issue #329 tracks this smoke checklist; close its "Real CLI smoke" box only after every step passes against the live Codex CLI build in the linked PR.

Gemini CLI (gemini)

GEMINI.md                              # canonical entry-point pointer body (written by sync)
.gemini/agents/<name>.md               # one per agent, Gemini's native subagent surface
.gemini/commands/<name>.toml           # one per command
.gemini/skills/<name>/SKILL.md         # one folder per skill, bundled assets included
.gemini/commands/skill-<name>.toml     # additional command form, only when emit-skills-as-commands: true
.gemini/settings.json                  # when MCP and/or hook entries exist (merged with existing user config)
.geminiignore                          # when ignore entries exist
  • Rules: unscoped rules inline into the root GEMINI.md. A rule with scope: services/payments instead reaches services/payments/GEMINI.md. A globs field alone does not create a directory scope. Remove legacy outputs.gemini.rules-file overrides before using scoped rules. See scoped context for selector and runtime limits.

  • Agents: one native subagent per agent at .gemini/agents/<name>.md. Gemini CLI's subagents doc states "Custom agents are defined as Markdown files (.md) with YAML frontmatter ... Project-level: .gemini/agents/*.md (Shared with your team)", cross-confirmed by /agents reload, which "Rescans agent directories (~/.gemini/agents and .gemini/agents)". That surface is what buys automatic delegation, an isolated context window, @name invocation, and the /agents listing. Until this release agents emitted as a slash-command TOML instead, so an agent was a prompt the user had to type rather than a subagent Gemini could delegate to, and it shared a directory and a <name>.toml filename with commands, so a same-named agent and command overwrote each other (target-audit 2026-09-11, #733). Frontmatter carries the two required fields, name and description (falling back to the spec name), plus kind, model, temperature, max_turns, and timeout_mins when declared; the body is the system prompt. mcpServers (inline per-agent MCP servers) is documented too and has no agnostic-ai field, so it reaches the file through x-gemini. A spec's generic tools list translates onto Gemini's own tool names (Read to read_file, Write to write_file, Edit to replace, Glob to glob, Grep to grep_search, Bash to run_shell_command, WebFetch to web_fetch, WebSearch to google_web_search); a name outside that set is dropped with a coverage note rather than written unconfirmed, since an unknown entry here restricts the subagent to a tool that does not exist while an absent tools key inherits every tool from the parent session. Set x-gemini.tools to write Gemini's own vocabulary directly, including the documented *, mcp_*, and mcp_<server>_* wildcards; that override wins outright over the translated form. Set outputs.gemini.emit-agents-as-commands: true to also keep writing the old <name>.toml slash command, for a project that already types /name; with the key off, a managed TOML an earlier sync left there is swept.

  • Skills: native Agent Skills folders under .gemini/skills/<name>/SKILL.md (the workspace tier Gemini CLI scans; it also reads the cross-tool .agents/skills/ alias, which takes precedence over .gemini/skills/ within the same tier when a skill shares a name in both, target-audit 2026-08-08, #563). Gemini CLI resolves that conflict itself at session start, so no sync-time detection is needed here. Bundled sibling files propagate byte-for-byte. Set outputs.gemini.emit-skills-as-commands: true to additionally emit one skill-<name>.toml command per skill.

  • Commands: one TOML per command spec under .gemini/commands/<name>.toml, the directory Gemini reads project slash commands from. description frontmatter maps to the TOML description; the body becomes the prompt. Agents left this directory in #733, so a command and an agent may now share a name without overwriting each other.

  • MCP + hooks: written into .gemini/settings.json (mcpServers map, hooks map). Gemini keys the endpoint by transport: streamable-HTTP servers (type: http) use httpUrl, SSE servers (type: sse) use url; the adapter routes each automatically. Stdio servers also accept cwd (working directory), the same cross-tool field Codex reads. Every server, any transport, also accepts timeout (milliseconds), trust (bypass tool-call confirmations), description, includeTools, and excludeTools; all five pass through verbatim (geminicli.com/docs/reference/configuration, #661). Hooks route by event frontmatter (e.g. BeforeTool, AfterTool, SessionStart, SessionEnd; Gemini CLI documents 11 events in total); each definition contains matcher and a nested hooks array of {type: "command", command} handlers, as required by the hook reference. Portable hook timeouts convert from seconds to milliseconds. x-gemini.sequential controls execution within a group. Pre-existing user keys survive syncs.

  • Ignore: ignore specs emit as .geminiignore (gitignore syntax), the file Gemini CLI reads. Multiple specs concatenate. Override via outputs.gemini.ignore-file. Up to v0.49 this wrote .aiexclude, which belongs to Gemini Code Assist and Gemini CLI never opens; a managed .aiexclude is removed on the next sync. (#625)

  • Import: import gemini reads every native directory on its own pass. .gemini/agents/*.md becomes agent specs, .gemini/commands/*.toml becomes command specs, .gemini/skills/<name>/ becomes skill folders, and .gemini/settings.json becomes MCP and hook specs. A command's prompt becomes the spec body in either form Gemini documents, the triple-quoted block or the single-line string, and description stays in frontmatter. A project synced before #733 emitted its agents as command TOMLs; those import as commands, since .gemini/commands/ is the slash-command directory, and a re-sync writes them back to the same path. The mirror TOMLs emit-agents-as-commands and emit-skills-as-commands write import as command specs too, for the same reason; the emitted bytes stay identical across the round-trip (#750). Native nested hooks preserve commands, matchers, names, descriptions, environment maps, timeouts, and sequential groups. Distinct definitions with the same command and matcher import into separate files. Multiple handlers stay together under x-gemini.hooks; old flat hook files still import (#762).

Config keys: outputs.gemini.agents-dir (default .gemini/agents), outputs.gemini.commands-dir (default .gemini/commands), outputs.gemini.skills-dir (default .gemini/skills), outputs.gemini.mcp-file (default .gemini/settings.json, also holds hooks), outputs.gemini.emit-skills-as-commands (default false), outputs.gemini.emit-agents-as-commands (default false), outputs.gemini.rules-file (unset; writes legacy concatenated rules and skips the pointer-body write), outputs.gemini.ignore-file (default .geminiignore).

Verify with the real CLI:

  1. Install: npm install -g @google/gemini-cli (docs).
  2. Check the tree: ls GEMINI.md .gemini/agents/ .gemini/commands/ .gemini/settings.json, head -2 .gemini/agents/*.md (frontmatter first), head -1 .gemini/commands/*.toml for the provenance header, python -m json.tool .gemini/settings.json > /dev/null.
  3. gemini --list-commands parses every <name>.toml with no "invalid TOML" / "unknown field" errors, and /agents lists every .gemini/agents/<name>.md as delegatable.
  4. gemini --list-mcp-servers shows each mcpServers.<name> ready.
  5. Trigger a hook by performing the matcher action (e.g. an AfterTool); the hook command runs.

Cursor (cursor)

.cursor/rules/<name>.mdc
.cursor/agents/<name>.md             # one native subagent per agent spec
.cursor/skills/<name>/SKILL.md       # one folder per skill, bundled assets included
.cursor/commands/<name>.md           # one per command spec
.cursor/hooks.json                   # when hook specs exist (managed, overwritten each sync)
.cursor/mcp.json                     # when MCP entries exist
  • Rules emit with alwaysApply: true (override in spec frontmatter). An always-apply rule omits globs. A non-always rule without globs falls back to the Claude-spelled paths list (comma-joined); when both are absent, globs is omitted too rather than defaulted to **/*, so Cursor treats the rule as description-driven ("Apply Intelligently") or manual-only ("Apply Manually") instead of auto-attaching it to every file. Scalar globs keep minimal quoting so a hand-authored .mdc round-trips clean. (#443, #536)

  • Agents: native Cursor subagents at .cursor/agents/<name>.md (Cursor 2.4+): frontmatter name + description plus optional model, readonly, and is_background when the spec declares them; the body is the system prompt. The old flattened .mdc and agent-as-command emissions are gone; the ledger sweeps stale copies.

  • Each command spec emits as a Cursor command under .cursor/commands/: Markdown whose body is the prompt. The old /docs/agent/chat/commands page 308s to that link, a "migrate commands to skills" FAQ; no Cursor page documents .cursor/commands directly any more, so this is the closest surviving reference. Override the directory via outputs.cursor.commands-dir.

  • Skills: native folders under .cursor/skills/<name>/SKILL.md (the Agent Skills layout Cursor 2.4+ discovers), with every bundled sibling file (scripts, references, assets) propagated byte-for-byte. A source-layout scope moves the native tree under that directory and survives import. Frontmatter carries name + description; optional paths, disable-model-invocation, icon, color, and metadata pass through when the spec declares them (icon and color style the badge when the skill backs a Custom Mode). The pre-native flattened skill-<name>.mdc copies are no longer written and get swept by the ledger on the next sync.

  • Review specs emit as Bugbot files inside .cursor/ directories: .cursor/BUGBOT.md at the repo root for unscoped specs, <scope>/.cursor/BUGBOT.md for scoped ones, with same-scope specs concatenated. Bugbot always includes the root file and picks up per-directory copies while traversing up from changed files. Override the basename via outputs.cursor.review-file. (#433)

  • Environment specs emit as .cursor/environment.json (background-agent bootstrap). The spec keys pass through verbatim minus agnostic routing fields; multiple specs merge by top-level key. Override the path via outputs.cursor.environment-file. (#434)

  • Ignore specs emit as .cursorignore (gitignore syntax). Multiple specs concatenate. Override via outputs.cursor.ignore-file. (#435)

  • Hook specs emit as Cursor Hooks in a managed .cursor/hooks.json (version + per-event arrays). Command hooks retain their {command, matcher?} shape. A type: prompt hook instead emits prompt and optional model. Both forms preserve timeout, loop_limit (including null), failClosed, and matcher. Cursor uses camelCase event names (beforeShellExecution, afterFileEdit, ...), passed through verbatim; validate flags unrecognized ones. Thirteen of those events consume a matcher, per the vendor's own "Available matchers by hook" table: preToolUse, postToolUse, postToolUseFailure (tool name), subagentStart, subagentStop (subagent type), beforeShellExecution, afterShellExecution (the full command string), beforeReadFile, afterFileEdit (tool name), and beforeSubmitPrompt, stop, afterAgentResponse, afterAgentThought (one fixed value each). lint no longer flags a matcher on the last five (#734). It also stays quiet on beforeMCPExecution and afterMCPExecution, which that table does not list; a warning there would be the same false positive in the other direction. Override via outputs.cursor.hooks-file. (#438)

    Cursor also reads Claude Code's own hook file. Third-party hooks puts .claude/settings.json at rank 6 of a seven-rank merge with .cursor/hooks.json at rank 3, and "All matching hooks from every source run." So a repo syncing claude and cursor together runs every hook twice. Two gates keep it off until you ask for it, both on that page: "Enable Third-party skills in Cursor Settings → Rules, Skills, Subagents", and "The feature must be enabled for your account" (#756).

  • MCP: written into .cursor/mcp.json under the standard mcpServers map (the shared builder also used by Claude Code). A stdio server accepts envFile, a path to an env file loading additional variables; a remote (url) server accepts a static-OAuth auth object (CLIENT_ID, CLIENT_SECRET, scopes) for a provider without OAuth Dynamic Client Registration (cursor.com/docs/mcp, #661). Neither field is documented for the other targets sharing this builder, so both stay scoped to Cursor rather than appearing everywhere the shared schema is used. disabled: true has no effect here; see disabled support by target.

The MCP file is managed as a whole document. Each sync replaces .cursor/mcp.json from MCP specs.

Config keys: outputs.cursor.rules-dir (default .cursor/rules), outputs.cursor.agents-dir (default .cursor/agents), outputs.cursor.skills-dir (default .cursor/skills), outputs.cursor.commands-dir (default .cursor/commands), outputs.cursor.mcp-file (default .cursor/mcp.json), outputs.cursor.review-file (default BUGBOT.md), outputs.cursor.environment-file (default .cursor/environment.json), outputs.cursor.ignore-file (default .cursorignore), outputs.cursor.hooks-file (default .cursor/hooks.json).

Verify with the real IDE:

  1. Install Cursor from cursor.com.
  2. Check the tree: ls .cursor/rules/ .cursor/skills/ .cursor/commands/ .cursor/mcp.json, grep "Generated by agnostic-ai" .cursor/rules/*.mdc for the provenance header (it sits after the frontmatter block), python -m json.tool .cursor/mcp.json > /dev/null.
  3. Open the project. The Rules panel loads every .cursor/rules/*.mdc (confirm alwaysApply matches each rule's frontmatter, no "failed to parse" warnings), the Skills list shows each .cursor/skills/<name>/, the agent picker lists each .cursor/agents/<name>.md, and the / command picker lists each .cursor/commands/<name>.md.
  4. If MCPs are configured, Settings → MCP shows every mcpServers.<name> green.
  5. If hooks are configured, python -m json.tool .cursor/hooks.json > /dev/null parses; trigger the matched event (e.g. a shell command for beforeShellExecution) and confirm the command runs.

GitHub Copilot (copilot)

.github/copilot-instructions.md                            # canonical entry-point pointer body (written by sync)
.github/instructions/<name>.instructions.md                # scoped rule per file
.github/agents/<name>.agent.md                             # one custom-agent profile per agent
.github/skills/<name>/SKILL.md                             # one folder per skill, bundled assets included
.vscode/mcp.json                                           # when MCP entries exist; VS Code's file, servers key
.github/mcp.json                                           # when MCP entries exist; Copilot CLI's file, mcpServers key
.mcp.json                                                  # only with outputs.copilot.root-mcp-file; mcpServers key
.github/hooks/agnostic-ai.json                              # when hook entries exist
.github/copilot/settings.json                              # when a Settings model exists
  • Rules: Copilot supports path-scoped instructions via applyTo: frontmatter. Rules with globs (or a source-layout scope like rules/backend/auth.md) emit as a separate .instructions.md with applyTo derived from globs (explicit globs wins, else <scope>/**). Always-on rules (no globs, no scope, or alwaysApply: true) skip per-file emission and are reachable via the pointer body plus the source spec dir.

  • Agents: native custom agent profiles at .github/agents/<name>.agent.md: frontmatter name + description plus tools and model when the spec declares them; arbitrary x-copilot keys (target, user-invocable, mcp-servers, ...) pass through. The body is the agent prompt. The old flattened agent-<name>.instructions.md copies are gone; the ledger sweeps them.

  • Skills: native Copilot skills folders at .github/skills/<name>/SKILL.md (Copilot also scans .claude/skills/ and .agents/skills/), with bundled sibling files propagated byte-for-byte. The old flattened skill-<name>.instructions.md copies are gone; the ledger sweeps them.

  • Chat modes: when outputs.copilot.chatmodes-dir is set, each agent also emits as a Copilot Custom Chat Mode at <dir>/<name>.chatmode.md with description/model/tools frontmatter. The native agent profile still emits alongside.

  • Settings: the last portable model value merges into .github/copilot/settings.json, where Copilot CLI documents it as the repository default model. Other repository settings survive. import copilot restores the model to settings/imported.yaml and leaves target-only keys in the native file.

  • MCP: two files, because Copilot has two readers that disagree on which one to open. .vscode/mcp.json uses the VS Code schema, top-level servers key, each entry carrying a type (stdio, http, or sse). VS Code's Agent Host does not read that file itself: the vendor doc says it "doesn't read .vscode/mcp.json directly" and that VS Code forwards the config instead, except servers needing interactive input. .github/mcp.json carries the same servers under mcpServers for Copilot CLI, which its project-level discovery table lists as "Shared configuration that is committed to the repository". That second file exists because the CLI rejects the VS Code wrapper outright: "The .vscode/mcp.json file for VS Code is not read by Copilot CLI. It uses the unsupported top-level key servers." Before #646 only the VS Code file emitted, so a Copilot CLI user with no VS Code in the loop got no MCP server at all. Override either path with outputs.copilot.mcp-file / outputs.copilot.cli-mcp-file.

    VS Code MCP output owns the complete servers map and preserves unrelated top-level keys, including inputs and sandbox, at the default or configured path. JSONC is accepted; sync removes comments and normalizes formatting. Invalid JSONC aborts the write. .github/mcp.json and the opt-in root mirror remain managed as whole documents.

    VS Code alone accepts stdio cwd, envFile, and sandboxEnabled, remote oauth: {clientId, enterpriseManaged}, and dev.watch on stdio, HTTP, and SSE servers. dev.debug is stdio-only; a remote server that sets it gets a coverage note while its watch patterns still emit. These fields stay out of the Copilot CLI file and root mirror. See VS Code MCP configuration.

    The same table's third entry, a .mcp.json anywhere from the working directory up to the repository root, stays opt-in behind outputs.copilot.root-mcp-file: .mcp.json. The project root is shared ground with Claude Code rather than Copilot's own directory, and a repository-root file is a surprise for a project that does not need it. Claude Code writes its own root .mcp.json under mcpServers too, so enabling both targets produces identical bytes at one path; the collision check compares content, not owners, so it stays quiet and sync writes the file once.

  • Hooks: land in .github/hooks/agnostic-ai.json (override via outputs.copilot.hooks-file), one of possibly several *.json files Copilot loads and merges from that directory: "Repository-level hook files: .github/hooks/*.json in the repository root", read by both Copilot CLI and Copilot cloud agent (docs.github.com/en/copilot/reference/hooks-reference, "Hooks locations"). The wrapper is {"version": 1, "hooks": {...}} with an integer version, and each hook entry is a flat object carrying matcher directly ({"type": "command", "matcher": ..., "command": ..., "timeoutSec": ...}), not Claude Code's nested {matcher, hooks: [...]} Group. 14 events today, one more than the 13 recorded when #629 was filed. event: passes through verbatim, same as every other hook emitter in this repo: the vendor documents both the PascalCase vocabulary Claude Code, Codex, OpenHands, Windsurf, and Qoder share (PreToolUse) and Copilot's own camelCase form (preToolUse) as independently valid keys in this same file, selecting between its "VS Code compatible" and "camelCase" hook payload formats respectively. The PascalCase form also carries Claude's own matcher semantics and tool names, so matcher: Bash reaches it unchanged; the camelCase form answers only to Copilot's own lowercase tool names (bash, edit, view, ...), and pairing it with a Claude-style matcher earns a coverage note rather than a guessed rename. Two of the 14 rows are camelCase-only, userPromptTransformed and subagentStart ("A subagent is spawned (before it runs)."): neither has a PascalCase pairing anywhere on the page, so a spec spelling either one in PascalCase emits a key Copilot parses and never fires. SubagentStart is the one that bites, since Claude Code and Codex both accept it (target-audit 2026-09-11, #737). Command, HTTP (url, headers, allowedEnvVars), and sessionStart prompt handlers all survive import copilot and re-emission.

    A hook spec that sets args emits Copilot's own shell-free form instead: {"type": "command", "exec": <command>, "args": [...]}. The field table is explicit that the executable moves out of command (exec is accepted "Instead of bash, powershell, and command", and "Do not combine exec with bash, powershell, or command"), which is where this differs from Claude Code, whose exec form keeps the executable in command. It buys what args is for, a path or argument carrying a space running as written, and costs one surface: exec and args are both marked "Only supported in Copilot CLI", and a cloud agent job reads the same .github/hooks/*.json files: "A subset of events fires, and only bash (or command) entries are honored." An exec-form entry is skipped whole there. sync raises a coverage note naming that trade. Leave args unset for a hook that must run under cloud agent (#755).

    Every hook runs twice when you sync claude and copilot together. The same "Hooks locations" section names both files: .github/hooks/*.json and, as a cross-tool read, .claude/settings.json and .claude/settings.local.json. Sources are "loaded ... and combined", and "When the same event appears in multiple sources, all hook entries from all sources are run." Both targets are in the default target list, so this is the out-of-the-box path: a formatter runs twice, an audit hook double-writes, and a blocking preToolUse returns two decisions per tool call. Copilot names no toggle for that read, unlike Cursor and Trae, which gate theirs behind an off-by-default switch. Until there is one, give the hook spec a single target: rather than both (#755).

Config keys: outputs.copilot.instructions-dir (default .github/instructions), outputs.copilot.agents-dir (default .github/agents), outputs.copilot.skills-dir (default .github/skills), outputs.copilot.mcp-file (default .vscode/mcp.json), outputs.copilot.cli-mcp-file (default .github/mcp.json; Copilot CLI's own file, mcpServers key), outputs.copilot.root-mcp-file (unset, opt-in; writes the same servers to a workspace-root .mcp.json under the mcpServers key), outputs.copilot.chatmodes-dir (default empty, opt-in; emits one Custom Chat Mode per agent), outputs.copilot.rules-file (unset; writes always-on rules concatenated at that path and skips the pointer-body write), outputs.copilot.hooks-file (default .github/hooks/agnostic-ai.json).

Verify with the real extension:

  1. Install the GitHub Copilot extension (and optional Copilot Chat) in VS Code.
  2. Check the tree: ls .github/copilot-instructions.md .github/instructions/ .vscode/mcp.json .github/mcp.json, grep "Generated by agnostic-ai" .github/instructions/*.md for the provenance header (it sits after the applyTo frontmatter), python -m json.tool .vscode/mcp.json > /dev/null && python -m json.tool .github/mcp.json > /dev/null.
  3. Open the project. Copilot loads .github/copilot-instructions.md plus every matching .instructions.md. The Output → GitHub Copilot channel shows no "failed to parse instructions" warnings.
  4. Open a file matching a rule glob (e.g. a .go file for applyTo: "**/*.go") and trigger chat; the rule body shows in context.
  5. If MCPs are configured, run MCP: Show Installed Servers; each servers.<name> from .vscode/mcp.json is ready. In Copilot CLI, run /mcp from the project root after accepting the folder-trust prompt; each mcpServers.<name> from .github/mcp.json is listed.
  6. If hooks are configured, python -m json.tool .github/hooks/agnostic-ai.json > /dev/null confirms the file parses. In Copilot CLI, a hook set on PreToolUse or preToolUse fires on the next matching tool call; the CLI's own hook log names which file and entry ran.

Aider (aider)

CONVENTIONS.md           # pointer body + inlined rules block (written by sync)
.aider.conf.yml          # only when conf-file is set

CONVENTIONS.md carries the pointer body plus a sentinel-marked ## Rules block with unscoped rule bodies inline, so the conventions reach Aider by default; import aider strips that block. Wire the file in via aider --read CONVENTIONS.md. Set outputs.aider.conf-file: .aider.conf.yml to also merge a read: entry into Aider's project config so the file auto-loads. model and weak-model propagate into the same file when set. Pre-existing keys are preserved; the read: list de-duplicates.

Config keys: outputs.aider.conf-file (default empty, opt-in), outputs.aider.model, outputs.aider.weak-model, outputs.aider.rules-file (unset; writes a legacy merged document and skips the pointer-body write), outputs.aider.ignore-file (default .aiderignore).

Verify with the real CLI:

  1. Install: python -m pip install -U aider-chat (or pipx install aider-chat).
  2. Check the tree: ls CONVENTIONS.md .aider.conf.yml, head -1 .aider.conf.yml must start with the provenance header.
  3. Validate YAML: python -c "import yaml,sys; yaml.safe_load(open('.aider.conf.yml'))".
  4. aider --config .aider.conf.yml --no-stream --message "list the rules you were told to follow". The banner prints the resolved read: paths (including CONVENTIONS.md); the response reflects the rules text.
  5. No Warning: lines mentioning CONVENTIONS.md or .aider.conf.yml.

Cline (cline)

AGENTS.md                            # canonical entry-point pointer body (written by sync, shared across the AGENTS.md consumers)
.cline/rules/<name>.md
.cline/agents/<name>.md
.cline/workflows/<name>.md           # one per agent, only when workflows-dir is set
.cline/skills/<name>/SKILL.md        # one folder per skill (Cline's recommended skills path)

Cline reads the cross-tool root AGENTS.md, so sync distributes the shared pointer body there (deduplicated with the other AGENTS.md consumers). Rules and agents each emit per file into their own directory under .cline/, the layout Cline's current config reference documents (.cline/{rules,skills,hooks,agents,plugins,cron}/). The older cline-rules page still calls the pre-migration .clinerules/ the "Primary rule format", so this is a live migration rather than a completed removal (target-audit 2026-08-01): set outputs.cline.rules-dir: .clinerules to keep rules at that path; a stale managed tree there is swept on sync otherwise. Agents always emit at .cline/agents/, independent of that override: Cline's own file format for that directory has no dedicated doc page, so this adapter writes the spec body verbatim, no synthesized heading and no invented frontmatter.

  • Rule activation: scoped rules emit conditional paths constrained to the directory, even when the source sets alwaysApply: true. For unscoped rules, alwaysApply: false enables native paths derived from the file selector; without a usable selector, the adapter reports a coverage note. See Cline conditions and scoped selector limits. The .cline/rules versus .clinerules runtime caveat above still applies.
  • Skills: one folder per skill under .cline/skills/<name>/SKILL.md, the path Cline's skills docs recommend. A flat file directly under the rules directory never loads as a skill, so this is a folder, not a rule-form file. The SKILL.md frontmatter carries name + description; sibling assets next to the source SKILL.md are copied byte-for-byte. import cline reads all three documented project paths in order: .cline/skills/, .clinerules/skills/, then .claude/skills/. The first same-name skill wins, and .clinerules/skills/ is excluded from the legacy rules walk.

When outputs.cline.workflows-dir is set, each agent also emits as a Markdown file at <dir>/<name>.md, invokable from chat as /<name>.md, with the italic description prefixing the body when present. Cline's doc for this feature, docs.cline.bot/features/workflows, 404s, and llms.txt lists no project-scoped replacement in the current customization/ tree (Rules, .clineignore, Hooks, Plugins, Skills; no Workflows entry, target-audit 2026-08-08, #563). Treat this as an unconfirmed export rather than a vendor-documented surface until a current doc backs it. The native .cline/agents/<name>.md emission still happens either way.

Config keys: outputs.cline.rules-dir (default .cline/rules; set to .clinerules for the pre-migration layout), outputs.cline.agents-dir (default .cline/agents), outputs.cline.skills-dir (default .cline/skills), outputs.cline.workflows-dir (default empty, opt-in).

Verify with the real extension:

  1. Install the Cline extension in VS Code.
  2. Check the tree: ls .cline/rules/ .cline/agents/ .cline/skills/, grep "Generated by agnostic-ai" .cline/rules/*.md for the provenance header, test -f .cline/skills/*/SKILL.md.
  3. Open the project, open the Cline panel. Cline loads every .cline/rules/*.md; each appears in the rules list with no "failed to parse" warnings. Each .cline/agents/<name>.md appears wherever Cline surfaces project agent definitions, and each .cline/skills/<name>/ loads as a skill. Open a file matching a paths rule and the "Conditional rules applied: workspace:<name>.md" notification names it; open an unrelated file and it stays out.
  4. If outputs.cline.workflows-dir is set, each <workflows-dir>/<name>.md is invokable as /<name>.md; the italic description previews the workflow.

Windsurf / Devin Desktop (windsurf)

AGENTS.md                            # shared pointer body (dedup with the other AGENTS.md consumers)
.devin/rules/<name>.md
<scope>/.devin/rules/<name>.md       # one per scoped rule
.devin/agents/<name>.md              # one per agent (custom subagent profile)
.agents/skills/<name>/SKILL.md       # one folder per skill (shared tree with codex/amp/zed/crush/openhands)
.devinignore                         # when ignore entries exist
.devin/mcp_config.json               # when MCP entries exist
.devin/hooks.v1.json                 # when hook entries exist

Windsurf became Devin Desktop (2026-06). Devin Desktop prefers .devin/rules/*.md and keeps .windsurf/rules/ as a backward-compat fallback (.windsurfrules is legacy), so rules now emit at the preferred path. The target keeps its windsurf name: existing outputs.windsurf.* keys and x-windsurf meta continue to work. Set outputs.windsurf.rules-dir: .windsurf/rules to stay on the old layout; otherwise sync sweeps managed leftovers at the pre-rename path (hand-authored files survive). Devin also reads the cross-tool root AGENTS.md, so sync distributes the shared pointer body there (see the entry-point table above, #645).

  • Agents: one custom subagent profile per agent at .devin/agents/<name>.md. "Custom subagents are defined as markdown files under agents/", project layout .devin/agents/, "Flat file: agents/<name>.md" (docs.devin.ai/cli/subagents). Frontmatter carries name, description, model, allowed-tools, and max-nesting; the body after the closing delimiter is the subagent's system prompt. Agents used to flatten into .devin/rules/agent-<name>.md, which reached the rules loader instead of the subagent loader and had no key for any of those five fields (target-audit 2026-08-27, #638); a managed copy at the old name is swept for every current agent. A scoped agent lands flat here, since Devin documents sub-directory discovery for rules only. allowed-tools translates agnostic-ai's Claude-style names onto the five Devin publishes as its complete set, read, edit, grep, glob, exec (docs.devin.ai/cli/reference/permissions): Read/Grep/Glob/Bash map one-to-one onto read/grep/glob/exec, while Write and Edit both collapse onto edit, so an agent declaring only Write also gains edit capability. An mcp__<server>__<tool> name passes through untranslated. Anything else drops with a coverage note rather than shipping a name the vendor never documented; set x-windsurf.allowed-tools to write Devin's vocabulary directly, and x-windsurf.max-nesting for the nesting override, which has no generic spec field. model passes through verbatim, since the vendor's own example pins model: sonnet. The vendor caveat holds: "Custom subagents are experimental. The format, behavior, and configuration options may change in future releases."
  • Scoped rules: a scoped rule lands at <scope>/.devin/rules/<name>.md, not nested inside the root rules dir. Devin reads ".devin/rules or .windsurf/rules in any sub-directory of your workspace" (docs.devin.ai/desktop/cascade/memories) and globs each one single-level as .devin/rules/*.md (docs.devin.ai/cli/extensibility/rules), so the old .devin/rules/<scope>/<name>.md reached no documented discovery path (target-audit 2026-08-27, #628). Sync sweeps the old nested tree through the ledger. With outputs.windsurf.rules-dir set the prefix follows it, so the legacy layout scopes to <scope>/.windsurf/rules/<name>.md. Devin CLI loads a sub-directory rules dir lazily, when the agent touches files there; Devin Desktop discovers every one of them at session start, so the scope narrows what the CLI sees but not what Desktop sees.
  • Rule activation: a rule that sets alwaysApply: false carries a trigger frontmatter key, the activation mode Devin reads. globs present writes trigger: glob plus the pattern verbatim, a description alone writes trigger: model_decision, and neither writes trigger: manual. An always-on rule stays bare: Devin loads a file with no frontmatter as always-on, and its Always On mode puts the full body in the system prompt on every message, so a description has no job there. Devin's fifth documented value, agent, has no counterpart in the spec format and is never emitted. Before this, no rule file carried frontmatter, so alwaysApply: false was silently promoted to always-on (#628).
  • Skills: one folder per skill under .agents/skills/<name>/SKILL.md. Devin documents .agents/skills/, .devin/skills/, and .windsurf/skills/ as project paths. This adapter writes the shared first path so identical skills dedupe with Codex, Amp, Zed, Crush, OpenHands, Antigravity, Augment, and Kilo. import windsurf reads all three in that order, and the first same-name skill wins. Native triggers values move under x-windsurf in the source spec, then return to top-level frontmatter on sync. This preserves [user], [model], and combined invocation policy without leaking a Devin-only field to other targets. Sibling assets and modes survive every path.
  • Ignore: ignore specs emit as .devinignore, gitignore syntax under a # provenance header: "you can add a .devinignore file to your repo root, with the same syntax as .gitignore" (docs.devin.ai/desktop/context-awareness/windsurf-ignore). Devin Desktop also still respects the legacy .codeiumignore filename and .windsurfignore, but this adapter only writes the current .devinignore path; override via outputs.windsurf.ignore-file to write one of the legacy names instead.
  • MCP: merges into .devin/mcp_config.json under a root mcpServers map, the file Devin Local reads for project scope, not Cascade: Devin Desktop v3.9.19 removed Cascade, leaving Devin Local as the only agent (docs.devin.ai/desktop/changelog.md, target-audit 2026-09-09, #707), and Cascade's own MCP page states "The MCP configuration on this page applies to the legacy Cascade agent only. The Devin Local agent ... configures MCP servers in the Devin CLI config files instead." The schema carries command/args/env for local (stdio) servers, and url/transport (http or sse)/headers/oauthClientId/oauthClientSecret/oauthResource for remote ones; both accept disabled, which devin mcp enable|disable also toggles on this file (see disabled support by target). The field is spelled transport, not the type key the shared mcpServers-with-type builder writes for claude, cursor, and the rest, so this adapter holds its own schema. The vendor documents that the file moved here in v3000.3 (Local 3.6); mcpServers entries in the older .devin/config.json migrate to this dedicated file automatically on startup, so this path is correct for both. Cascade's own MCP file, ~/.codeium/windsurf/mcp_config.json, is user-tier and out of reach: agnostic-ai only emits project-tier files, so this is a new surface rather than a restored one. Devin CLI also documents a third scope this adapter does not write: .devin/mcp_config.local.json, saved there "by default" and "gitignored... use these for personal API keys" (docs.devin.ai/cli/extensibility/mcp/configuration), next to the project-committed .devin/mcp_config.json above ("shared with your team via version control"). The vendor's local/committed split exists so a team can share servers while keeping personal secrets out of version control; agnostic-ai's own generated .devin/mcp_config.json is itself added to the managed .gitignore block by default (gitignore.enabled: true), so it is never committed either, and the split's purpose does not apply to agnostic-ai's own output (target-audit 2026-08-11, #609). A type: ws spec emits no server and raises a coverage note because Devin documents only http and sse remote transports.
  • Hooks: merge into .devin/hooks.v1.json: "Create .devin/hooks.v1.json in your project" (docs.devin.ai/cli/extensibility/hooks/overview, #629). Eight events: PreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit, Stop, PostCompaction, SessionStart, SessionEnd. Unlike Claude Code, Codex, Gemini, and Qoder's shared {"hooks": {...}} wrapper, "the hooks object is the entire file (no wrapper key needed)", so this adapter's document renders {"<Event>": [...]} at the top level with no wrapper. Per entry: type ("command" runs a shell command, or "prompt" evaluates an LLM prompt instead; agnostic-ai's generic hook spec has no prompt field, so that shape only reaches the file through a hand-authored type/prompt Meta pair), command, and optional timeout (seconds). matcher is a regex on the event's tool_name, available on PreToolUse, PostToolUse, and PermissionRequest. Devin CLI names its own tools in lowercase snake_case (exec, edit, read, write, apply_patch, grep, glob, webfetch, ...), not Claude's PascalCase, so a matcher carried over from a Claude spec parses as a valid regex and then matches nothing; that case surfaces a coverage note rather than a guessed rename, the same treatment OpenHands and Antigravity give their own mismatched vocabularies.

outputs.windsurf.workflows-dir no longer emits anything. It used to write each agent as a Workflow, invokable in Cascade as /<name>, but Devin Desktop v3.9.19 ("September 8, 2026") removed Cascade, the only agent that ever read one: "Cascade has been removed. Devin Local is now the only agent available in Devin Desktop" (docs.devin.ai/desktop/changelog.md). Devin Local does not pick the surface back up: "Workflows are not available with the Devin Local agent. Migrate your workflows to skills with the Devin: Open Cascade Migration Wizard command" (docs.devin.ai/desktop/devin-local, Limitations; target-audit 2026-09-09, #707). Setting the key now only prints a warning naming that migration path; the native .devin/agents/<name>.md emission happens either way, unaffected.

Config keys: outputs.windsurf.rules-dir (default .devin/rules), outputs.windsurf.agents-dir (default .devin/agents), outputs.windsurf.skills-dir (default .agents/skills), outputs.windsurf.workflows-dir (default empty; set, it only warns, see above), outputs.windsurf.ignore-file (default .devinignore), outputs.windsurf.mcp-file (default .devin/mcp_config.json), outputs.windsurf.hooks-file (default .devin/hooks.v1.json).

Verify with the real IDE:

  1. Install Devin Desktop from devin.ai (formerly windsurf.com).
  2. Check the tree: ls .devin/rules/ .devin/agents/ .agents/skills/, grep "Generated by agnostic-ai" .devin/rules/*.md for the provenance header, test -f .agents/skills/*/SKILL.md, python -m json.tool .devin/mcp_config.json > /dev/null when MCP specs exist, python -m json.tool .devin/hooks.v1.json > /dev/null when hook specs exist.
  3. Open the project. Devin Local loads every .devin/rules/*.md; each appears in the Rules panel with no "failed to parse" warnings (docs.devin.ai/cli/extensibility/rules still documents this discovery path; only the agent that reads it changed with Cascade's removal, #707). Each .agents/skills/<name>/ loads as a skill. With a scoped rule, <scope>/.devin/rules/<name>.md appears in the same panel, and a rule with trigger: frontmatter shows that activation mode rather than Always On. Ask Devin CLI to use a subagent by name (review this using the <name> subagent); each .devin/agents/<name>.md profile appears alongside the built-in subagent_explore and subagent_general, with no "profile skipped" warning.
  4. outputs.windsurf.workflows-dir has nothing left to verify: no shipping Devin Desktop agent reads a Workflow file, so setting the key only prints a sync-time warning now (#707).
  5. When ignore specs exist, cat .devinignore shows the concatenated patterns and indexing skips them.
  6. Devin Local is the only agent in Devin Desktop (Cascade was removed in v3.9.19, #707); confirm each mcpServers.<name> from .devin/mcp_config.json connects, with a disabled spec showing as disabled.
  7. Run /hooks in Devin CLI to confirm each entry in .devin/hooks.v1.json loads, with the file listed as its source.

Continue (continue)

.continue/rules/<name>.md
.continue/mcpServers/<name>.yaml       # one per MCP entry
.continue/assistants/<name>.yaml       # one per agent, only when assistants-dir is set
  • Rule activation: scoped rules emit globs constrained to the directory and omit alwaysApply, allowing file matching to control inclusion. Unscoped rules retain their configured globs, alwaysApply, and description; x-continue.regex is available only without scope. See scoped selector limits.
  • MCP: each YAML under .continue/mcpServers/ is a Continue block file: a name + version + schema: v1 wrapper with the server nested under an mcpServers: list (a flat single-server file does not load). Stdio emits command/args/env. Remote servers emit type/url/requestOptions, with no env: Continue declares that field on its stdio server only, so a remote entry that sets one drops it and raises a coverage note rather than writing a key the schema strips. The spelling is Continue's rather than the spec's: type: http lands as type: streamable-http, the only Streamable HTTP literal Continue's schema accepts (sse and streamable-http carry over unchanged), and a headers map nests as requestOptions.headers, the only place that branch reads headers from. import continue undoes both, so a spec that round-trips through Continue stays portable. Two kinds of entry emit no file at all, each with a coverage note. type: ws, because Continue documents no websocket transport. And an entry missing the field its branch requires (command on stdio, url on a remote server), which trae, warp, antigravity and windsurf decline too. Both would match neither branch of the union and make blockSchema.parse throw, failing the whole file rather than skipping the entry.
  • Assistants: when outputs.continue.assistants-dir is set, each agent also emits as a YAML file at <dir>/<name>.yaml: name, version (0.0.1 by default), and schema: v1 at the top level, with the agent body wrapped as a single prompts: [{name, description, prompt}] entry. That shape matches Continue's own schema exactly (promptSchema and configYamlSchema in continuedev/continue's packages/config-yaml/src/schemas/index.ts; docs.continue.dev/reference documents the same name/version/schema: v1 top level), confirmed 2026-08-08 after the prior citation, /hub/assistants/intro, 404d and the whole /hub/ doc namespace turned out to be gone (#563). What is not confirmed: that Continue itself scans outputs.continue.assistants-dir as a directory of assistants. understanding-configs.mdx describes Local Configuration as one global ~/.continue/config.yaml, with no project-scoped per-agent directory anywhere in the current docs. Since the emitted file is a self-contained, valid config.yaml, point Continue at it explicitly instead: cn --config <dir>/<name>.yaml, or the IDE's config picker. Models and rules are omitted so user defaults apply. The rule-form emission (.continue/rules/agent-<name>.md) still happens either way.

Stdio MCP servers preserve cwd; every transport preserves connectionTimeout. Remote requestOptions preserves timeout, TLS, proxy, certificate, and header settings. Portable headers fills requestOptions.headers; an explicit native header wins a duplicate key. x-continue overrides each corresponding top-level option. import continue reads YAML blocks containing exactly one server and JSONC .json files, either an mcpServers map or a bare server named after the file. Unsafe names and duplicate names across files fail before MCP specs are written.

Config keys: outputs.continue.rules-dir (default .continue/rules), outputs.continue.mcp-dir (default .continue/mcpServers), outputs.continue.assistants-dir (default empty, opt-in).

Verify with the real extension:

  1. Install the Continue extension in VS Code (or JetBrains).
  2. Check the tree: ls .continue/rules/ .continue/mcpServers/, grep "Generated by agnostic-ai" .continue/rules/*.md .continue/mcpServers/*.yaml for the provenance header, python -c "import yaml,sys; [yaml.safe_load(open(f)) for f in __import__('glob').glob('.continue/mcpServers/*.yaml')]".
  3. Open the project. Continue loads every .continue/rules/*.md; each appears in the rules picker with no "failed to parse" warnings. A rule with globs shows as active only while a matching file is in context.
  4. The MCP picker shows each .continue/mcpServers/<name>.yaml green.
  5. If outputs.continue.assistants-dir is set, each <dir>/<name>.yaml parses as a valid config.yaml (python -c "import yaml; yaml.safe_load(open('<dir>/<name>.yaml'))"). Native directory discovery is unconfirmed, so load one explicitly with cn --config <dir>/<name>.yaml to confirm Continue accepts it.

Amp (amp)

AGENTS.md                              # canonical entry-point pointer body (written by sync, shared across the AGENTS.md consumers)
.agents/skills/<name>/SKILL.md         # one folder per skill (Amp's native skills path)
.agents/setup                          # executable dependency setup when an environment sets install
.amp/services.yaml                     # supervised services when an environment sets terminals
.amp/settings.json                     # when MCP entries exist (merged with existing user config)
  • Rules: root rules inline into AGENTS.md. Scoped rules use <scope>/AGENTS.md; see directory-specific instructions for shared-reader compatibility.
  • Skills: one folder per skill under .agents/skills/<name>/SKILL.md (Amp's native skills layout). Amp removed custom slash commands in favor of skills, so skills no longer emit as .agents/commands/skill-<name>.md. The SKILL.md frontmatter carries name + description; sibling assets next to the source SKILL.md are copied byte-for-byte. import amp restores the full folder and preserves executable modes. Arbitrary x-amp keys pass through, which is how a skill scopes its own MCP servers: Amp's docs say "a skill can define MCP servers in a sibling mcp.json file or in the mcpServers field of its SKILL.md frontmatter", and prefers mcpServers when both are present. Set x-amp.mcpServers on the skill spec and it lands in the emitted frontmatter verbatim, so the server's tools stay hidden until that skill loads. Amp's docs recommend this over user settings "for most use cases" (#591).
  • Agents: no file of their own. They used to emit as slash commands under .agents/commands/<name>.md, until Amp removed custom commands in favor of skills on 2026-01-29 and its migration steps ended with "Delete the original command file". Emitting there wrote a file Amp never reads, on a green sync with no warning, so sync now writes nothing and sweeps what a previous sync left behind (#727). Agent bodies reach Amp through the merged document when outputs.amp.rules-file is set, and otherwise only through the entry-point pointer to the source specs, which sync reports as a coverage note. The migration's replacement path, .agents/skills/<name>/SKILL.md, is deliberately not reused: that tree is shared with codex, goose, crush, factory, augment and antigravity, most of which already emit the same Agent spec to their own native agent surface, so writing it there as a skill would duplicate one spec inside those tools and would overwrite a Skill spec of the same name. A native agent surface does exist (amp.createAgent(...) / amp.registerAgentMode(...) in plugin TypeScript), but it is programmatic, out of reach of a declarative emitter.
  • Commands: not supported. A full sweep of every page in Amp's docs index finds no file-based command surface at all: commands register programmatically via amp.registerCommand(...) in plugin TypeScript, and the migration post tells users to delete the old command file rather than pointing at a replacement path. Since there is no path left to redirect a Command spec to, a Command spec targeting amp is skipped with a warning (on-unsupported: warn by default) instead of writing a file Amp never reads. .agents/checks/, a code-review surface documented as recently as the 2026-08-20 snapshot, is also gone from the current docs; that surface retires rather than becoming a target for a future reviews emitter.
  • MCP: written into .amp/settings.json under amp.mcpServers (a single dotted key, not a nested object). Stdio emits command/args/env; HTTP/SSE emit url/headers. Any other field reaches the entry through x-amp, the same passthrough commands and skills already have; the builder enumerated a fixed set with no escape hatch until #634, so even an explicit x-amp key was dropped. The field this unblocks is includeTools, "optional but recommended" per ampcode.com/docs/customize/skills: "tool names or glob patterns used to choose which tools are exposed". It stays namespaced rather than mapped top-level because Amp's MCP page says servers "use the same configuration fields as MCP servers in skills" and then enumerates without naming it, so the clause implies the field and the enumeration does not. Amp's own advice is to trim: "Too many available tools can reduce model performance, so for best results, be selective." Pre-existing non-managed keys (theme, editor settings) are preserved; only amp.mcpServers is overwritten. Workspace MCPs require explicit approval on first open (Amp's safety model). This is the project-wide surface; to scope a server to one skill instead, which Amp's manual recommends "for most use cases", put it under x-amp.mcpServers on the skill spec (see Skills above) rather than emitting an MCP spec here.
  • Environments: install writes executable .agents/setup, which Amp runs while preparing a project orb or snapshot. Named terminals entries write .amp/services.yaml as supervised services, so long-running processes survive independently of the setup script. Each service requires command; its name must contain only lowercase letters, numbers, and hyphens, start with a letter or number, and stay within 32 characters. Remaining terminal fields pass through, so an x-amp.terminals override can use native cwd, port, env, health, portal, portals, review, and agent controls. .agents/resume is deliberately not emitted because it runs after activation and every wake with thread credentials, which is not equivalent to dependency installation or a supervised service. Multiple environment specs merge by top-level field, last value wins.
  • Legacy rename: Amp's owner's manual specifies AGENTS.md (plural). On first sync after upgrading, any agnostic-generated AGENT.md at the configured root is renamed to AGENT.md.bak. A user-authored AGENT.md (no Generated by agnostic-ai marker) is left untouched.

Config keys: outputs.amp.skills-dir (default .agents/skills), outputs.amp.mcp-file (default .amp/settings.json), outputs.amp.setup-file (default .agents/setup), outputs.amp.environment-file (default .amp/services.yaml), outputs.amp.rules-file (unset; writes legacy concatenated rules and skips the pointer-body write). outputs.amp.commands-dir no longer affects Amp now that no command surface is left to point at.

Verify with the real CLI:

  1. Install with Amp's recommended direct installer: curl -fsSL https://ampcode.com/install.sh | bash. If your environment requires npm, use npm install -g @ampcode/cli. The VS Code extension reads the same files.
  2. Check the tree: ls AGENTS.md .agents/skills/ .agents/setup .amp/services.yaml .amp/settings.json, grep "Generated by agnostic-ai" .agents/skills/*/SKILL.md .agents/setup .amp/services.yaml, test -x .agents/setup.
  3. Confirm no command files came back: test ! -d .agents/commands.
  4. Validate the generated files: python -m json.tool .amp/settings.json > /dev/null and amp orb services ensure inside an orb.
  5. Open the project; Amp indexes AGENTS.md under "Project rules", loads each .agents/skills/<name>/SKILL.md as a skill, runs .agents/setup while preparing a new snapshot, and supervises each declared service.
  6. The MCP picker lists every amp.mcpServers entry green. Workspace MCPs prompt for approval the first time (expected, not an error).

Zed (zed)

.rules                                 # canonical entry-point pointer body + inlined rules (written by sync)
.agents/skills/<name>/SKILL.md         # one folder per skill (shared tree with codex/amp/crush)
.zed/settings.json                     # when MCP entries exist (merged with existing user config)
.zed/tasks.json                        # one task per hook, only when tasks-file is set
  • Rules: Zed 1.4.2 retired its rules library, so always-on project instructions are an instruction file. sync writes the pointer body plus the sentinel-marked ## Rules block to the root .rules. Zed reads the first matching file from .rules, .cursorrules, .windsurfrules, .clinerules, .github/copilot-instructions.md, AGENT.md, AGENTS.md, CLAUDE.md, GEMINI.md and stops there. AGENTS.md is rank 7, behind Copilot's pointer-only entry-point at rank 5, so writing the rules to AGENTS.md left Zed with none of them whenever copilot was enabled too (target-audit 2026-08-27, #624). .rules is rank 1 and nothing agnostic-ai emits can outrank it. Zed still calls AGENTS.md its primary instruction file and .rules a compatibility one, so a Zed release dropping .rules moves this back. Set outputs.zed.rules-file: .rules to replace the pointer body with the legacy merged document (which also carries agent bodies) for older Zed versions.
  • Skills: native Zed skills folders at .agents/skills/<name>/SKILL.md, the cross-tool path codex, amp, and crush emit too. Identical rendered bytes dedupe into one write; divergent x-zed overrides surface through the collision check. Zed also documents disable-model-invocation ("Set to true to hide from the agent's catalog (invocable via slash command or @-mention only)"): it reaches the emitted file via x-zed: {disable-model-invocation: true}, since this renderer merges only x-zed keys. A plain top-level disable-model-invocation: field, the form Cursor promotes to a native key, has no effect here.
  • Agents: no per-agent surface in current Zed; sync prints a coverage note unless outputs.zed.rules-file is set (the merged document carries agent sections).
  • MCP: written into .zed/settings.json under context_servers (Zed's key, not mcpServers). Stdio servers use a flat command/args/env shape; remote (HTTP / SSE) servers use a native url/headers shape. A spec's disabled: true emits as Zed's own enabled: false; an enabled server gets no key, since Zed defaults it to true. That toggle is file-backed, not UI-only, and the evidence is Zed's Rust settings struct rather than its docs: every variant of ContextServerSettingsContent in crates/settings_content/src/project.rs carries /// Whether the context server is enabled. over #[serde(default = "default_true")] enabled: bool, and context_servers is a field of ProjectSettingsContent, the struct .zed/settings.json deserializes into (target-audit 2026-08-27, #641). zed.dev/docs/ai/mcp names none of it; its only enabled-family key is enable_all_context_servers, an agent-profile key rather than a per-server one, which is why earlier audits missed this. The same source carries three more per-server fields no doc page names (timeout on either transport, oauth on an HTTP server, remote on stdio and extension servers); those reach the file through x-zed, since no other target documents a same-named field with the same meaning. timeout is on both transports because the Stdio variant #[serde(flatten)]s ContextServerCommand, whose own field reads "Timeout for tool calls in seconds. Defaults to 60 if not specified" (target-audit 2026-09-11, #737). User-managed keys (theme, buffer_font_size) are preserved. See disabled support by target.
  • Hooks: when outputs.zed.tasks-file is set, hook specs emit as Zed Tasks using sh -c "<hook command>". WorktreeCreate writes hooks: ["create_worktree"], so Zed runs the task after creating a linked worktree; import restores that event. Tasks without that hook import as OnDemand and run from the command palette. Any other documented Zed Task field passes through under x-zed.

Zed skill names must contain 1-64 lowercase letters or digits, with single hyphens between segments. Invalid names fail sync with the name and required format; they are not renamed. import zed copies .agents/skills/<name>/SKILL.md and bundled assets.

Config keys: outputs.zed.skills-dir (default .agents/skills), outputs.zed.mcp-file (default .zed/settings.json), outputs.zed.tasks-file (default empty, opt-in), outputs.zed.rules-file (unset; writes the legacy merged document and skips the pointer-body write, so pointing it at .rules replaces the entry-point rather than colliding with it).

Verify with the real editor:

  1. Install Zed from zed.dev.
  2. Check the tree: ls .rules .agents/skills/ .zed/settings.json .zed/tasks.json, test -f .agents/skills/*/SKILL.md, python -m json.tool .zed/settings.json > /dev/null, python -m json.tool .zed/tasks.json > /dev/null.
  3. Open the project. The agent panel reads .rules as project instructions and lists each .agents/skills/<name>/ as a skill (@skill / slash command).
  4. The MCP picker shows each context_servers.<name> ready.
  5. The command palette runs every entry from .zed/tasks.json as a Zed Task.

Warp (warp)

AGENTS.md                              # canonical entry-point pointer body (written by sync, shared across the AGENTS.md consumers)
.agents/skills/<name>/SKILL.md         # one folder per skill (shared tree with codex/amp/zed)
.warp/workflows/<name>.yaml            # one per agent, only when workflows-dir is set
.warp/.mcp.json                        # when MCP entries exist
  • Rules: root rules inline into AGENTS.md. Scoped rules use <scope>/AGENTS.md; see directory-specific instructions for shared-reader compatibility.
  • Skills: native Warp skills folders at .agents/skills/<name>/SKILL.md, the vendor's own recommended path and the cross-tool tree codex, amp, and zed emit too. A source-layout scope moves the native tree under that directory and survives import. Identical root renders dedupe into one write. Warp's docs list ten scanned directories in total: .agents/skills/ (recommended), plus .warp/skills/, .claude/skills/, .codex/skills/, .cursor/skills/, .gemini/skills/, .copilot/skills/, .factory/skills/, .github/skills/, and .opencode/skills/; a WARP_SKILL_DIRS env var indexes further directories, scoped to Cloud agents indexing skills that live outside the repo, not a general extension of the ten scanned directories. This adapter only writes the recommended path. .opencode/skills/ is OpenCode's own default skills directory, so a project running both tools gets Warp skill-scanning for free with no extra write.
  • Workflows: when outputs.warp.workflows-dir is set, each agent emits as a Warp Workflow YAML at <dir>/<name>.yaml (name/command/description/tags). The vendor page now opens with a caution that it recommends new workflows in Warp Drive instead "for a better editing experience"; nothing breaks here, since {{path_to_git_repo}}/.warp/workflows/ still loads and Warp Drive workflows are cloud-stored, so they are not a file this adapter could emit as an alternative. The command: is the agent body verbatim; tailor it to a Warp-friendly shell snippet. Other documented workflow fields (shells, arguments, source_url, author, author_url) pass through when declared under x-warp; import warp captures them back the same way.
  • MCP: written into .warp/.mcp.json under the standard mcpServers map. A stdio server's command/args/env carry through, plus working_directory: docs.warp.dev/agents/capabilities/mcp documents it as "Working directory path where the command is run, used for resolving relative paths," Warp's own name for the cross-tool spec's cwd field. A remote server (HTTP/SSE/WS) carries url/headers; Warp's remote-server table has no transport discriminant at all, so no type field is ever emitted, unlike claude, cursor, and the rest of the shared-builder targets. Those two tables are the whole emitted key set. description, disabled, and roots appear in neither and no longer emit (target-audit 2026-08-27, #641): they came from the shared builder this adapter used before it grew its own and carried through that split unexamined, and writing a key from outside a closed vendor list asserts support no vendor sentence backs. disabled raises a coverage note instead of vanishing, and little is lost either way, since the same page says "project-scoped servers never auto-spawn" there. description and roots stay reachable through x-warp for anyone who wants them written anyway. import warp reads .warp/.mcp.json back, renaming working_directory to cwd. An entry missing the field its table marks required (command on the CLI Server table, url on the URL Server one) emits nothing at all, the same call trae, antigravity, and windsurf make. Until #753 the missing key was left out and the rest of the entry was written, so a spec carrying only args or env produced a server Warp lists and cannot launch.
  • Legacy rename: Warp's docs recommend AGENTS.md for new projects but still fully support WARP.md, and rank it first: "If both WARP.md and AGENTS.md exist in the same directory, WARP.md takes priority" (docs.warp.dev/agents/capabilities/rules, target-audit 2026-09-08, #691). On first sync after upgrading, any agnostic-generated WARP.md at the configured root is renamed to WARP.md.bak so the new AGENTS.md layout takes over. A user-authored WARP.md (no Generated by agnostic-ai marker) is left untouched, but since it still outranks AGENTS.md, sync warns that none of the synced rules reach Warp until that file is renamed or removed.

import warp copies .agents/skills/<name>/SKILL.md and bundled assets.

Config keys: outputs.warp.skills-dir (default .agents/skills), outputs.warp.workflows-dir (default empty, opt-in), outputs.warp.mcp-file (default .warp/.mcp.json), outputs.warp.rules-file (unset; writes legacy concatenated rules and skips the pointer-body write).

Verify with the real terminal:

  1. Install Warp from warp.dev.
  2. Check the tree: ls AGENTS.md .agents/skills/ .warp/workflows/ .warp/.mcp.json, test -f .agents/skills/*/SKILL.md, grep "Generated by agnostic-ai" .warp/workflows/*.yaml for the provenance header, python -m json.tool .warp/.mcp.json > /dev/null.
  3. Open the project; confirm the rules panel surfaces AGENTS.md (no "unrecognized file" warnings), the skills picker lists each .agents/skills/<name>/, the Warp Drive workflows picker shows every <workflows-dir>/<name>.yaml, and the MCP picker lists every mcpServers.<name> from .warp/.mcp.json.

OpenCode (opencode)

AGENTS.md                                 # canonical entry-point pointer body + inlined rules (written by sync)
.opencode/agents/<name>.md                # one native subagent definition per agent
.opencode/skills/<name>/SKILL.md          # one folder per skill, bundled assets included
.opencode/commands/<name>.md              # one per command spec
.opencode/commands/skill-<name>.md        # additional command form, only when emit-skills-as-commands: true
opencode.json                             # when MCP entries exist (merged with existing user config)
  • Routing: the entry point is the repo-root AGENTS.md, the file OpenCode's rules lookup walks up for ("Local files by traversing up from the current directory (AGENTS.md, CLAUDE.md)"), confirmed in the vendor's own packages/core/src/instruction-context.ts on branch dev: fs.up({ targets: ["AGENTS.md"] }). It shares that path with codex, amp, warp, and the rest of the AGENTS.md family; the pointer body and the inlined rules block are byte-identical across them, so sync writes the file once instead of colliding. Before #623 the adapter wrote .opencode/AGENTS.md to stay clear of Codex, and no OpenCode doc or code path ever read it: in a project syncing only opencode, no rule reached the tool at all. A managed leftover at the old path is swept on the next sync; a hand-authored one is left alone.
  • Agents: native OpenCode agents at .opencode/agents/<name>.md (plural dir; the singular is legacy): frontmatter filtered to description, mode, model, temperature, permission, with arbitrary x-opencode keys passing through. The body is the system prompt.
  • Skills: native OpenCode skills folders at .opencode/skills/<name>/SKILL.md (OpenCode also scans .claude/skills/ and .agents/skills/), with bundled sibling files propagated byte-for-byte. A source-layout scope moves the full native tree under that directory and survives import. Set outputs.opencode.emit-skills-as-commands: true to additionally emit the command form.
  • Commands: one markdown file per command spec under .opencode/commands/<name>.md, frontmatter filtered to the OpenCode command keys (description, agent, model, subtask).
  • MCP: written into opencode.json at the project root with a $schema link and the mcp map. Stdio maps to {type: "local", command: [...], cwd}; HTTP/SSE/remote maps to {type: "remote", url, headers}. cwd is documented on the local-server table only ("Working directory for the MCP server process. Relative paths resolve from the workspace."), and timeout on both ("Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds)."); both names match the spec field exactly, so both map top-level with no rename (target-audit 2026-09-03, #641). A spec's disabled: true writes "enabled": false, the key OpenCode's own MCP docs document ("You can also disable a server by setting enabled to false"); an enabled server gets no key at all, and import opencode reads enabled: false back into disabled: true. Any other documented field, including the oauth client-credentials object for a "Pre-registered" remote server (same doc), reaches the entry through x-opencode, the same passthrough commands and agents already have. Pre-existing non-managed keys (theme, model) are preserved; only $schema and mcp are overwritten. Drift checks (sync --check, doctor) read the existing file, so user keys never report as drift and doctor --fix keeps them.
  • Settings: a settings spec's default model merges into the same opencode.json. Existing native keys survive. import opencode restores the field to settings/imported.yaml.

Config keys: outputs.opencode.agents-dir (default .opencode/agents), outputs.opencode.skills-dir (default .opencode/skills), outputs.opencode.commands-dir (default .opencode/commands), outputs.opencode.mcp-file (default opencode.json), outputs.opencode.emit-skills-as-commands (default false), outputs.opencode.rules-file (unset; writes legacy concatenated rules and skips the pointer-body write).

Verify with the real CLI:

  1. Install: npm install -g sst/opencode (install docs).
  2. Check the tree: ls AGENTS.md .opencode/agents/ .opencode/skills/ .opencode/commands/ opencode.json, grep "Generated by agnostic-ai" .opencode/agents/*.md for the provenance header (it sits after the frontmatter), python -m json.tool opencode.json > /dev/null.
  3. Launch opencode. Every rule body from AGENTS.md is in context, every .opencode/agents/<name>.md appears in the agent picker, every .opencode/skills/<name>/ in the skills list, and every .opencode/commands/<name>.md in the slash-command picker.
  4. The MCP panel shows each mcp.<name> from opencode.json ready, with a disabled spec showing as disabled.

Google Antigravity (antigravity)

.agent/AGENTS.md               # canonical entry-point pointer body (written by sync)
.agents/rules/<name>.md        # one per rule
.agents/agents/<name>/agent.md # one per agent (custom subagent)
.agents/skills/<name>/SKILL.md # one folder per skill (Antigravity's native path)
.agents/mcp_config.json        # when MCP entries exist

Antigravity reads project instructions from a top-level AGENTS.md-style file, per-rule files under .agents/rules/, and custom subagents under .agents/agents/. The adapter emits all three; sync writes the pointer body to .agent/AGENTS.md. The entry-point path stays under .agent/ (singular) to avoid clashing with codex / amp / warp at the project-root AGENTS.md; rules, skills, and MCP default to the plural .agents/ form Antigravity itself now prefers (rules, skills), which still "maintains backward support" for the singular paths. A stale managed tree at the pre-plural .agent/rules / .agent/skills defaults is swept on sync unless outputs.antigravity.rules-dir / skills-dir opts back into the legacy path explicitly. import antigravity prefers .agents/rules and .agents/skills, falling back to their singular .agent/ counterparts only when the preferred directory is absent. Skill imports include bundled assets.

  • Agents: one custom subagent per agent at .agents/agents/<name>/agent.md, the nested workspace form documented alongside the flat form in Antigravity's subagent reference. Goose and OpenHands only scan top-level .md files in the same root, so the nested form keeps Antigravity's restricted model tier separate from their free-form model IDs (#717). Frontmatter carries name and description, both required; the body defines the system prompt. A managed flat profile from an earlier sync is removed by the sync ledger when no enabled target still writes it. When Goose or OpenHands is enabled, that flat path remains as their current shared output. import antigravity prefers nested profiles and falls back to legacy flat files only when no nested profile exists, so it does not ingest co-located Goose or OpenHands agents. Agents previously flattened into .agents/rules/agent-<name>.md, a path the subagent loader never reads (#638); a managed copy at the old name is swept for every current agent. A generic tools list never reaches this file: Antigravity's vocabulary is its own (view_file, replace_file_content, grep_search, run_command, ...) with no name in common with agnostic-ai's Claude-style set, and the vendor warns that an unmapped name can hang the subagent. It drops with a coverage note; set x-antigravity.tools to write Antigravity's own names. model is a three-value tier enum (inherit, flash, pro), not a model ID, so a value outside it drops the same way. Every other documented key (mainAgent, subagent, commandExecutionPolicy, mcpServers, skills/plugins) reaches the file through x-antigravity too.
  • Skills: one folder per skill under .agents/skills/<name>/SKILL.md (Antigravity's native skills layout, one folder per skill; the same tree Codex, Amp, Zed, Crush, and OpenHands share, so identical skill folders dedupe). The SKILL.md frontmatter is reduced to name + description; the body follows. Sibling files next to the source SKILL.md (helper scripts, fixtures) are copied byte-for-byte into the emitted folder.
  • MCP: servers land in .agents/mcp_config.json under a single mcpServers object (antigravity.google/docs/ide/mcp). Remote servers carry serverUrl; the vendor doc states the legacy url / httpUrl field names "are not supported," so this is a dedicated schema, not the shared mcpServers-with-url shape claude and cursor use. stdio servers carry command, args, env, and cwd; remote servers add headers. Both transports accept disabled under that literal name (see disabled support by target), unlike codex and kilo which map it onto their own enabled: false. Three more documented fields (authProviderType, oauth, disabledTools, same page) have no dedicated mapping; they, description, roots, and any field the vendor adds next reach the file through x-antigravity instead, the same escape hatch Zed and Warp give their own unmapped fields. import antigravity reads .agents/mcp_config.json back, renaming serverUrl to the spec's generic url and preserving any other field under x-antigravity the same way.
  • Hooks: merge into .agents/hooks.json (override via outputs.antigravity.hooks-file): "Hooks are configured in a hooks.json file located in your customization directory (e.g., .agents/ in your workspace)" (antigravity.google/docs/ide/hooks, #629). The file is keyed by hook definition name, not by event, unlike every other hook target here, so each spec becomes its own top-level definition named after it and holding the one event it names. That is also where enabled lives: a spec's disabled: true writes enabled: false on its own definition ("Set to false to disable the hook without removing it"), a sibling of the event key rather than a member of the handler array, and the inverse spelling of the literal disabled this same adapter writes for MCP servers. Five events: PreToolUse, PostToolUse, PreInvocation, PostInvocation, Stop. The first two hold {matcher, hooks: [...]} groups; for the other three "the structure is simpler (a list of handlers directly under the event key) and the matcher is ignored", so this adapter writes each shape where the vendor documents it and notes a matcher set on the three that ignore it. Per handler: type (optional, defaulting to "command", written explicitly here), command, and optional timeout (seconds, vendor default 30). An event outside the documented five is skipped with a coverage note rather than written as a key no handler backs. Antigravity names its own tools (view_file, replace_file_content, grep_search, run_command, ...) with none in common with Claude's, so a matcher carried over from a Claude spec parses as a valid regex and then matches nothing; that case emits verbatim with a coverage note rather than a guessed rename, the same line OpenHands, Crush, and Windsurf hold. The hook payload's transcriptPath resolves under ~/.gemini/antigravity-ide, the IDE's own app-data directory, confirming the IDE itself runs them (target-audit 2026-08-27, #563).

Commands remain fully unconfirmed in the public-preview docs and skip with a warning. Add on-unsupported: silent to suppress it, or wait for a future release once they stabilise.

Config keys: outputs.antigravity.rules-dir (default .agents/rules), outputs.antigravity.agents-dir (default .agents/agents), outputs.antigravity.skills-dir (default .agents/skills), outputs.antigravity.mcp-file (default .agents/mcp_config.json), outputs.antigravity.hooks-file (default .agents/hooks.json), outputs.antigravity.rules-file (unset; writes a legacy merged document and skips the pointer-body write).

Verify with the real IDE:

  1. Install Antigravity from the Google Antigravity public-preview download page.
  2. Check the tree: ls .agent/AGENTS.md .agents/rules/ .agents/agents/ .agents/skills/, grep "Generated by agnostic-ai" .agents/rules/*.md for the provenance header (it sits after the frontmatter), test -f .agents/skills/*/SKILL.md, and python -m json.tool .agents/mcp_config.json > /dev/null when MCP specs exist.
  3. Open the project; it surfaces .agent/AGENTS.md in the project-instructions panel with no "unrecognized file" warnings.
  4. Open one of .agents/rules/<name>.md and verify the per-rule file is picked up; confirm each .agents/skills/<name>/SKILL.md loads as a skill, and any .agents/mcp_config.json server appears in the MCP panel.
  5. Ask the agent to delegate to a custom subagent by name; each .agents/agents/<name>/agent.md profile is selectable in the subagent panel and its task runs to Idle rather than hanging (a hang points at an unmapped tools name, so check x-antigravity.tools against the vendor's vocabulary).
  6. Trigger an agent action (e.g. ask for a refactor); the rules apply, with no schema-validation log entries referencing .agents/.

Junie (junie)

AGENTS.md                        # root entry-point pointer body (written by sync, shared path; fallback)
.junie/AGENTS.md                 # preferred entry-point: pointer body + inlined rules (written by this adapter)
.junie/agents/<name>.md          # one file per subagent
.junie/skills/<name>/SKILL.md    # one folder per skill, plus any bundled assets
.junie/commands/<name>.md        # one file per slash command
.junie/mcp/mcp.json              # when MCP entries exist
.junie/config.json               # when a settings spec selects a default model
.aiignore                        # when ignore entries exist

Junie's guidelines lookup is strict precedence, first match wins, not a merge: .junie/AGENTS.md ("the most preferred standard location"), then the root AGENTS.md, combined with .junie/playbook.md and every .junie/rules/*.md file, "if no file is found in the .junie folder", then the legacy .junie/guidelines.md / .junie/guidelines/ (junie.jetbrains.com/docs/junie-ide-plugin.html and guidelines-and-memory.html, target-audit 2026-08-08, #552). sync always writes .junie/AGENTS.md, so step 1 always matches, and step 2, including .junie/playbook.md and .junie/rules/*.md, is pre-empted outright in a synced project (junie.jetbrains.com/docs/environment-variables.html: "If this file exists, it is used exclusively; no other guidelines files are combined with it."). Rule bodies therefore inline directly into .junie/AGENTS.md, under a sentinel-marked ## Rules block immediately after the pointer body, using the same ### <name> shape (source comment, optional description, full body) every other inlining target uses. A prior version of this adapter instead flattened rules and agents to one .md file each under .junie/rules/. That directory is read at step 2 alongside .junie/playbook.md, but .junie/AGENTS.md always wins step 1 once sync has run, so a hand-authored file left there is shadowed rather than unread; any agnostic-ai-managed leftovers there are swept on sync (hand-authored files survive).

Subagents and slash commands are both CLI-only surfaces (junie-ide-plugin.html mentions neither, confirmed by full-text search) that shipped after this adapter's original write-up and predate its own tracking issue: junie-cli-subagents.html has been live since 2026-03-10, custom-slash-commands.html since 2026-04-13 (target-audit 2026-08-11, #604 and #605). Subagents emit one file per agent at .junie/agents/<name>.md: "Subagents are Markdown files with YAML metadata stored in the .junie/agents/ or .agents/ directory." This adapter defaults to .junie/agents/, the vendor's own preferred location (the same page says Junie CLI detects .cursor/agents/, .claude/agents/, and .codex/agents/ on open and offers to import them specifically into .junie/agents/), rather than the shared .agents/ tree several other targets already write skills, rules, commands, or an MCP file into. No registered target defaults an agent file into .agents/ itself today, so there is nothing to dedupe with either way. Set outputs.junie.agents-dir: .agents for the shared alternative, the same pattern Codex uses for its own outputs.codex.agents-dir: .agents/agents community layout. Frontmatter passes through verbatim: the vendor's documented fields (name, description, tools, disallowedTools, mcpServers, model, permissionMode, reasoningLevel, maxTurns, skills, allowPromptArgument) are already spelled the way a spec author writes them, so nothing here is translated. reasoningLevel also accepts effort as an alias, taking precedence when both are set; a spec author can write either key and both pass through unchanged. Agent bodies no longer inline into .junie/AGENTS.md now that this native destination exists, the same rule Augment and Kilo Code follow for their own native agents directories; .junie/AGENTS.md is fully regenerated from the canonical pointer body on every sync rather than patched in place, so a project still carrying the pre-#604 inlined ## Agents block loses it on its very next sync with no extra sweep step. One sub-feature on the subagents page, an auto model-selection policy toggle read from /settings → Subagents, is marked Early Access; the base file format and discovery are not caveated.

Slash commands emit one file per command at .junie/commands/<name>.md: "Project-specific commands are stored as Markdown files in the .junie/commands folder at your project's root directory." The vendor documents two frontmatter fields for commands, description and allowPromptArgument (accepts free-form text via a $prompt placeholder); every other key still passes through verbatim, same as agents. The body may reference $argumentName placeholders Junie substitutes at invocation.

Skills are unaffected by any of the above: they emit into their own native folder tree at .junie/skills/<name>/SKILL.md (Junie's Native Agent Skills feature, shipped 2026-07-31): a flat file never loads as a skill, and bundled sibling assets copy byte-for-byte. MCP servers use the standard mcpServers schema at .junie/mcp/mcp.json (command/args/env local, url/headers remote). The IDE plugin doc alone now adds a Custom path step ahead of .junie/AGENTS.md, read from Settings | Tools | Junie | Project Settings; the CLI-facing doc has no such step, and since that per-workspace IDE preference is not usually committed to the repo, it rarely changes which file wins in a synced project (target-audit 2026-08-09, #590).

A settings spec's default model merges into .junie/config.json, preserving unrelated native keys. import junie restores the model to settings/imported.yaml.

Ignore specs emit as .aiignore in the project root: "You can restrict Junie from processing the contents of specific files or folders by creating and configuring an .aiignore file in the project root directory" and "The .aiignore file follows the same syntax and pattern format as the .gitignore file" (junie-ide-plugin.html, target-audit 2026-09-11, #728). The guarantee is softer than a block: Junie "will ask for explicit approval before viewing or editing" a listed file rather than refuse it, only the contents are protected (file and folder names stay visible), and Brave Mode or an allowlisted command naming the path skips the prompt. This is also the one junie surface documented on the IDE-plugin page alone, with no Junie CLI page naming it. Multiple specs concatenate. Override via outputs.junie.ignore-file.

Config keys: outputs.junie.agents-dir (default .junie/agents), outputs.junie.skills-dir (default .junie/skills), outputs.junie.commands-dir (default .junie/commands), outputs.junie.mcp-file (default .junie/mcp/mcp.json), outputs.junie.ignore-file (default .aiignore). outputs.junie.rules-dir (default .junie/rules) no longer controls where anything is written; it only redirects the legacy-tree sweep above, for a project that customized it before this fix. .junie/AGENTS.md is a fixed path, not configurable.

import junie reads .junie/AGENTS.md's Rules block, .junie/agents/<name>.md (or .agents/<name>.md) for agents, .junie/commands/<name>.md for commands, and .junie/skills/<name>/SKILL.md folders for skills. A project synced between #552 and #604 has no native agent file yet; import falls back to .junie/AGENTS.md's pre-#604 sentinel-marked Agents block for that case. A project synced before #552 falls back further, to the pre-fix .junie/rules/ directory, when that still exists on disk.

Verify with the real agent:

  1. Install the Junie plugin in a JetBrains IDE (or the Junie CLI, docs).
  2. Check the tree: ls AGENTS.md .junie/AGENTS.md .junie/agents/ .junie/skills/ .junie/commands/, grep "Generated by agnostic-ai" .junie/AGENTS.md, python -m json.tool .junie/mcp/mcp.json > /dev/null.
  3. Ask Junie to list its guidelines; the rule bodies inlined in .junie/AGENTS.md apply, each .junie/agents/<name>.md appears as a delegatable subagent, each .junie/skills/<name>/ folder appears as a skill, and each .junie/commands/<name>.md appears as a /name slash command.

Kiro (kiro)

AGENTS.md                          # canonical entry-point pointer body (written by sync, shared path)
.kiro/steering/<name>.md           # one per rule (inclusion: always, or fileMatch + fileMatchPattern from globs)
.kiro/skills/<name>/SKILL.md       # one per skill, Kiro's native skill-folder surface (+ bundled scripts/, references/, assets/)
.kiro/agents/<name>.md             # one per agent, Kiro's native agent-profile surface
.kiro/hooks/<name>.json            # one per hook, Kiro's native hook surface
.kiro/settings/mcp.json            # when MCP entries exist
.kiroignore                        # when ignore entries exist

AWS Kiro loads steering files whose YAML frontmatter must be the first content in the file. The adapter maps rules onto Kiro's inclusion modes: unscoped rules are always, globbed rules become fileMatch with fileMatchPattern. Kiro also reads the root AGENTS.md (always included), which carries the shared pointer body.

Skills are native too, not a steering-file convention: one folder per skill at .kiro/skills/<name>/SKILL.md ("Workspace skills (.kiro/skills/)"), the tree Kiro's own skill picker globs (skill://.kiro/skills/*/SKILL.md) and the standard Agent Skills layout (name + description frontmatter) this adapter shares byte-for-byte with the .agents/skills/ render ten other targets already produce. Bundled sibling assets (scripts/, references/, assets/) copy alongside SKILL.md. A prior version of this adapter flattened skills into .kiro/steering/skill-<name>.md with inclusion: auto, which dropped bundled assets entirely and never reached Kiro's skill picker (#642); sync sweeps a stale file of that shape left behind for a current skill name, the same convention agents already use below. Kiro's steering docs separately confirm auto and manual inclusion modes exist; this adapter has no rule shape that needs either.

Agents are native custom agents, not a steering-file convention: one YAML-frontmatter Markdown file per agent at .kiro/agents/<name>.md, the tree Kiro's agent picker reads. description (falls back to the agent name) and model pass through. The canonical spec name remains the safe filename, while x-kiro.name sets Kiro's optional display name and survives import. Kiro's documented agent schema also carries tools, mcpServers, permissions, hooks, keyboardShortcut, and welcomeMessage; only tools has an agnostic-ai spec equivalent. That page documents Kiro's own tools vocabulary (category tags read/write/shell/web/subagent/knowledge/todo_list, @server_name, @server_name/tool_name, @mcp, @builtin, *); /docs/tools/, updated 2026-08-21 versus configuration-reference's 2026-08-04, tables the same field as read/write/shell/web/subagent/spec/context instead, folding knowledge into a new context bundle (disclose_context/introspect/knowledge) and dropping todo_list. The two pages disagree and neither says which one the shipping product follows, so this section keeps citing configuration-reference rather than guessing; it makes no functional difference here, since both pages agree on the four categories a spec's generic tools list actually translates onto: Read/Grep/Glob collapse onto read, Write/Edit onto write, Bash onto shell, and WebFetch/WebSearch onto web, deduplicated. Kiro's built-in-tools catalog documents each category as a bundle rather than a single tool, so this widens access beyond a single Claude-style name on its own: write also covers delete_file, so declaring only Edit grants delete too, and web covers both fetch and search, so either WebFetch or WebSearch alone grants both. A tools value outside agnostic-ai's Read/Write/Edit/Bash/Grep/Glob/WebFetch/WebSearch set has no confirmed Kiro equivalent; it is dropped rather than written unconfirmed and surfaces a coverage note, while any name in the same list that does translate still emits (the same unconfirmed-vocabulary failure class Kilo Code and Augment still hit, since their own tool vocabularies remain undocumented). Set x-kiro.tools to bypass the translation table with Kiro's own vocabulary directly, or x-kiro.mcpServers, x-kiro.permissions, x-kiro.hooks, x-kiro.keyboardShortcut, or x-kiro.welcomeMessage for fields with no agnostic-ai equivalent; arbitrary x-kiro keys always pass through, and x-kiro.tools always wins outright over the translated form. A prior version of this adapter flattened agents into .kiro/steering/agent-<name>.md with inclusion: manual, which never reached the agent picker; sync sweeps a stale file of that shape left over from an older sync.

Hooks are native too: one JSON file per hook spec, {"version": "v1", "hooks": [{name, trigger, matcher, action, timeout, enabled, description}]}. event becomes trigger, passed through verbatim; command (string or list) becomes action: {"type": "command", "command": ...}, one entry per command sharing the file when the spec lists several, name suffixed -2, -3, ... to stay unique. disabled: true writes "enabled": false; the enabled default needs no key. The spec's generic description field (free-form documentation) now reaches the file too; the vendor lists the matching hooks[].description as "Documentation only". Every entry marshals from a map rather than a fixed set of fields, so arbitrary x-kiro keys pass through as well: confirm ("Ask for confirmation before a Stop command hook runs", taking question, options (id/label/run each), and an optional confirmCommand) has no agnostic-ai spec equivalent and is only reachable this way, and x-kiro.action accepts {"type": "agent", "prompt": ...} or {"type": "command", "command": ...}. A valid explicit action needs no generic command and replaces the whole fallback command list with one native action. Invalid actions fail sync instead of running a fallback. Explicit timeout: 0 disables the command timeout; omission retains Kiro's 60-second default. Before #642, description and confirm were unreachable at any layer, including x-kiro, because the prior fixed-struct shape had no route for a key it did not declare. Unlike Claude Code, Codex, Gemini, and Cursor, stashed hook scripts under .agnostic-ai/scripts/ do not materialize into .kiro/hooks/: that directory is where Kiro looks for hook definitions, not scripts.

MCP servers write to .kiro/settings/mcp.json under the standard mcpServers map, the workspace tier on Kiro's own configuration page ("Workspace Level: .kiro/settings/mcp.json"). A local server carries command plus optional args and env; a remote server carries url plus optional headers and env. Both tables also document disabled ("Whether the server is disabled (default: false)"), which passes through under that literal name, unlike Claude Code and Cursor which have no file-based equivalent, plus autoApprove ("Tool names to auto-approve without prompting", "*" for all) and disabledTools ("Tool names to omit when calling the Agent"). A remote server adds an oauth object (clientId, clientSecret, redirectUri, clientMetadataUrl, oauthScopes) and the top-level oauthScopes fallback; an explicitly empty oauthScopes: [] emits as written, since the vendor makes that the documented remedy for scope errors. All four were unreachable before #634, top-level or namespaced. Kiro's oauth is not Claude Code's, so each target maps only the sub-keys its own vendor documents. See disabled support by target.

Ignore specs emit as .kiroignore in the project root, plain gitignore syntax: "To exclude files in a specific project, create a .kiroignore file in your project root (or any subdirectory) and add patterns for files you want to exclude" and ".kiroignore uses standard gitignore syntax" (kiro.dev/docs/kiroignore, target-audit 2026-09-11, #728). Two vendor caveats gate how far the file reaches, and neither is something sync can set: the IDE reads ignore filenames from its own Agent Ignore Files setting, so .kiroignore has to be in the kiroAgent.agentIgnoreFiles array before the IDE honors it (the vendor suggests [".gitignore", ".kiroignore"]), and CLI V3 applies it to content- and filename-search results only rather than across every agent tool. Multiple specs concatenate. Override via outputs.kiro.ignore-file.

Config keys: outputs.kiro.rules-dir (default .kiro/steering), outputs.kiro.agents-dir (default .kiro/agents), outputs.kiro.skills-dir (default .kiro/skills), outputs.kiro.hooks-dir (default .kiro/hooks), outputs.kiro.mcp-file (default .kiro/settings/mcp.json), outputs.kiro.ignore-file (default .kiroignore).

Verify with the real IDE:

  1. Install Kiro from kiro.dev.
  2. Check the tree: ls AGENTS.md .kiro/steering/ .kiro/skills/ .kiro/agents/ .kiro/hooks/ .kiro/settings/mcp.json .kiroignore, head -2 .kiro/steering/*.md .kiro/skills/*/SKILL.md .kiro/agents/*.md (frontmatter first, no leading blank lines), python -m json.tool .kiro/settings/mcp.json > /dev/null, and python -m json.tool .kiro/hooks/*.json > /dev/null for each hook file.
  3. Open the project; the steering panel lists every rule file with its inclusion mode and no parse warnings, the skill picker lists every .kiro/skills/<name>/ folder, and the agent picker lists every .kiro/agents/<name>.md profile.
  4. Trigger a hook's trigger event (e.g. save a file for PostFileSave); the configured command runs with no schema warning.

Crush (crush)

AGENTS.md                          # canonical entry-point pointer body + inlined rules (written by sync, shared path)
.agents/skills/<name>/SKILL.md     # one folder per skill (shared tree with codex/amp/zed)
crush.json                         # when MCP or PreToolUse hook entries exist (merged with existing user config)
.crushignore                       # project-root ignore patterns

Charm Crush reads the root AGENTS.md natively and has no per-rule directory, so rule bodies inline into the shared entry-point. Skills emit into .agents/skills/, the first project path Crush scans; the render is byte-identical with codex/amp/zed so the shared tree dedupes. Set x-crush.user-invocable: true on a skill to also add it to Crush's command palette (ctrl+p). MCP servers merge into the mcp key of crush.json ({type: stdio, command, args, env}, {type: http, url, headers, oauth, oauth_client_id, oauth_client_secret, oauth_callback_port}, or {type: sse, url, headers, oauth, ...}; oauth fields optional, shipped in Crush v0.87.0). sse keeps its own type rather than collapsing into http: Crush's MCPType enum treats them as distinct values routed to different transports, and an SSE-only server does not speak the Streamable HTTP that a mislabelled http entry would connect with. A spec's remote type has no matching Crush value and defaults to http. Either transport also carries disabled, sessionless, enabled_tools, and disabled_tools, all four read from the vendor's published schema.json rather than the README, which is the only place the MCP property set appears closed. disabled ("Whether this MCP server is disabled") was dropped silently until #641, and that drop was worse than a warning: one spec synced to crush and trae printed a note for trae and nothing for crush, so the silence read as success. sessionless marks a server that sends no Mcp-Session-Id "so Crush skips the subscriptions/listen stream it would otherwise reject" (shipped in v0.91.2; leave it unset to let Crush auto-detect known cases such as GitHub MCP), and the two tool lists gate which of the server's tools reach the agent. Every field here is mapped explicitly rather than merged from a generic x-crush block: MCPConfig sets "additionalProperties": false, so a typo in a namespaced passthrough would produce a config Crush rejects outright rather than one it ignores. Skill frontmatter has no such constraint, which is why the shared skill renderer still takes the generic merge. User-managed keys (models, providers, lsp, options) survive every sync. Agents have no Crush surface and skip with a warning.

  • Hooks: merge into that same crush.json, under a hooks key alongside mcp, in one MergeJSONFile write (not two, so neither key's snapshot goes stale during sync's collision-detection pass). Crush's own hooks doc states "Crush currently supports just one hook, PreToolUse, with plans to support the full gamut" (re-verified 2026-09-10 against both that doc and the vendor's schema.json, whose $defs.HookConfig carries no per-event variant), so a hook spec targeting any other event surfaces a coverage note instead of a dead JSON entry Crush never reads. That one event answers to five spellings: "Event names are case insensitive and snake-caseable, so PreToolUse, pretooluse, PRETOOLUSE, pre_tool_use, and PRE_TOOL_USE all work" (verified 2026-09-11, #731). This adapter applies that rule when it decides whether a spec is a PreToolUse hook, and always writes the canonical PreToolUse key, so the output shape never depends on how the spec spelled it. A PreToolUse entry renders flat, one array item per hook ({"name": ..., "matcher": ..., "command": ..., "timeout": ...}), unlike the Claude-style {"matcher": ..., "hooks": [...]} grouping Claude Code, Codex, OpenHands, and Qoder use. command is the only required field; timeout is seconds, defaulting to 30 when unset. Crush's own tool names are lowercase (bash, edit, write, mcp_<server>_<tool>; its worked examples use ^bash$), so a Claude-style matcher (Bash, Edit, ...) parses as a valid regex and then matches nothing, the same trap OpenHands and Windsurf hit with their own tool vocabularies; that case surfaces a field no-op note. import crush reads hooks.PreToolUse back into hook specs: a named entry's name becomes both the spec's name: field and its filename, so a re-import lands at the same path.

crush.json is Crush's legacy format: the vendor's own docs call it deprecated and say "new configuration options will only be added to Bash-based config", the documented primary format now, crushrc (a Bash script Crush sources on startup). JSON still loads today (the vendor: "we plan to support it for the forseeable future") and this adapter still targets it; writing a crushrc emitter is a separate feature with its own design questions (shell-quoting header values, merge interaction) and is not done here. Two things to know: any future crush-only MCP field ships Bash-only and has no path through this adapter, and Crush's discovery order merges files rather than picking one, lower numbers taking precedence: ./.crushrc, then ./crushrc, then $XDG_CONFIG_HOME/crush/crushrc, with legacy crush.json / .crush.json merged in alongside those paths (project over global, crushrc over JSON in the same directory) and a startup warning logged whenever a directory holds both. A project that also hand-authors a crushrc gets that warning against our crush.json on every launch.

Ignore specs write project-root .crushignore with gitignore syntax, supported by Crush v0.94.1. import crush captures the file into an ignore spec. The shared hand-authored-file protection applies.

Config keys: outputs.crush.skills-dir (default .agents/skills), outputs.crush.mcp-file (default crush.json, also the hooks file), outputs.crush.ignore-file (default .crushignore).

Verify with the real CLI:

  1. Install: brew install charmbracelet/tap/crush (or see the README).
  2. Check the tree: ls AGENTS.md .agents/skills/, python -m json.tool crush.json > /dev/null.
  3. Launch crush; the context loads AGENTS.md, the skills list shows each .agents/skills/<name>/, each mcp.<name> connects, and a PreToolUse hook's matcher fires (or stays silent) as expected against a real tool call.

Trae (trae)

AGENTS.md                     # canonical entry-point pointer body (written by sync, shared path)
.trae/rules/<name>.md         # one per rule
.trae/agents/<name>.md        # one per agent (project subagent)
.trae/skills/<name>/SKILL.md  # one folder per skill
.trae/commands/<name>.md      # one per command
.trae/hooks.json              # when hook entries exist
.trae/.ignore                 # when ignore entries exist
.trae/mcp.json                # MCP server registry

ByteDance Trae reads persistent rules from .trae/rules/ and the root AGENTS.md natively, and project subagents from .trae/agents/.

  • Rules: every .trae/rules/*.md file carries description / globs / alwaysApply YAML frontmatter, the same three-field activation matrix Cursor's .mdc rules use. alwaysApply defaults to true; a true rule omits globs entirely; alwaysApply: false with no explicit globs falls back to the Claude spelling (paths, comma-joined). The Trae docs document all three keys but not what a file carrying none of them defaults to, so this adapter always emits them rather than leave the activation mode to guesswork (#607). Set x-trae.scene: git_message on a rule to mark it for AI-generated Git commit messages: the vendor states the field "is compatible with existing fields such as alwaysApply, description, and globs", so it merges onto the same block instead of gating behind it (#635).

  • Agents: one project subagent per agent at .trae/agents/<name>.md, the path Trae's subagents doc tables as {project_folder}/.trae/agents/{my_agent}.md. Frontmatter carries name and description (both required), plus optional model, tools, disallowedTools, and mcpServers; the body after the closing delimiter is the system prompt. Agents used to flatten into .trae/rules/agent-<name>.md, which reached the rules loader instead of the subagent loader and dropped every field rule frontmatter has no key for (target-audit 2026-08-27, #638); a managed copy at the old name is swept for every current agent. tools passes through with no translation table: Trae's vocabulary is Claude-style and covers agnostic-ai's set exactly (Bash, Edit, Glob, Grep, Read, Write, WebFetch, WebSearch, plus Skill, LSP, TodoWrite, and mcp__<server>__<tool>), comma-joined into the string spelling the vendor documents rather than a YAML list. model is the opposite case: Trae accepts "Only built-in models provided by TraeCode", a table of its own IDs (gpt-5.4, minimax-m3, ...) with no overlap with a cross-target model: value, so a bare generic model drops with a coverage note. Name one for Trae with model: {trae: <id>} or x-trae.model; disallowedTools and mcpServers reach the file through x-trae the same way. Subagents sit behind a switch: "Go to Settings > Beta > Subagents, ensure that the Enable Subagents Directory switch is toggled on." The vendor never states its default, so a project may need that switch flipped before the emitted files load.

  • Skills: one folder per skill under .trae/skills/<name>/SKILL.md, Trae's native skills layout. The SKILL.md frontmatter carries name + description; sibling assets (examples/, templates/, resources/) copy byte-for-byte alongside it. A flat file directly under .trae/rules/ never loads as a skill, so this is a folder, not a rule-form file.

  • Commands: one .md per command under .trae/commands/<name>.md, name + description frontmatter and the body as the prompt. This shape is vendor-confirmed rather than reverse-engineered: Trae's slash-commands doc tables the same two frontmatter fields, Name and Description, matching what this adapter already emitted from real .trae/commands/*.md files. Only name and description are documented, so nothing else emits. Nesting under .trae/commands/ is bounded at 3 levels, not merely organizational: the vendor's own example tree marks a file 3 levels deep as the deepest readable one and a file 4 levels deep as exceeding the limit, so this adapter still writes every command flat rather than risk crossing it.

  • MCP servers: merge into .trae/mcp.json under a root mcpServers map, confirmed by a direct fetch of Trae's own MCP doc. Stdio entries carry command (required) plus optional args / env; HTTP entries carry url (required) plus optional headers. Neither carries a type field: Trae tells the two apart by which of command or url is present, so this adapter never writes one. disabled has no documented per-server key either (only a project-level MCP toggle under Settings > MCP), so a spec's disabled: true is stripped with a coverage note instead of written dead. The vendor doc cautions that a stdio command must not contain spaces, or parsing fails.

  • Hooks: written to .trae/hooks.json, the project tier of Trae's hook configuration reference: "Project Hook | $PROJECT_FOLDER/.trae/hooks.json | Applies only to the current project or workspace" (target-audit 2026-09-11, #729). The file is an integer version envelope ("The default value is 1, and currently only 1 is supported") around a hooks map keyed by event, each event holding the same {matcher, hooks: [{type, command, timeout}]} groups Claude Code, Codex, OpenHands, and Qoder already get, so one hook spec feeds all five. Trae documents six events (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, Notification) and three fields per entry: type ("currently only command is supported"), command, and timeout (seconds, default 30). A spec's loop_limit also emits, Trae's own group-level field capping how often a Stop hook may block the agent from stopping; leave it unset for the vendor default of 5. The matcher vocabulary is the trap here. Trae's hook tool_name table is not its subagent tools table: it reads Read, Write, Edit, Glob, Grep, LS, RunCommand, WebSearch, WebFetch, AskUserQuestion, Skill, and mcp__<serverName>__<toolName>. The terminal tool is RunCommand, not Bash, and there is no TodoWrite, so a Claude-style matcher: Bash parses as a valid regex and then matches nothing; that case emits verbatim with a coverage note rather than a guessed rename, the same line OpenHands and Crush hold. matcher is read on PreToolUse, PostToolUse, and Notification only, and on Notification it selects a notification type (idle_prompt, permission_prompt, ...) rather than a tool. Project hooks are enabled through Settings > Hooks behind a security-consent panel ("read the warning message. After confirming there is no risk, click the Enable button"), the same shape as the project-level MCP toggle this adapter already emits past, so the file is inert until a user flips it.

    Trae reads Claude Code's hook file too: "Additionally, TraeCode supports reading hook configurations from Claude Code", with $PROJECT_FOLDER/.claude/settings.json and .claude/settings.local.json in its own Project Hook table, and "If both Claude Code hooks and TraeCode hooks are enabled at the same time, TraeCode will read all enabled hook configurations and execute them in combination." A repo syncing claude and trae together therefore runs every hook twice, but only after you turn it on: automate-actions-with-hooks says "Toggle the Import Hook configuration in CLAUDE switch on", and it is off by default, behind the same security-warning panel (#756).

  • Ignore: ignore specs emit as .trae/.ignore, the path Trae's own Settings > Indexing & Docs flow creates: "TraeCode automatically creates the .ignore file in the .trae/ folder and opens this file in the editor" (docs.trae.ai/ide/ignore-files, target-audit 2026-09-11, #728). The filename is a bare .ignore, scoped by the directory rather than a tool prefix. It supplements .gitignore, which Trae already honors by default, and governs codebase indexing plus #Workspace / #Folder context ("any ignored files or folders will not be included as context"). Unlike every other ignore target, it does not apply on save: "The .ignore file will take effect after re-indexing is complete", so a freshly synced pattern needs a Build under Settings > Indexing & Docs before it holds. Multiple specs concatenate. Override via outputs.trae.ignore-file.

Config keys: outputs.trae.rules-dir (default .trae/rules), outputs.trae.agents-dir (default .trae/agents), outputs.trae.skills-dir (default .trae/skills), outputs.trae.commands-dir (default .trae/commands), outputs.trae.hooks-file (default .trae/hooks.json), outputs.trae.ignore-file (default .trae/.ignore), outputs.trae.mcp-file (default .trae/mcp.json).

Verify with the real IDE:

  1. Install Trae from trae.ai.
  2. Check the tree: ls AGENTS.md .trae/rules/ .trae/agents/ .trae/skills/ .trae/commands/ .trae/mcp.json .trae/hooks.json .trae/.ignore, grep "Generated by agnostic-ai" .trae/rules/*.md, python -m json.tool .trae/mcp.json > /dev/null, python -m json.tool .trae/hooks.json > /dev/null.
  3. Open the project; the Rules panel lists every .trae/rules/*.md with no parse warnings. Each .trae/skills/<name>/ loads as a skill, each .trae/commands/<name>.md is invokable from chat, and Settings > MCP lists each .trae/mcp.json server (project-level MCP toggled on).
  4. Turn on Settings > Beta > Subagents > Enable Subagents Directory, then ask the Agent to delegate to one by name; each .trae/agents/<name>.md is routable and the frontmatter parses with no BOM or delimiter error.
  5. Enable the project hook under Settings > Hooks (accept the security panel); Configured Hooks lists the file, and triggering the matched tool runs the command. Edit .trae/.ignore, then Build under Settings > Indexing & Docs; the ignored paths drop out of #Workspace context.

Qoder (qoder)

AGENTS.md                        # canonical entry-point pointer body (written by sync, shared path)
.qoder/rules/<name>.md           # one per rule (native, one file per rule)
.qoder/agents/<name>.md          # one per agent (native, one file per agent)
.qoder/skills/<name>/SKILL.md    # one folder per skill, plus any bundled assets
.qoder/commands/<name>.md        # one per command
.qoder/settings.json             # when MCP, hook, or settings entries exist (merged; unrelated keys preserved)

Alibaba Qoder reads project rules from .qoder/rules/ natively, one Markdown file per rule, and also reads the root AGENTS.md. The per-rule files take precedence over AGENTS.md, so rules emit there rather than inlining into the shared pointer. Skills emit into their own native folder tree at .qoder/skills/<name>/SKILL.md (docs.qoder.com/extensions/skills, target-audit 2026-08-08, #558): "Each Skill contains a SKILL.md file", at project scope .qoder/skills/{skill-name}/SKILL.md (the vendor doc also lists a user-level ~/.qoder/skills/{skill-name}/SKILL.md tier this adapter has no reach into). That doc does not list .agents/skills/ as a compatible path, unlike Kilo Code, Augment, and OpenHands, so this is Qoder's own tree rather than a dedupe target for the shared one. import qoder reads rules, agents, skill folders, commands, and portable settings fields; it does not yet read hooks or MCP servers back out of .qoder/settings.json.

  • Agents: Qoder Subagents reads .qoder/agents/<name>.md, one file per agent. name and description are required frontmatter; model, tools, color, skills, and mcpServers are optional. color (one of eight named values, e.g. red, cyan) is documented on the CLI field reference rather than the smaller extensions page, which defers to the CLI page as "the complete guide" for the identical path; it is a shared portable field Augment and Kilo Code already promote the same way. tools renders as a comma-separated string (tools: Read, Grep, Bash), the only form the vendor doc shows, not a YAML list; import qoder splits it back into agnostic-ai's generic list form so the spec stays usable by every other target. Qoder's built-in tool vocabulary is Claude-style (Bash, Edit, Write, Glob, Grep, Read, WebFetch, WebSearch), which is what makes passing agnostic-ai's generic tools list straight through safe here, unlike Kilo Code and Augment, whose own vocabularies differ and which drop the field with a coverage note instead. skills and mcpServers have no agnostic-ai-native shape and pass through whatever the spec declares.

  • Skills: Qoder Skills reads a folder per skill at .qoder/skills/<name>/SKILL.md. Frontmatter is plain name + description, the only keys the vendor doc shows; bundled sibling files (scripts, references, templates) copy byte-for-byte alongside SKILL.md, the same folder-layout render every other Agent Skills target here uses.

  • Commands: one Markdown file per command spec at .qoder/commands/<name>.md. docs.qoder.com/cli/commands tables .qoder/commands/<command_name>.md as the project-level location, "Recommended (team sharing)", and docs.qoder.com/user-guide/commands corroborates the same path for the IDE ("Project Commands", "Only effective in the current project root directory and its subdirectories"), so this is not a CLI-only surface. The CLI page's field table documents exactly two frontmatter keys: description (Required: Yes) and name (Required: No, "serves only as the display name in the TUI; the invocation name is always derived from the file path"). Since the filename already drives invocation, this adapter never writes name and always writes description, falling back to the command's name when the spec has none. One precedence quirk: the CLI page states that when a command with the same name exists at both the project and User levels, "the User-Level command takes precedence and overrides the project-level command with the same name" — backwards from the read-order most targets document — and this adapter has no reach into the user-level tier (~/.qoder/commands/) to warn about a same-name collision. The IDE page describes different behavior for the same case (both entries stay listed, distinguished by "a scope indicator", rather than one overriding the other), so a project-level command this adapter writes can still be shadowed depending on which Qoder product reads it.

  • MCP: merges into .qoder/settings.json under the standard mcpServers map (stdio: command/args/env/cwd, no type; remote: type + url/headers). Every entry also accepts the nine fields on the vendor's "Common Optional Fields" table: timeout (milliseconds), description, trust ("Trusts the server, skipping confirmation when its tools are called"), includeTools, excludeTools, alwaysAllow, disabled ("Disables the server (keeps the configuration without deleting it)"), and an oauth object passed through as declared, since the vendor's own field list for it is open-ended. Unrelated keys in that file (mcp.enableAllProjectMcpServers, permissions, custom models) survive every sync; only mcpServers is overwritten. That holds for a JSONC file too: the vendor documents this path as "JSON format (supporting // comments)" (docs.qoder.com/cli/settings), so sync strips comments and trailing commas before reading. Keys survive, comments do not, and the sync that drops them prints a one-line warning (target-audit 2026-09-11, #725).

  • Settings: the shared default model maps to .qoder/settings.json as model.name; permissions.allow, permissions.deny, and permissions.ask map directly. The write merges with hooks, MCP servers, and unrelated native keys. Import restores those portable fields to settings/imported.yaml.

    This adapter wrote the project-root .mcp.json until #641, byte-for-byte the file Claude Code writes there, and the two deduped into one write. That only held while their emitted bytes stayed identical, and they no longer can: Qoder documents nine per-server fields Claude Code does not, Claude Code documents four Qoder does not (headersHelper, alwaysLoad, and its own oauth and timeout shapes), and a spec using any of them would make two adapters write different bytes to one path. That is a hard error in the collision check, on a pair of targets both in the default set. <project>/.qoder/settings.json → mcpServers is the vendor's other documented project-level location and Claude Code never reads it, so moving there removes the shared path instead of arbitrating it. One migration step: a .mcp.json left by an older agnostic-ai still loads, and Qoder's precedence order puts it ahead of .qoder/settings.json for a same-named server. Sync cannot sweep it (a JSON file carries no provenance header, and the file may belong to Claude Code), so delete it by hand in a Qoder-only project. See disabled support by target.

  • Hooks: merge into that same .qoder/settings.json, under a hooks key alongside mcpServers (docs.qoder.com/cli/hooks, "Configuration Format"): {"hooks": {"<Event>": [{"matcher": ..., "hooks": [{"type": "command", "command": ..., ...}]}]}}, the same nested shape Claude Code, Codex, and OpenHands use, so this adapter's renderer is the shared claudehooks wire structs rather than a fourth hand-rolled copy. 27 events, PascalCase: SessionStart, SessionEnd, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied, Stop, StopFailure, SubagentStart, SubagentStop, PreCompact, PostCompact, Notification, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, Elicitation, ElicitationResult, TaskCreated, TaskCompleted, TeammateIdle, Setup (up from 6 at an earlier audit, then 23 at #629; the authoritative list is docs.qoder.com/cli/hooks-reference, re-counted 2026-09-12, #737). Every one of them emits, since event: passes through verbatim, and all 27 sort in the vendor's own order ahead of any event it does not list. The last four sorted behind an unlisted event until #744, so a project already using one of those names writes different bytes after that fix. PreToolUse/PostToolUse's matcher documents "Tool name (e.g. Bash, Write, Edit, Read, Glob, Grep; MCP tool names like mcp__server__tool)", Claude Code's own vocabulary, so a Claude-authored matcher reaches it unchanged, unlike OpenHands and Windsurf whose own tool names diverge. Per entry: command, type (always command), optional args, timeout (seconds, vendor default 600), statusMessage, async, asyncRewake, shell (bash or powershell), and if (a permission-rule filter, e.g. Bash(git *)). All nine are documented with the same semantics claudehooks.CommandEntry already models for Claude Code. args switches the entry to exec form: command becomes the path of a single executable and each args element is one literal argv entry, launched directly with no shell, so a path or argument holding a space, apostrophe, $, or backtick reaches the binary intact (#746). Qoder ignores shell once args is set; a spec that sets both still writes both, and sync prints a note saying the shell never runs. Qoder additionally documents env, rewakeMessage, rewakeSummary, and three more hook entry types (http, prompt, agent); none has a field on the shared hook spec, so they are unreachable from a generic spec today.

    Merging hooks and mcpServers happens in one write, not two: MergeJSONFile re-reads .qoder/settings.json from disk on every call, and two separate calls in the same sync would each see the file before the other's write landed during sync's collision-detection pass, which reads as two targets disagreeing on one file's content when only Qoder writes it.

Config keys: outputs.qoder.rules-dir (default .qoder/rules), outputs.qoder.agents-dir (default .qoder/agents), outputs.qoder.skills-dir (default .qoder/skills), outputs.qoder.commands-dir (default .qoder/commands), outputs.qoder.mcp-file (default .qoder/settings.json, also the hooks path since the two share one file).

Verify with the real IDE:

  1. Install Qoder from qoder.com.
  2. Check the tree: ls AGENTS.md .qoder/rules/ .qoder/agents/ .qoder/skills/ .qoder/commands/ .qoder/settings.json, grep "Generated by agnostic-ai" .qoder/rules/*.md .qoder/agents/*.md .qoder/skills/*/SKILL.md .qoder/commands/*.md for the provenance header, python -m json.tool .qoder/settings.json > /dev/null.
  3. Open the project; the rules panel lists every .qoder/rules/*.md with no parse warnings, the agent picker lists every .qoder/agents/*.md, each .qoder/skills/<name>/ folder loads as a skill, each .qoder/commands/<name>.md runs from /, the MCP picker shows each mcpServers.<name> from .qoder/settings.json connected (project-level servers need approval on first use), and a configured hook fires on its event (e.g. a PreToolUse hook prints or blocks before the matching tool runs).

OpenHands (openhands)

AGENTS.md                          # canonical entry-point pointer body + inlined always-on rules (written by sync, shared path)
.agents/agents/<name>.md           # one project agent (shared with Goose)
.agents/skills/<name>/SKILL.md     # one folder per skill, plus one per path-triggered rule (shared tree with codex/amp/zed/crush)
config.toml                        # when MCP entries exist
.openhands/hooks.json              # when hook entries exist
.openhands/setup.sh                # when an environment spec sets `install`

All Hands OpenHands reads the root AGENTS.md natively and loads skills from .agents/skills/, the same cross-tool tree codex, amp, zed, and crush emit. The render is byte-identical, so the shared tree dedupes into one write. Local conversations also auto-register top-level project agents from .agents/agents/<name>.md, the vendor's primary project path. The shared Goose/OpenHands renderer writes name, description, optional free-form model, and the prompt body byte-identically. A generic tools list is omitted with a coverage note because OpenHands uses its own file_editor and terminal vocabulary. Set x-openhands.tools with native names and move outputs.openhands.agents-dir to the documented secondary .openhands/agents path when an OpenHands profile must differ from Goose's shared file. An always-on rule (no globs/paths value and no source-layout or frontmatter scope) inlines into the shared AGENTS.md ## Rules block, same as every other AGENTS.md-family target. A rule that carries one of those instead emits as a path-triggered rule: .agents/skills/<name>/SKILL.md with a paths: frontmatter list, OpenHands' own deterministic per-file mechanism: "guaranteed to load for the files they scope, with no reliance on the model choosing them", and "zero baseline cost" to the context window until a matching file is touched. The vendor documents two locations for this, a flat .md file and a folder; this adapter writes the folder form, so a path-triggered rule shares outputs.openhands.skills-dir with regular skills instead of needing a separate rules-dir key. A catch-all globs/paths value stays inlined because it scopes to every file.

  • Hooks: land in .openhands/hooks.json (override via outputs.openhands.hooks-file), the file OpenHands reads "per-repository" and honors "across Cloud, CLI, and local GUI setups" (docs.openhands.dev/openhands/usage/customization/hooks). Six events: PreToolUse, PostToolUse, UserPromptSubmit, Stop, SessionStart, SessionEnd. OpenHands' own layout keys them in snake_case with no wrapper, and documents the Claude form as equally valid: "PascalCase event keys (e.g., PreToolUse) and the {\"hooks\": {...}} wrapper are both supported, so you can share hook scripts between the two tools". This adapter emits that shared form, so one renderer serves both targets and a hook spec renders comparably across them. Per entry: command, type (always command), optional timeout (seconds, vendor default 60) and async. A matcher only applies to the two ToolUse events. OpenHands names its own tools, and the vendor flags the trap itself ("tool names (e.g., terminal vs Bash)"), so a matcher carried over from a Claude spec parses and then matches nothing; that case surfaces a coverage note rather than a guessed rename, since only terminal is documented alongside * and regex, leaving no vendor-stated counterpart for the rest.
  • MCP: merges into ./config.toml under a [mcp] table with three arrays instead of a type field: stdio_servers ([[mcp.stdio_servers]] tables carrying name/command/args/env) and sse_servers / shttp_servers (shttp_servers is OpenHands' streamable-HTTP transport, the cross-tool spec's type: http). Each remote element is a bare URL string, OpenHands' simplest documented form, or the vendor's { url, api_key, timeout } object once the entry sets a top-level api_key and/or (shttp only) timeout field; TOML allows mixing both forms in one array, as OpenHands' own example does. timeout (int, 1-3600 seconds, default 60, vendor example timeout = 1800) is documented for the SHTTP tab only; an sse entry that sets it gets a coverage note instead of a silent no-op. The spec's generic headers field has no equivalent here (OpenHands documents only the single api_key credential, never a header map) and surfaces a coverage note instead of reaching the target with the credential silently missing. A transport OpenHands documents no array for (e.g. type: ws) reaches neither array and surfaces a coverage note instead of guessing one. The project-tier config.toml is managed (its [mcp] table is overwritten each sync); keep unmanaged OpenHands config elsewhere.
  • Environments: an environment spec's install field writes .openhands/setup.sh, the vendor's documented repository bootstrap script ("You can add a .openhands/setup.sh file, which will run every time OpenHands begins working with your repository... an ideal location for installing dependencies, setting environment variables, and performing other setup tasks"). The script gets a #!/bin/bash shebang, then the provenance header, then install verbatim as the body. OpenHands chmods the script itself before running it (chmod +x {script} && source {script}), so this file needs no executable bit set on write. terminals (Cursor's long-running dev processes) has no equivalent here, since the script runs once, synchronously, at repo start; it surfaces a coverage note instead of being silently dropped. Multiple environment specs merge the same way Cursor's do: last spec's install wins.

Config keys: outputs.openhands.agents-dir (default .agents/agents), outputs.openhands.skills-dir (default .agents/skills), outputs.openhands.mcp-file (default config.toml), outputs.openhands.hooks-file (default .openhands/hooks.json), outputs.openhands.setup-file (default .openhands/setup.sh).

Verify with the real CLI:

  1. Install OpenHands (docs).
  2. Check the tree: ls AGENTS.md .agents/agents/ .agents/skills/ config.toml .openhands/setup.sh, test -f .agents/agents/*.md, test -f .agents/skills/*/SKILL.md, head -1 config.toml for the provenance comment.
  3. Launch OpenHands; the context loads AGENTS.md, each .agents/skills/<name>/ appears as a skill, a path-triggered rule's .agents/skills/<name>/SKILL.md injects only when a file matching its paths: globs is touched, each [mcp] server from config.toml connects, .openhands/setup.sh runs at session start, and each .openhands/hooks.json entry fires on its event (check OPENHANDS_EVENT_TYPE in the hook's environment to confirm which).

Factory (factory)

AGENTS.md                          # canonical entry-point pointer body + inlined rules (written by sync, shared path)
.factory/droids/<name>.md          # one custom-droid profile per agent
.agents/skills/<name>/SKILL.md     # one folder per skill (shared tree with codex/amp/zed/crush)
.factory/commands/<name>.md        # one Markdown slash command per command spec
.factory/hooks.json                # when hook entries exist
.factory/mcp.json                  # when MCP entries exist

Factory Droid reads the root AGENTS.md natively and loads custom droids from .factory/droids/. Each agent emits as one <name>.md profile with name, description, and optional model / tools frontmatter (tools translates onto Droid CLI's own tool IDs, see below); arbitrary x-factory keys pass through. An agent spec with an empty body skips instead of writing a frontmatter-only file: Droid CLI's own schema says the body "is the system prompt and cannot be empty", so the skip surfaces as a coverage note rather than landing a file the tool rejects. Factory has no per-rule directory, so rule bodies inline into the shared AGENTS.md ## Rules block. Skills load from .agents/skills/, the same cross-tool tree codex, amp, zed, and crush emit; the render is byte-identical, so the shared tree dedupes into one write. docs.factory.ai/harness/skills also documents a second compatibility path, .agent/skills/**/SKILL.md, which this adapter does not additionally write.

  • Tools: a spec's generic tools list is translated onto Droid CLI's own tool IDs, not passed through. The vendor's table is the complete set of valid IDs (Read, LS, Grep, Glob, Create, Edit, ApplyPatch, Execute, WebSearch, FetchUrl) and "Unknown IDs cause a validation error", so one unknown name costs the author the whole droid, not just that tool. Bash becomes Execute, Write becomes Create, and WebFetch becomes FetchUrl, the same three renames Factory's own Claude Code importer performs; the rest of agnostic-ai's vocabulary is already valid and carries over. Three load-time rules shape the rest: TodoWrite and Skill are "always included for every droid ... You do not list them", so they drop without a note since the droid keeps them anyway; ExitSpecMode and GenerateDroid "cannot be enabled by a custom droid", so they drop like any unknown name; and the literal tools: all is rejected by Droid CLI, so a scalar value never reaches the frontmatter and the omitted key means "allow every tool", which is Factory's own way to spell it. Any other name drops with a coverage note rather than being written unconfirmed, while the names that do translate still emit. Set x-factory.tools to bypass the table with Factory's own vocabulary directly, the only way to reach a category name (read-only, edit, execute, web, mcp) or a registered MCP tool ID; it wins outright over the translated form. See tools support by target.
  • Commands: written to .factory/commands/<name>.md with description and argument-hint frontmatter. The body stays Markdown and $ARGUMENTS is preserved. Factory recommends Skills for new reusable workflows but continues to load this command surface.
  • Hooks: written to .factory/hooks.json: "Project | .factory/hooks.json | Commit to share with teammates." (docs.factory.ai/harness/hooks, #629). The file is managed: sync overwrites it whole, the same as .factory/mcp.json below. A hand edit is lost on the next sync; change the hook spec instead (target-audit 2026-09-11, #745). Nine events: PreToolUse, PostToolUse, UserPromptSubmit, Notification, Stop, SubagentStop, PreCompact, SessionStart, SessionEnd. Unlike Claude Code, Codex, Gemini, and Qoder's shared {"hooks": {...}} wrapper, "Standalone hooks.json files are keyed directly by event name", so this adapter's document renders {"<Event>": [{matcher, hooks: [...]}]} at the top level with no wrapper, the same divergence Windsurf/Devin CLI's .devin/hooks.v1.json carries. Per entry: type (always "command", the vendor's field table documents no other value), command, and optional timeout (seconds, vendor default 60 when absent, not milliseconds). matcher is a regex; the vendor's own "Common tool matchers" (Execute, Read, Edit, Create, ApplyPatch, LS, Glob, Grep, Task, FetchUrl, WebSearch) already match Claude's own spelling except three: Bash, Write, and WebFetch, the same three names tools.go's translation table renames to Execute, Create, and FetchUrl for the tools frontmatter field. A matcher carried over from a Claude spec using one of those three still emits verbatim but folds into one coverage note, since it parses as a valid regex and then matches nothing. The vendor's other matcher-group field, commandRegex ("Additional regex filter for Execute commands"), has no counterpart on agnostic-ai's generic hook spec and is not emitted.
  • MCP: written to .factory/mcp.json under the standard mcpServers map, the same shape Claude Code and Cursor use (stdio: command/args/env, no type; remote: type + url/headers). The file is managed: sync overwrites it whole, the same as Claude Code, Cursor, Junie, and Kiro. That is worth knowing here, because Factory's own docs send you to hand-edit it: "Project servers cannot be removed with droid mcp remove or the /mcp manager. To remove them, edit .factory/mcp.json directly" (docs.factory.ai/harness/mcp). Such an edit is lost on the next sync; remove the MCP spec instead (target-audit 2026-09-11, #737). Factory's schema documents a working per-server disabled boolean (default false), unlike Claude Code, Cursor, and Copilot, so agnostic-ai passes a spec's disabled: true straight through instead of stripping it. Both transports preserve disabledTools, timeout, and connectTimeout (milliseconds), including explicit zero timeouts. Remote HTTP/SSE servers also accept oauth: false or an OAuth object with scopes, resource, authorizationServerIssuer, clientId, clientSecret, clientMetadataUrl, tokenEndpointAuthMethod, and callbackPort. x-factory overrides each top-level option. These fields stay scoped to Factory. See disabled support by target. A type: ws spec emits no server and raises a coverage note because Factory documents only stdio, HTTP, and SSE.

Config keys: outputs.factory.agents-dir (default .factory/droids), outputs.factory.skills-dir (default .agents/skills), outputs.factory.commands-dir (default .factory/commands), outputs.factory.hooks-file (default .factory/hooks.json), outputs.factory.mcp-file (default .factory/mcp.json).

Verify with the real CLI:

  1. Install the Factory CLI (subagents docs).
  2. Check the tree: ls AGENTS.md .factory/droids/ .agents/skills/ .factory/hooks.json .factory/mcp.json, grep "Generated by agnostic-ai" .factory/droids/*.md for the provenance header (it sits after the frontmatter), python -m json.tool .factory/hooks.json > /dev/null when hook specs exist, python -m json.tool .factory/mcp.json > /dev/null.
  3. Launch droid; each .factory/droids/<name>.md appears in the droid picker, each .agents/skills/<name>/ loads as a skill, each mcpServers.<name> from .factory/mcp.json connects, and /hooks shows each entry in .factory/hooks.json under the Project tab.

Kilo (kilo)

AGENTS.md                          # canonical entry-point pointer body + inlined rules (written by sync, shared path)
.kilo/rules/<name>.md              # one per rule
.kilo/agents/<name>.md             # one per agent
.agents/skills/<name>/SKILL.md     # one folder per skill, plus any bundled assets (shared cross-tool tree)
.kilo/commands/<name>.md           # one per command
kilo.jsonc                         # instructions array (one entry per rule) and/or mcp map (merged with existing user config)
.kilocodeignore                    # compatibility input for Kilo's permission migrator

Kilo Code reads the root AGENTS.md natively and loads agents from .kilo/agents/, one <name>.md per agent; Kilo Code takes the agent's name from the filename, so name: is never written. Frontmatter carries description (falls back to the spec name) plus color, mode, and model when the spec sets them: color is the same generic top-level key augment and qoder also promote, written through without per-target validation (the three targets document different value spaces, see color support by target), and mode shares OpenCode's primary/subagent/all vocabulary under the identical name. Kilo Code's full agent Configuration Options table also documents disable, hidden, steps, temperature, and top_p; none has a confirmed counterpart on another registered target, and temperature/top_p are provider-scaled tuning knobs besides, so all five stay reachable only through x-kilo (e.g. x-kilo: {temperature: 0.1, steps: 15}) rather than a generic top-level key (target-audit 2026-08-08, #562). There is no tools: key: Kilo Code's full agent option table has no such field, so a spec's tools allowlist would silently do nothing; an agent with tools set surfaces a coverage note instead, and per-tool restriction goes through Kilo Code's native permission map via x-kilo: {permission: {...}}. Skills emit into the shared .agents/skills/<name>/SKILL.md tree (target-audit 2026-08-01): Kilo Code documents its own .kilo/skills/ path, but also lists .agents/skills/ as a compatibility directory "loaded by default", and that is the same tree codex, amp, zed, crush, openhands, windsurf, and augment already write byte-identically, so pointing here dedupes instead of adding a second on-disk copy.

Unscoped rules emit as one file per rule under .kilo/rules/, each one also listed by its own path in kilo.jsonc's instructions array (target-audit 2026-08-01): "Each entry points to a file path or glob pattern", and this adapter lists explicit paths rather than a .kilo/rules/*.md glob for ordinary rules. Scoped rules use nested AGENTS.md and are omitted from this unconditional list. Kilo Code's own precedence order is agent prompt > project instructions > AGENTS.md > global, so the instructions entry outranks the shared AGENTS.md block below it; AGENTS.md is always loaded when present regardless, so this adapter keeps inlining full rule bodies there too as a fallback, rather than treating instructions as a replacement. The legacy .kilocode/rules/ tree (the pre-rename Kilo Code branding) is separate and still auto-included for backward compatibility, but this adapter never emits it.

Commands emit as one Markdown file per command spec at .kilo/commands/<name>.md, the new Kilo Code extension's slash-command path: "Workflows are Markdown files stored as slash commands in .kilo/commands/" (packages/kilo-docs/pages/customize/workflows.md, mirrored on GitHub since kilo.ai's rendered docs defeat fetching). Kilo Code takes the command name from the filename, so name is never written. Frontmatter carries description, agent, model, variant, and subtask when set, a field list near-identical to OpenCode's own command frontmatter; variant (a reasoning-effort override, e.g. low or high) is the one extra key this vendor documents (#630).

One name is taken. Kilo v7.6.0 made goal reserved: "A custom command or an MCP prompt named goal is reserved. Kilo rejects it and reports an error; rename it" (code-with-ai/agents/goals, verified 2026-09-11). A command spec called goal still emits, because dropping it would lose the spec in silence, and surfaces a coverage note telling you to rename it (#736). The same sentence also reserves an MCP prompt named goal. An MCP spec names a server, not a prompt, and prompt names come from the server itself at runtime, so nothing agnostic-ai writes can collide there.

MCP servers merge into the mcp map of kilo.jsonc: stdio combines command+args into one command array and sets type: "local", using environment for env vars; remote sets type: "remote" and uses url/headers. A spec's disabled: true writes "enabled": false, the key Kilo Code's own documented MCP example carries; an enabled server gets no key at all. Both transports preserve timeout in milliseconds, including zero. Remote servers accept oauth: false to disable automatic OAuth; x-kilo can override both options. instructions and mcp merge into kilo.jsonc together; user-managed keys there survive every sync. That holds for a JSONC file too, which matters most here because the vendor documents comments on this exact file: "Disable a rule temporarily: Comment out the line in kilo.jsonc (JSONC supports // comments)" (kilo.ai/docs/customize/custom-rules), and the page's own worked example carries a comment plus two trailing commas. sync strips both before reading. Keys survive, comments do not, and the sync that drops them prints a one-line warning (target-audit 2026-09-11, #725). Hooks have no Kilo surface yet and skip with a warning.

A settings spec's default model merges into the top level of kilo.jsonc alongside instructions and mcp. import kilo restores that portable field to settings/imported.yaml and continues to import .kilocodeignore.

Kilo Code also reads a second project-tier file, .kilo/kilo.jsonc, which this adapter does not write. The vendor's documented 8-level config precedence places .kilo/kilo.jsonc above the root kilo.jsonc, but describes it as higher levels overriding lower ones, a merge rather than an exclusive first-match read: any key the root file sets and .kilo/kilo.jsonc does not still reaches Kilo Code. A hand-authored .kilo/kilo.jsonc that redeclares mcp or instructions itself would shadow this adapter's output for those two keys specifically (target-audit 2026-08-27, #644).

Ignore specs write project-root .kilocodeignore. Kilo's compatibility migrator converts its patterns into read/edit permission denials. This adapter writes the native input file and does not translate patterns into permission maps. import kilo imports this ignore file and the portable default model; other Kilo configuration is not imported. The shared hand-authored-file protection applies.

Config keys: outputs.kilo.rules-dir (default .kilo/rules), outputs.kilo.agents-dir (default .kilo/agents), outputs.kilo.skills-dir (default .agents/skills), outputs.kilo.commands-dir (default .kilo/commands), outputs.kilo.mcp-file (default kilo.jsonc), outputs.kilo.ignore-file (default .kilocodeignore).

Verify with the real IDE:

  1. Install Kilo Code (docs).
  2. Check the tree: ls AGENTS.md .kilo/rules/ .kilo/agents/ .agents/skills/ .kilo/commands/ kilo.jsonc, grep "Generated by agnostic-ai" .kilo/rules/*.md .kilo/agents/*.md .kilo/commands/*.md for the provenance header.
  3. Open the project; each .kilo/rules/<name>.md listed in kilo.jsonc's instructions array appears in the loaded-rules list, each .kilo/agents/<name>.md appears in the agent picker, each .agents/skills/<name>/ folder loads as a skill, each .kilo/commands/<name>.md runs as /<name>, and each mcp.<name> from kilo.jsonc connects, with a disabled spec showing as disabled.

Jules (jules)

AGENTS.md                     # canonical entry-point pointer body + inlined rules (written by sync, shared path)

Google Jules is a cloud agent. It reads the root AGENTS.md and has no project-local surface of its own, so it contributes nothing but the shared pointer body and the inlined ## Rules block. Enabling it adds no unique output, which is why it stays opt-in (see Selecting targets). Agents, skills, hooks, and MCP skip with a warning.

Config keys: none.

Verify with the real agent:

  1. Sign in to Jules (docs).
  2. Check the tree: ls AGENTS.md.
  3. Point Jules at the repo; it reads AGENTS.md as project context.

Goose (goose)

AGENTS.md                          # canonical entry-point pointer body + inlined rules (written by sync, shared path)
.agents/agents/<name>.md           # one project agent (shared with OpenHands)
.agents/skills/<name>/SKILL.md     # one folder per skill (shared tree with codex/amp/zed/crush)
.agents/plugins/agnostic-ai/plugin.json
.agents/plugins/agnostic-ai/hooks/hooks.json
.goosehints                        # opt-in concatenated rules, only when rules-file is set
.agents/REVIEW.md                  # root review instructions
<scope>/.agents/REVIEW.md          # directory-specific review instructions

Block Goose reads both the root AGENTS.md and a .goosehints file. By default rule bodies inline into the shared AGENTS.md ## Rules block, so Goose needs no extra file for rules. Set outputs.goose.rules-file: .goosehints to also write a concatenated .goosehints document. Project agents load from .agents/agents/<name>.md with name, description, optional free-form model, and the prompt body. OpenHands reads the same primary path and fields, so both adapters use one byte-identical renderer and sync dedupes their writes. A generic tools list is omitted with a coverage note because Goose's documented project-agent fields do not include it. Target-specific x-goose fields remain available; set a different outputs.goose.agents-dir if they make the file differ from another target sharing the default path. Skills load from .agents/skills/, the recommended standard in Goose's own docs, ahead of a legacy .goose/skills/, .claude/skills/, and others it also discovers; the render is byte-identical to codex, amp, zed, and crush's, so the shared tree dedupes into one write. Goose stays opt-in regardless (see Selecting targets). MCP specs still skip with a warning.

Hooks emit as a complete Open Plugins package. The manifest lives at .agents/plugins/agnostic-ai/plugin.json, and the wrapped hook map at hooks/hooks.json. All 12 documented events pass through, matcher is a regular expression, timeout is seconds, and x-goose.on_failure accepts allow or block for PreToolUse failure policy.

Goose discovers additional context files (any of CONTEXT_FILE_NAMES, default AGENTS.md and .goosehints) as it reads or modifies files in nested subdirectories, not just at the working directory and repository root. Scoped rules use nested AGENTS.md by default. With the rules-file opt-in set, a rule carrying a source-layout or frontmatter scope (e.g. backend/) routes into a sibling backend/.goosehints instead of flattening into the root document; rules sharing a scope concatenate into that scope's one file, the same "one file per scope" shape the root document already used (#608).

goose review reads .agents/REVIEW.md and <scope>/.agents/REVIEW.md from directories containing changed files and their ancestors, so root and scoped guidance compose. Same-scope bodies concatenate, and routing frontmatter is omitted because the loader reads plain text. The v1.50.0 CLI reference documents this surface. Agent-shaped check files remain outside this adapter.

Config keys: outputs.goose.agents-dir (default .agents/agents), outputs.goose.rules-file (unset; opt-in, writes a concatenated .goosehints document), outputs.goose.skills-dir (default .agents/skills), outputs.goose.hooks-file (default .agents/plugins/agnostic-ai/hooks/hooks.json; overrides must keep the <plugin>/hooks/hooks.json suffix), outputs.goose.review-file (default .agents/REVIEW.md, relative to each scope).

Verify with the real CLI:

  1. Install Goose (docs).
  2. Check the tree: ls AGENTS.md .agents/agents/ .agents/skills/, plus .goosehints when outputs.goose.rules-file is set.
  3. Launch goose; it reads AGENTS.md (and .goosehints when present) as context, each .agents/agents/<name>.md appears as a project agent, and each .agents/skills/<name>/ appears as a skill.

Augment (augment)

AGENTS.md                     # canonical entry-point pointer body + inlined rules (written by sync, shared path)
.augment/
├── rules/<name>.md           # one per rule
├── agents/<name>.md          # one per agent
├── commands/<name>.md        # one per command; nested source scope becomes a namespace
└── settings.json             # mcpServers + hooks, merged; only present with MCP or hook specs
.agents/skills/<name>/SKILL.md  # one folder per skill (shared cross-tool tree)
.augmentignore                # workspace indexing exclusions
.augment-guidelines           # opt-in legacy concatenated rules, only when rules-file is set

Augment Code reads the root AGENTS.md, with rule bodies inlined into the shared ## Rules block, and also loads rules natively from .augment/rules/. Each rule frontmatter carries type: agent_requested (with a description, falling back to the rule name) when the spec sets alwaysApply: false; the vendor default always_apply stays implicit. There is no name key for rules. Agents load from .augment/agents/, one <name>.md per agent, with name (required), description (falls back to the spec name), color, and model when set. tools and disabled_tools are real Augment fields but only in Augment's own vocabulary (view, codebase-retrieval, str-replace-editor, ...), not Claude-style names, so agnostic-ai's generic tools field never reaches them: an agent that sets it surfaces a coverage note, and x-augment: {tools: [...]} / x-augment: {disabled_tools: [...]} is the way to reach Augment's real per-tool access control. Skills emit into the shared .agents/skills/ tree, which Augment also scans directly alongside .claude/skills/ and .augment/skills/. Set outputs.augment.rules-file: .augment-guidelines to additionally write the legacy concatenated document; the vendor's own precedence order truncates it first under budget pressure, which is why it stays opt-in rather than the default rules surface. MCP servers merge into <workspace>/.augment/settings.json under mcpServers in the standard shape (stdio: command/args/env, no type; remote: type: http|sse plus url/headers): "Project settings (shared, per-project) ... Best for team-shared project configuration, such as shared MCP servers" (docs.augmentcode.com/cli/config). This is Auggie CLI's own settings hierarchy, not the VS Code / JetBrains extension's Settings Panel, and the file also holds shell, startupScript, theme, plugin keys, and tool permissions, so the write merges in only the mcpServers key and leaves everything else untouched. That holds for a JSONC file too: "The files support JSON with Comments (JSONC), allowing comments and trailing commas for better documentation" (docs.augmentcode.com/cli/config), so sync strips both before reading. Keys survive, comments do not, and the sync that drops them prints a one-line warning (target-audit 2026-09-11, #725). No per-server disabled key is documented, so a spec's disabled: true is stripped with a coverage note rather than written as a key Auggie would ignore. Hooks merge into that same .augment/settings.json, under a hooks key, in the same single write as mcpServers rather than a second MergeJSONFile call (a second call would re-read the pre-write file during sync's collision-capture pass and produce a second, divergent snapshot for the one path, tripping the collision check against this adapter's own two writes). Five events: PreToolUse, PostToolUse, Stop, SessionStart, SessionEnd (docs.augmentcode.com/cli/hooks). timeout reaches the file in milliseconds; the shared hook spec's own timeout field is seconds, so this adapter multiplies by 1000 (vendor default 60000 when absent). command must be a path ending in .sh, .ps1, .cmd, or .bat: "Path to the script to execute (must use a supported script extension: .ps1, .cmd, .bat, or .sh)". Unlike Claude Code, Codex, and Qoder, Augment never runs an inline shell string; a command missing one of the four extensions still emits verbatim (no guessed rename) but surfaces a coverage note. matcher is optional even on PreToolUse/PostToolUse (vendor default .*) and omitted entirely on the three session events, which the vendor documents as not using it at all. Augment's own PreToolUse/PostToolUse matcher vocabulary is its own tool names (launch-process, str-replace-editor, save-file, ...), the same set x-augment.tools already documents above, so a Claude-style matcher (Bash, Write, ...) parses and then matches nothing; that case surfaces a coverage note too. Commands emit to .augment/commands/; a source-layout scope becomes a nested command namespace, while description, argument-hint, and model stay in frontmatter. Ignore specs emit to .augmentignore, and import augment restores a hand-authored file without changing pattern order or negation semantics.

Config keys: outputs.augment.rules-dir (default .augment/rules), outputs.augment.agents-dir (default .augment/agents), outputs.augment.skills-dir (default .agents/skills), outputs.augment.commands-dir (default .augment/commands), outputs.augment.ignore-file (default .augmentignore), outputs.augment.rules-file (unset; opt-in, writes the legacy concatenated .augment-guidelines document), outputs.augment.mcp-file (default .augment/settings.json; also the hooks file, since both merge into the same document).

Verify with the real extension:

  1. Install the Augment Code extension (guidelines docs).
  2. Check the tree: ls AGENTS.md .augment/rules/ .augment/agents/ .agents/skills/, plus .augment-guidelines when outputs.augment.rules-file is set and .augment/settings.json when MCP or hook specs are present.
  3. Open the project; Augment reads AGENTS.md, .augment/rules/, .augment/agents/, .agents/skills/, and (via Auggie CLI) .augment/settings.json (and .augment-guidelines when present).
  4. When hook specs exist, confirm each .augment/settings.json hooks.<Event> entry loads: auggie prints no "invalid hook" warning at startup, and a PreToolUse hook against a script ending in .sh/.ps1/.cmd/.bat actually runs on the matching tool call.

Selecting targets

Persistent (config):

targets:
  - claude
  - cursor
  - copilot

Per-run (CLI):

agnostic-ai sync -t claude,cursor,copilot

CLI flag overrides config. Unknown targets log a warning and skip.

The default target set is 20: claude, codex, gemini, cursor, copilot, aider, cline, windsurf, continue, zed, opencode, antigravity, junie, kiro, crush, trae, qoder, openhands, factory, kilo. Amp, Warp, Jules, Goose, and Augment are opt-in, excluded from the default set so enabling them is a deliberate choice. Add them to targets: (or pass -t amp,warp,jules,goose,augment). Amp and Warp then emit their target-specific files (.agents/, .amp/settings.json, .warp/). Augment emits rules, agents, commands, ignore patterns, hooks, MCP servers, and shared skills under its documented project paths, plus the legacy .augment-guidelines document when outputs.augment.rules-file is set. Jules adds nothing beyond the shared pointer body. Goose emits project agents, shared skills, Open Plugins hooks, and review guidance, plus its native rules file when outputs.goose.rules-file is set.

Interactive init pre-ticks any target whose marker is present in the working directory (e.g. .claude/, .codex/, .gemini/, .cursor/, .github/copilot-instructions.md). The first-time sync prompt does the same. Toggle entries before confirming.

New targets

See adding adapters. ~50 lines plus one registry entry.

Global output

sync --global writes user-level configuration for 22 of the 25 targets. These paths are independent of the project outputs documented below. A dash means the vendor documents no user-level surface of that kind, so nothing is written rather than a path being guessed (target-audit 2026-09-07).

TargetInstructionsRulesHooksSkills
claude~/.claude/CLAUDE.mdinlined~/.claude/settings.json~/.claude/skills/<name>/
cursor~/.cursor/AGENTS.md (bridged)inlined~/.cursor/hooks.json~/.cursor/skills/<name>/
codex~/.codex/AGENTS.mdinlined~/.codex/hooks.json~/.agents/skills/<name>/
gemini~/.gemini/GEMINI.mdinlined~/.gemini/settings.json~/.gemini/skills/<name>/
qoder~/.qoder/AGENTS.mdinlined~/.qoder/settings.json~/.qoder/skills/<name>/
copilot~/.copilot/copilot-instructions.mdinlined-~/.copilot/skills/<name>/
cline~/.agents/AGENTS.mdinlined-~/.cline/skills/<name>/
windsurf~/.config/devin/AGENTS.mdinlined-~/.agents/skills/<name>/
amp~/.config/amp/AGENTS.mdinlined-~/.agents/skills/<name>/
zed~/.config/zed/AGENTS.mdinlined-~/.agents/skills/<name>/
warp~/.agents/AGENTS.mdinlined-~/.agents/skills/<name>/
opencode~/.config/opencode/AGENTS.mdinlined-~/.config/opencode/skills/<name>/
antigravity~/.gemini/GEMINI.mdinlined-~/.gemini/antigravity/skills/<name>/
junie~/.junie/AGENTS.mdinlined-~/.junie/skills/<name>/
kiro~/.kiro/steering/AGENTS.mdinlined-~/.kiro/skills/<name>/
crush~/.config/crush/CRUSH.mdinlined-~/.config/crush/skills/<name>/
factory~/.factory/AGENTS.mdinlined-~/.factory/skills/<name>/
kilo~/.config/kilo/AGENTS.mdinlined-~/.kilo/skills/<name>/
goose~/.config/goose/.goosehintsinlined-~/.agents/skills/<name>/
openhands---~/.agents/skills/<name>/
trae---~/.trae/skills/<name>/
augment-~/.augment/rules/<name>.md-~/.augment/skills/<name>/

Rules inline into the instructions file, under the same sentinel-marked managed block as the shared instructions body. Augment is the one exception: the vendor documents no user-level instructions file for the CLI (~/.augment/user-guidelines.md is VS Code only), and its ~/.augment/rules/ entries are "always treated as always_apply", which is exactly what a global rule is. Every path marked ~/.config/ follows XDG_CONFIG_HOME when that variable is set.

Three targets are absent by verdict. Aider reaches a home instructions file only through a read: entry in ~/.aider.conf.yml, never automatically. Continue's one documented home surface is the rules: list inside the config.yaml that Continue itself rewrites. Jules documents nothing at user scope at all: its CLI reference has no config file and no home path.

Several targets share a path on purpose, and the shared write happens once. ~/.agents/skills/ is read by codex, windsurf, amp, zed, warp, goose, and openhands; ~/.agents/AGENTS.md by cline and warp; ~/.gemini/GEMINI.md by gemini and antigravity. Two targets resolving to one path with different bytes is a hard error naming the path, not a last-writer-wins race.

Hooks reach five targets at user scope. Claude Code, Codex, Gemini, and Qoder all document the Claude-style {"hooks": {"<Event>": [{"matcher", "hooks": [...]}]}} shape there, so one renderer serves them; Cursor keeps its own. The other seventeen are declined for a stated reason, not for lack of a project surface: Factory keys hooks.json directly by event with no wrapper, Augment measures timeout in milliseconds and requires a script extension, Devin CLI keys .devin/hooks.v1.json as an unwrapped array of eight events with type accepting prompt as well as command, Antigravity nests events under a named hook object, Crush supports PreToolUse alone, Goose needs a wrapping plugin directory plus a manifest, Junie's are Early Access, Kiro uses a {"version": "v1", "hooks": [...]} array, and Amp, OpenCode, and Cline expose hooks only as TypeScript plugin modules. Copilot also documents a user-level hooks directory (~/.copilot/hooks/), left for a future user-scope pass; its {"version": 1, "hooks": {...}} shape and timeoutSec field are already implemented at project scope. Issue #629 is complete and covers project scope only.

Cursor does not automatically load the home-level AGENTS.md. Global sync therefore installs a managed sessionStart hook and a self-contained script bridge under ~/.cursor/hooks/ (POSIX shell on macOS and Linux, PowerShell on Windows). The bridge returns the rendered instructions as valid additional_context JSON without calling agnostic-ai, Python, or jq. Cursor session-start hooks are fire-and-forget context injection, not enforced policy. Existing sessionStart entries remain in place. Every other instructions path in the table above auto-loads, so Cursor is the only target that needs the bridge.

Two interactions to know before enabling everything at once. OpenCode reads ~/.claude/CLAUDE.md only when ~/.config/opencode/AGENTS.md does not exist, so syncing both targets moves OpenCode onto its own file and any user text that lived only in the Claude file stops reaching it. Devin CLI and VS Code Copilot read ~/.claude/ surfaces by default, so a user syncing claude plus windsurf or copilot gets the same instructions body through two paths.

Each adapter emits in its tool's native format: separate files where the tool supports them, a merged document otherwise. Unsupported features (e.g. hooks for a non-hook-aware target) skip with a warning by default. Override via on-unsupported in configuration.