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.
| Target | Entry-point file |
|---|---|
| claude | CLAUDE.md |
| codex | AGENTS.md |
| amp | AGENTS.md |
| warp | AGENTS.md |
| cline | AGENTS.md |
| windsurf | AGENTS.md |
| junie | AGENTS.md |
| kiro | AGENTS.md |
| crush | AGENTS.md |
| trae | AGENTS.md |
| jules | AGENTS.md |
| goose | AGENTS.md |
| augment | AGENTS.md |
| qoder | AGENTS.md |
| openhands | AGENTS.md |
| factory | AGENTS.md |
| kilo | AGENTS.md |
| opencode | AGENTS.md |
| gemini | GEMINI.md |
| aider | CONVENTIONS.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
| Target | Agents | Skills | Rules | Hooks | MCP | Commands | Settings | Reviews | Environments | Ignore |
|---|---|---|---|---|---|---|---|---|---|---|
Claude Codeclaude | Native | Native | Native | Native | Native | Native | Native | No | No | No |
Codexcodex | Native | Native | Native | Native | Native | Opt-in | Native | No | No | No |
Gemini CLIgemini | Native | Native | Native | Native | Native | Native | No | No | No | Native |
Cursorcursor | Native | Native | Native | Native | Native | Native | No | Native | Native | Native |
GitHub Copilotcopilot | Native | Native | Native | Native | Native | No | Native | No | No | No |
Aideraider | Source only | Source only | Native | No | No | No | No | No | No | Native |
Clinecline | Native | Native | Native | No | No | No | No | No | No | No |
Windsurf / Devinwindsurf | Native | Native | Native | Native | Native | No | No | No | No | Native |
Continuecontinue | Mapped | Mapped | Native | No | Native | No | No | No | No | No |
Ampamp | Opt-in | Native | Native | No | Native | No | No | No | Native | No |
Zedzed | Opt-in | Native | Native | Opt-in | Native | No | No | No | No | No |
Warpwarp | Opt-in | Native | Native | No | Native | No | No | No | No | No |
OpenCodeopencode | Native | Native | Native | No | Native | Native | Native | No | No | No |
Google Antigravityantigravity | Native | Native | Native | Native | Native | No | No | No | No | No |
Juniejunie | Native | Native | Native | No | Native | Native | Native | No | No | Native |
Kirokiro | Native | Native | Native | Native | Native | No | No | No | No | Native |
Crushcrush | No | Native | Native | Native | Native | No | No | No | No | Native |
Traetrae | Native | Native | Native | Native | Native | Native | No | No | No | Native |
Qoderqoder | Native | Native | Native | Native | Native | Native | Native | No | No | No |
OpenHandsopenhands | Native | Native | Native | Native | Native | No | No | No | Native | No |
Factoryfactory | Native | Native | Native | Native | Native | Native | No | No | No | No |
Kilo Codekilo | Native | Native | Native | No | Native | Native | Native | No | No | Native |
Julesjules | No | No | Native | No | No | No | No | No | No | No |
Goosegoose | Native | Native | Native | Native | No | No | No | Native | No | No |
Augmentaugment | Native | Native | Native | Native | Native | Native | No | No | No | Native |
No targets match this comparison.
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; aWARP_SKILL_DIRSenv 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/(globskill://.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>.mdinstead, 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. Withsync.shared-skills: true, byte-identical skill folders across these targets collapse into one canonical copy (.agents/skills/<name>preferred) plus per-skill symlinks; seesync.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 likebeforeShellExecution), Kiro (.kiro/hooks/<name>.json, one file per hook; Kiro also hasPreTaskExec/PostTaskExec/PostFileCreate/PostFileSave/PostFileDelete/AgentSpawnevents 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,hookskey, 27 events, merged alongsidemcpServersin the same file), Augment (.augment/settings.json,hookskey, five events, merged alongsidemcpServersin one write;timeoutis milliseconds, converted from the shared spec's seconds field, andcommandmust be a path ending in.sh/.ps1/.cmd/.bator it never runs, which still emits verbatim with a coverage note rather than a guessed rename), Crush (crush.json,hookskey,PreToolUseonly, the vendor's owndocs/hooks/README.mdsaying "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 alongsidemcpin the same file and the same write), Copilot (.github/hooks/agnostic-ai.json,{"version": 1, "hooks": {...}}, 14 events, command, HTTP, andsessionStartprompt handlers,timeoutSec), Trae (.trae/hooks.json, six events, the same integerversionwrapper Copilot uses around Claude-shaped{matcher, hooks: [...]}groups, plus aloop_limittheStopevent reads), Factory (.factory/hooks.json, nine events, keyed directly by event with no wrapper key,timeoutin seconds), Antigravity (.agents/hooks.json, five events, keyed by hook definition name rather than by event, withenabled: falsedisabling 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 themExecute/Create/FetchUrlwhere Claude saysBash/Write/WebFetch, so both join that divergent group too. Copilot documentsevent: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,userPromptTransformedandsubagentStart, 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 hooktool_nametable is not its own subagenttoolstable. The terminal tool isRunCommand, notBash, and there is noTodoWrite, so a Claude-style matcher parses and then matches nothing there too. Zed runs them via opt-inoutputs.zed.tasks-file;WorktreeCreatebecomes an automaticcreate_worktreetask hook and other events remain on-demand tasks. Other targets skip with a warning..claude/settings.jsonis turning into a cross-tool hook file, so syncingclaudealongside 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.jsonand.claude/settings.local.jsonfiles 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 singletarget: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
typefield (claude, cursor, copilot, continue, opencode, crush, kilo, factory, qoder, augment), remote (HTTP / SSE) entries carry an explicittypeand stdio entries omit it (crush and kilo tag stdio explicitly, with kilo also combiningcommand+argsinto one array; the others infer it as the default). amp, gemini, zed, junie, kiro, antigravity, trae, and warp have notypefield and infer the transport from the emitted keys (gemini viahttpUrlvsurl; antigravity viaserverUrlvscommand, and its doc is explicit that the legacyurl/httpUrlnames "are not supported"; the rest, trae and warp included, viaurlvscommand). Windsurf also has an explicit field, but spelledtransportrather thantype: 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 fieldtransportwith valueshttp(default) orsse, so this adapter holds its own builder rather than reuse the sharedtype-keyed one. OpenHands has notypefield 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.jsonuntil #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.jsonmerges themcpServersmap alongsideshell,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 onlycommand/args/envandurl/headers; a spec'sdescriptionandrootsare 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. Seedisabledsupport by target for which of these targets honor a spec'sdisabled: 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+descriptionfrontmatter only), Qoder (.qoder/commands/<name>.md,description-only frontmatter, required;nameis 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,descriptionandallowPromptArgumentthe vendor-documented frontmatter fields, though every other key still passes through verbatim; the body may reference$argumentNameplaceholders Junie substitutes at invocation; target-audit 2026-08-11, #605), Kilo Code (.kilo/commands/<name>.md, withdescription,agent,model,variant, andsubtaskfrontmatter; 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 unlessoutputs.codex.commands-diropts 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 viaamp.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,syncrequires every existing pattern to survive unchanged and in order, with no added negations. Extra exclusions are allowed. When preservation cannot be established,AAI-103names the risk and leaves the file untouched (#761). Runagnostic-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: falseremoves 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.mdfile under that directory (recursively) at session start, so the emitted rules load natively with no extra wiring. A spec with the cross-toolglobsfield (or a nativepathslist) emitspaths:frontmatter, which scopes the rule to matching files.outputs.claude.rules-mode: import(appends a sentinel-marked block of@.claude/rules/<name>.mdimports to the pointer body, round-trip-stripped onimport) predates native rules loading; keep it only for Claude Code versions older than the.claude/rules/rollout. The legacy alternativeoutputs.claude.rules-file: CLAUDE.mdconcatenates rule bodies into a single file and skips the pointer-body write forclaude. - Commands: one file per spec under
.claude/commands/. Specdeploybecomes/deploy. Frontmatter passes through; body is the prompt template. - Settings overlay:
agnostic-ai import claudecaptures the non-hooksportion of.claude/settings.json(statusLine, enabledPlugins, any top-level key) into.agnostic-ai/overlays/claude.settings.json.sync -t claudelayers the spec-derivedhookskey on top, reproducing the full settings.json from a fresh checkout. Re-runimport claudeafter editing settings.json by hand. - MCP: written into
.mcp.jsonunder the standardmcpServersmap (stdio:command/args/env, notype; remote:type+url/headers). Every entry also acceptstimeout(per-tool-call execution timeout in milliseconds; values under 1000 are ignored) andalwaysLoad(load the server's tools at session start instead of deferring them behind tool search, "available on all server types"). Anhttp,sse, orwsentry additionally acceptsheadersHelper, 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 anoauthobject takingclientId,callbackPort,authServerMetadataUrl, and a space-separatedscopesstring (code.claude.com/docs/en/mcp, target-audit 2026-08-27, #634).oauth.clientSecretis never written: the vendor keeps the secret in the system keychain, "not in your config".disabled: truehas no effect here; seedisabledsupport by target. - MCP import:
import claudereads.mcp.jsonand writes one spec under<mcps>/<name>.yamlpermcpServers.<name>. The nextsyncdistributes 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:
- Install:
npm install -g @anthropic-ai/claude-code(or the desktop app; both read the same files). - Check the tree:
ls CLAUDE.md .claude/agents/ .claude/skills/ .claude/rules/ .claude/commands/ .claude/settings.json .mcp.jsonandgrep "Generated by agnostic-ai" .claude/agents/*.md .claude/rules/*.md .claude/commands/*.mdfor the provenance header (it sits after the YAML frontmatter, sohead -1would only show---). - Validate JSON:
python -m json.tool .claude/settings.json > /dev/null && python -m json.tool .mcp.json > /dev/null. - Launch
claudefrom the project root./agents,/skills, and the slash-command picker should list every entry. The MCP picker shows each.mcp.jsonserver green. - Trigger a matcher action (e.g. an
EditforPostToolUse/Edit); the hook command runs with no "schema mismatch" in the log. - 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 withscope: services/paymentsinstead reachesservices/payments/AGENTS.md. Aglobsfield alone does not create a directory scope. Remove legacyoutputs.codex.rules-fileoverrides 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, anddeveloper_instructions, plus optional session config such asmodel,model_reasoning_effort, andsandbox_mode. A generictools: [Read, Bash, ...]list is not emitted because Codex definestoolsas a config table, not a tool allowlist. Sync reports the dropped field. Usex-codex.toolsfor native settings such asweb_searchandview_image. Use a per-targetmodelmap when another CLI's model name, such assonnet, 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 requiredSKILL.md(frontmattername+description, plus body). A scoped source skill moves the native directory under that scope, for exampleskills/services/api/review/SKILL.mdbecomesservices/api/.agents/skills/review/SKILL.md;import codexrestores the scope and bundled assets. When the spec carriesx-codex.interface,x-codex.policy, orx-codex.dependencies, anagents/openai.yamlis 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 viaoutputs.codex.hooks-file), routed byeventfrontmatter (SessionStart,SubagentStart,UserPromptSubmit,PreToolUse,PermissionRequest,PostToolUse,PreCompact,PostCompact,Stop,SubagentStop,SessionEnd,Interrupt) into per-event arrays withmatcherandcommand. Optionaltimeout,statusMessage,commandWindows,additionalContextLimit, andasync(run the hook in the background instead of blocking the session) pass through and surviveimport codex; an explicitadditionalContextLimit: 0is preserved because Codex uses it to pass complete hook context.import codexalso reads hooks straight out of a hand-authored.codex/config.toml, in the vendor's own documented inline shape:[[hooks.<event>]]carriesmatcheralone, 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 withmatcherandcommandon 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_toolcalls 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."serverandtoolare required,input(an argument-template object) is optional, and it sharestimeout/statusMessagewith the command shape. It emits as{type, server, tool, input, timeout, statusMessage}in the samehooks.json, andimport codexreads it back from there (#693).Exec policies: opt-in. Set
outputs.codex.exec-policies(inline list) oroutputs.codex.exec-policies-file(external YAML) to write.codex/rules/default.rulesin Codex's Starlarkprefix_rule(...)form. Unset writes nothing.MCP: lands in
.codex/config.toml. Servers emit as[mcp_servers.<name>]: stdio usescommand/args/env/cwdplus the mixedenv_varsarray, while HTTP/SSE usesurl/bearer_token_env_var/http_headers/env_http_headers/auth(oauthorchatgpt)/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 asnpm:@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_toolscarry 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, andexperimental_environmentalso 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 alongsideauth(#693). Atoolsmap 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 surviveimport codex. The project-tier config.toml is managed (overwritten each sync); put unmanaged Codex config in~/.codex/config.toml.Settings: the last portable
modelvalue writes to.codex/config.toml.outputs.codex.config.modelwins over the portable value. A captured.agnostic-ai/overlays/codex.config.tomlremains the highest-precedence layer for backward compatibility, so an importedmodelthere 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;syncprints a coverage note instead and sweeps a stale managed.codex/prompts/tree. Setoutputs.codex.commands-dirto emit the legacy layout anyway.Import:
import codexcaptures.codex/config.tomlminushooksandmcp_serversinto.agnostic-ai/overlays/codex.config.toml.sync -t codexprepends it before the spec-derived sections, somodel,sandbox,approval_policy,notify,[history],[profiles.*],[model_providers.*], and any other key survive a.codex/wipe. On conflict withoutputs.codex.config.*the overlay wins and the first-class key is dropped to keep TOML valid.import codexalso reads.codex/prompts/*.mdand 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:
- Install:
npm install -g @openai/codex(quickstart);codex --versionto confirm PATH. - Check the tree:
agnostic-ai sync -t codex, thenls .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-aiprovenance comment. - Validate syntax:
toml-test .codex/config.tomlandjq empty .codex/hooks.jsonshould both exit0. codex run "list one rule from this project". Codex picks upAGENTS.md, the agents, and skill folders. Look forloaded N agents/loaded N skills.- Trigger a hook by firing the targeted
event(e.g. anEditfor aPostToolUsehook); thecommandappears in the hook log. codex mcp listshows 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 withscope: services/paymentsinstead reachesservices/payments/GEMINI.md. Aglobsfield alone does not create a directory scope. Remove legacyoutputs.gemini.rules-fileoverrides 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/agentsand.gemini/agents)". That surface is what buys automatic delegation, an isolated context window,@nameinvocation, and the/agentslisting. 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>.tomlfilename with commands, so a same-named agent and command overwrote each other (target-audit 2026-09-11, #733). Frontmatter carries the two required fields,nameanddescription(falling back to the spec name), pluskind,model,temperature,max_turns, andtimeout_minswhen 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 throughx-gemini. A spec's generictoolslist translates onto Gemini's own tool names (Readtoread_file,Writetowrite_file,Edittoreplace,Globtoglob,Greptogrep_search,Bashtorun_shell_command,WebFetchtoweb_fetch,WebSearchtogoogle_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 absenttoolskey inherits every tool from the parent session. Setx-gemini.toolsto write Gemini's own vocabulary directly, including the documented*,mcp_*, andmcp_<server>_*wildcards; that override wins outright over the translated form. Setoutputs.gemini.emit-agents-as-commands: trueto also keep writing the old<name>.tomlslash 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. Setoutputs.gemini.emit-skills-as-commands: trueto additionally emit oneskill-<name>.tomlcommand per skill.Commands: one TOML per command spec under
.gemini/commands/<name>.toml, the directory Gemini reads project slash commands from.descriptionfrontmatter maps to the TOMLdescription; the body becomes theprompt. 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(mcpServersmap,hooksmap). Gemini keys the endpoint by transport: streamable-HTTP servers (type: http) usehttpUrl, SSE servers (type: sse) useurl; the adapter routes each automatically. Stdio servers also acceptcwd(working directory), the same cross-tool field Codex reads. Every server, any transport, also acceptstimeout(milliseconds),trust(bypass tool-call confirmations),description,includeTools, andexcludeTools; all five pass through verbatim (geminicli.com/docs/reference/configuration, #661). Hooks route byeventfrontmatter (e.g.BeforeTool,AfterTool,SessionStart,SessionEnd; Gemini CLI documents 11 events in total); each definition containsmatcherand a nestedhooksarray of{type: "command", command}handlers, as required by the hook reference. Portable hook timeouts convert from seconds to milliseconds.x-gemini.sequentialcontrols 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 viaoutputs.gemini.ignore-file. Up to v0.49 this wrote.aiexclude, which belongs to Gemini Code Assist and Gemini CLI never opens; a managed.aiexcludeis removed on the next sync. (#625)Import:
import geminireads every native directory on its own pass..gemini/agents/*.mdbecomes agent specs,.gemini/commands/*.tomlbecomes command specs,.gemini/skills/<name>/becomes skill folders, and.gemini/settings.jsonbecomes MCP and hook specs. A command'spromptbecomes the spec body in either form Gemini documents, the triple-quoted block or the single-line string, anddescriptionstays 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 TOMLsemit-agents-as-commandsandemit-skills-as-commandswrite 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 underx-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:
- Install:
npm install -g @google/gemini-cli(docs). - Check the tree:
ls GEMINI.md .gemini/agents/ .gemini/commands/ .gemini/settings.json,head -2 .gemini/agents/*.md(frontmatter first),head -1 .gemini/commands/*.tomlfor the provenance header,python -m json.tool .gemini/settings.json > /dev/null. gemini --list-commandsparses every<name>.tomlwith no "invalid TOML" / "unknown field" errors, and/agentslists every.gemini/agents/<name>.mdas delegatable.gemini --list-mcp-serversshows eachmcpServers.<name>ready.- 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 omitsglobs. A non-always rule withoutglobsfalls back to the Claude-spelledpathslist (comma-joined); when both are absent,globsis 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.mdcround-trips clean. (#443, #536)Agents: native Cursor subagents at
.cursor/agents/<name>.md(Cursor 2.4+): frontmattername+descriptionplus optionalmodel,readonly, andis_backgroundwhen the spec declares them; the body is the system prompt. The old flattened.mdcand 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/commandspage 308s to that link, a "migrate commands to skills" FAQ; no Cursor page documents.cursor/commandsdirectly any more, so this is the closest surviving reference. Override the directory viaoutputs.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 carriesname+description; optionalpaths,disable-model-invocation,icon,color, andmetadatapass through when the spec declares them (iconandcolorstyle the badge when the skill backs a Custom Mode). The pre-native flattenedskill-<name>.mdccopies 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.mdat the repo root for unscoped specs,<scope>/.cursor/BUGBOT.mdfor 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 viaoutputs.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 viaoutputs.cursor.environment-file. (#434)Ignore specs emit as
.cursorignore(gitignore syntax). Multiple specs concatenate. Override viaoutputs.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. Atype: prompthook instead emitspromptand optionalmodel. Both forms preservetimeout,loop_limit(includingnull),failClosed, andmatcher. Cursor uses camelCase event names (beforeShellExecution,afterFileEdit, ...), passed through verbatim;validateflags unrecognized ones. Thirteen of those events consume amatcher, 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), andbeforeSubmitPrompt,stop,afterAgentResponse,afterAgentThought(one fixed value each).lintno longer flags a matcher on the last five (#734). It also stays quiet onbeforeMCPExecutionandafterMCPExecution, which that table does not list; a warning there would be the same false positive in the other direction. Override viaoutputs.cursor.hooks-file. (#438)Cursor also reads Claude Code's own hook file. Third-party hooks puts
.claude/settings.jsonat rank 6 of a seven-rank merge with.cursor/hooks.jsonat rank 3, and "All matching hooks from every source run." So a repo syncingclaudeandcursortogether 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.jsonunder the standardmcpServersmap (the shared builder also used by Claude Code). A stdio server acceptsenvFile, a path to an env file loading additional variables; a remote (url) server accepts a static-OAuthauthobject (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: truehas no effect here; seedisabledsupport 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:
- Install Cursor from cursor.com.
- Check the tree:
ls .cursor/rules/ .cursor/skills/ .cursor/commands/ .cursor/mcp.json,grep "Generated by agnostic-ai" .cursor/rules/*.mdcfor the provenance header (it sits after the frontmatter block),python -m json.tool .cursor/mcp.json > /dev/null. - Open the project. The Rules panel loads every
.cursor/rules/*.mdc(confirmalwaysApplymatches 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. - If MCPs are configured, Settings → MCP shows every
mcpServers.<name>green. - If hooks are configured,
python -m json.tool .cursor/hooks.json > /dev/nullparses; trigger the matched event (e.g. a shell command forbeforeShellExecution) and confirm thecommandruns.
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 withglobs(or a source-layout scope likerules/backend/auth.md) emit as a separate.instructions.mdwithapplyToderived from globs (explicitglobswins, else<scope>/**). Always-on rules (no globs, no scope, oralwaysApply: 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: frontmattername+descriptionplustoolsandmodelwhen the spec declares them; arbitraryx-copilotkeys (target,user-invocable,mcp-servers, ...) pass through. The body is the agent prompt. The old flattenedagent-<name>.instructions.mdcopies 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 flattenedskill-<name>.instructions.mdcopies are gone; the ledger sweeps them.Chat modes: when
outputs.copilot.chatmodes-diris set, each agent also emits as a Copilot Custom Chat Mode at<dir>/<name>.chatmode.mdwithdescription/model/toolsfrontmatter. The native agent profile still emits alongside.Settings: the last portable
modelvalue merges into.github/copilot/settings.json, where Copilot CLI documents it as the repository default model. Other repository settings survive.import copilotrestores the model tosettings/imported.yamland 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.jsonuses the VS Code schema, top-levelserverskey, each entry carrying atype(stdio,http, orsse). VS Code's Agent Host does not read that file itself: the vendor doc says it "doesn't read.vscode/mcp.jsondirectly" and that VS Code forwards the config instead, except servers needing interactive input..github/mcp.jsoncarries the same servers undermcpServersfor 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.jsonfile for VS Code is not read by Copilot CLI. It uses the unsupported top-level keyservers." 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 withoutputs.copilot.mcp-file/outputs.copilot.cli-mcp-file.VS Code MCP output owns the complete
serversmap and preserves unrelated top-level keys, includinginputsandsandbox, at the default or configured path. JSONC is accepted; sync removes comments and normalizes formatting. Invalid JSONC aborts the write..github/mcp.jsonand the opt-in root mirror remain managed as whole documents.VS Code alone accepts stdio
cwd,envFile, andsandboxEnabled, remoteoauth: {clientId, enterpriseManaged}, anddev.watchon stdio, HTTP, and SSE servers.dev.debugis 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.jsonanywhere from the working directory up to the repository root, stays opt-in behindoutputs.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.jsonundermcpServerstoo, 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 viaoutputs.copilot.hooks-file), one of possibly several*.jsonfiles Copilot loads and merges from that directory: "Repository-level hook files:.github/hooks/*.jsonin 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 carryingmatcherdirectly ({"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, somatcher: Bashreaches 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,userPromptTransformedandsubagentStart("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.SubagentStartis the one that bites, since Claude Code and Codex both accept it (target-audit 2026-09-11, #737). Command, HTTP (url,headers,allowedEnvVars), andsessionStartprompt handlers all surviveimport copilotand re-emission.A hook spec that sets
argsemits Copilot's own shell-free form instead:{"type": "command", "exec": <command>, "args": [...]}. The field table is explicit that the executable moves out ofcommand(execis accepted "Instead ofbash,powershell, andcommand", and "Do not combineexecwithbash,powershell, orcommand"), which is where this differs from Claude Code, whose exec form keeps the executable incommand. It buys whatargsis for, a path or argument carrying a space running as written, and costs one surface:execandargsare both marked "Only supported in Copilot CLI", and a cloud agent job reads the same.github/hooks/*.jsonfiles: "A subset of events fires, and onlybash(orcommand) entries are honored." An exec-form entry is skipped whole there.syncraises a coverage note naming that trade. Leaveargsunset for a hook that must run under cloud agent (#755).Every hook runs twice when you sync
claudeandcopilottogether. The same "Hooks locations" section names both files:.github/hooks/*.jsonand, as a cross-tool read,.claude/settings.jsonand.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 blockingpreToolUsereturns 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 singletarget: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:
- Install the GitHub Copilot extension (and optional Copilot Chat) in VS Code.
- Check the tree:
ls .github/copilot-instructions.md .github/instructions/ .vscode/mcp.json .github/mcp.json,grep "Generated by agnostic-ai" .github/instructions/*.mdfor the provenance header (it sits after theapplyTofrontmatter),python -m json.tool .vscode/mcp.json > /dev/null && python -m json.tool .github/mcp.json > /dev/null. - Open the project. Copilot loads
.github/copilot-instructions.mdplus every matching.instructions.md. TheOutput → GitHub Copilotchannel shows no "failed to parse instructions" warnings. - Open a file matching a rule glob (e.g. a
.gofile forapplyTo: "**/*.go") and trigger chat; the rule body shows in context. - If MCPs are configured, run
MCP: Show Installed Servers; eachservers.<name>from.vscode/mcp.jsonis ready. In Copilot CLI, run/mcpfrom the project root after accepting the folder-trust prompt; eachmcpServers.<name>from.github/mcp.jsonis listed. - If hooks are configured,
python -m json.tool .github/hooks/agnostic-ai.json > /dev/nullconfirms the file parses. In Copilot CLI, a hook set onPreToolUseorpreToolUsefires 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:
- Install:
python -m pip install -U aider-chat(orpipx install aider-chat). - Check the tree:
ls CONVENTIONS.md .aider.conf.yml,head -1 .aider.conf.ymlmust start with the provenance header. - Validate YAML:
python -c "import yaml,sys; yaml.safe_load(open('.aider.conf.yml'))". aider --config .aider.conf.yml --no-stream --message "list the rules you were told to follow". The banner prints the resolvedread:paths (includingCONVENTIONS.md); the response reflects the rules text.- No
Warning:lines mentioningCONVENTIONS.mdor.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
pathsconstrained to the directory, even when the source setsalwaysApply: true. For unscoped rules,alwaysApply: falseenables nativepathsderived from the file selector; without a usable selector, the adapter reports a coverage note. See Cline conditions and scoped selector limits. The.cline/rulesversus.clinerulesruntime 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 carriesname+description; sibling assets next to the source SKILL.md are copied byte-for-byte.import clinereads 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:
- Install the Cline extension in VS Code.
- Check the tree:
ls .cline/rules/ .cline/agents/ .cline/skills/,grep "Generated by agnostic-ai" .cline/rules/*.mdfor the provenance header,test -f .cline/skills/*/SKILL.md. - 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>.mdappears wherever Cline surfaces project agent definitions, and each.cline/skills/<name>/loads as a skill. Open a file matching apathsrule and the "Conditional rules applied: workspace:<name>.md" notification names it; open an unrelated file and it stays out. - If
outputs.cline.workflows-diris set, each<workflows-dir>/<name>.mdis 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 underagents/", project layout.devin/agents/, "Flat file:agents/<name>.md" (docs.devin.ai/cli/subagents). Frontmatter carriesname,description,model,allowed-tools, andmax-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-toolstranslates 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/Bashmap one-to-one ontoread/grep/glob/exec, whileWriteandEditboth collapse ontoedit, so an agent declaring onlyWritealso gains edit capability. Anmcp__<server>__<tool>name passes through untranslated. Anything else drops with a coverage note rather than shipping a name the vendor never documented; setx-windsurf.allowed-toolsto write Devin's vocabulary directly, andx-windsurf.max-nestingfor the nesting override, which has no generic spec field.modelpasses through verbatim, since the vendor's own example pinsmodel: 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/rulesor.windsurf/rulesin 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>.mdreached no documented discovery path (target-audit 2026-08-27, #628). Sync sweeps the old nested tree through the ledger. Withoutputs.windsurf.rules-dirset 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: falsecarries atriggerfrontmatter key, the activation mode Devin reads.globspresent writestrigger: globplus the pattern verbatim, adescriptionalone writestrigger: model_decision, and neither writestrigger: 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 adescriptionhas 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, soalwaysApply: falsewas 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 windsurfreads all three in that order, and the first same-name skill wins. Nativetriggersvalues move underx-windsurfin 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.devinignorefile 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.codeiumignorefilename and.windsurfignore, but this adapter only writes the current.devinignorepath; override viaoutputs.windsurf.ignore-fileto write one of the legacy names instead. - MCP: merges into
.devin/mcp_config.jsonunder a rootmcpServersmap, 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 carriescommand/args/envfor local (stdio) servers, andurl/transport(httporsse)/headers/oauthClientId/oauthClientSecret/oauthResourcefor remote ones; both acceptdisabled, whichdevin mcp enable|disablealso toggles on this file (seedisabledsupport by target). The field is spelledtransport, not thetypekey the sharedmcpServers-with-typebuilder 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);mcpServersentries in the older.devin/config.jsonmigrate 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.jsonabove ("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.jsonis itself added to the managed.gitignoreblock 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). Atype: wsspec emits no server and raises a coverage note because Devin documents onlyhttpandsseremote transports. - Hooks: merge into
.devin/hooks.v1.json: "Create.devin/hooks.v1.jsonin 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 nopromptfield, so that shape only reaches the file through a hand-authoredtype/promptMeta pair),command, and optionaltimeout(seconds).matcheris a regex on the event'stool_name, available onPreToolUse,PostToolUse, andPermissionRequest. 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:
- Install Devin Desktop from devin.ai (formerly windsurf.com).
- Check the tree:
ls .devin/rules/ .devin/agents/ .agents/skills/,grep "Generated by agnostic-ai" .devin/rules/*.mdfor the provenance header,test -f .agents/skills/*/SKILL.md,python -m json.tool .devin/mcp_config.json > /dev/nullwhen MCP specs exist,python -m json.tool .devin/hooks.v1.json > /dev/nullwhen hook specs exist. - 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>.mdappears in the same panel, and a rule withtrigger: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>.mdprofile appears alongside the built-insubagent_exploreandsubagent_general, with no "profile skipped" warning. outputs.windsurf.workflows-dirhas 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).- When ignore specs exist,
cat .devinignoreshows the concatenated patterns and indexing skips them. - 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.jsonconnects, with a disabled spec showing as disabled. - Run
/hooksin Devin CLI to confirm each entry in.devin/hooks.v1.jsonloads, 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
globsconstrained to the directory and omitalwaysApply, allowing file matching to control inclusion. Unscoped rules retain their configuredglobs,alwaysApply, anddescription;x-continue.regexis available only withoutscope. See scoped selector limits. - MCP: each YAML under
.continue/mcpServers/is a Continue block file: aname+version+schema: v1wrapper with the server nested under anmcpServers:list (a flat single-server file does not load). Stdio emitscommand/args/env. Remote servers emittype/url/requestOptions, with noenv: 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: httplands astype: streamable-http, the only Streamable HTTP literal Continue's schema accepts (sseandstreamable-httpcarry over unchanged), and aheadersmap nests asrequestOptions.headers, the only place that branch reads headers from.import continueundoes 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 (commandon stdio,urlon a remote server), which trae, warp, antigravity and windsurf decline too. Both would match neither branch of the union and makeblockSchema.parsethrow, failing the whole file rather than skipping the entry. - Assistants: when
outputs.continue.assistants-diris set, each agent also emits as a YAML file at<dir>/<name>.yaml:name,version(0.0.1by default), andschema: v1at the top level, with the agent body wrapped as a singleprompts: [{name, description, prompt}]entry. That shape matches Continue's own schema exactly (promptSchemaandconfigYamlSchemaincontinuedev/continue'spackages/config-yaml/src/schemas/index.ts; docs.continue.dev/reference documents the samename/version/schema: v1top 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 scansoutputs.continue.assistants-diras a directory of assistants.understanding-configs.mdxdescribes 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, validconfig.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:
- Install the Continue extension in VS Code (or JetBrains).
- Check the tree:
ls .continue/rules/ .continue/mcpServers/,grep "Generated by agnostic-ai" .continue/rules/*.md .continue/mcpServers/*.yamlfor the provenance header,python -c "import yaml,sys; [yaml.safe_load(open(f)) for f in __import__('glob').glob('.continue/mcpServers/*.yaml')]". - Open the project. Continue loads every
.continue/rules/*.md; each appears in the rules picker with no "failed to parse" warnings. A rule withglobsshows as active only while a matching file is in context. - The MCP picker shows each
.continue/mcpServers/<name>.yamlgreen. - If
outputs.continue.assistants-diris set, each<dir>/<name>.yamlparses as a validconfig.yaml(python -c "import yaml; yaml.safe_load(open('<dir>/<name>.yaml'))"). Native directory discovery is unconfirmed, so load one explicitly withcn --config <dir>/<name>.yamlto 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 carriesname+description; sibling assets next to the source SKILL.md are copied byte-for-byte.import amprestores the full folder and preserves executable modes. Arbitraryx-ampkeys pass through, which is how a skill scopes its own MCP servers: Amp's docs say "a skill can define MCP servers in a siblingmcp.jsonfile or in themcpServersfield of itsSKILL.mdfrontmatter", and prefersmcpServerswhen both are present. Setx-amp.mcpServerson 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 whenoutputs.amp.rules-fileis set, and otherwise only through the entry-point pointer to the source specs, whichsyncreports 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: warnby 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.jsonunderamp.mcpServers(a single dotted key, not a nested object). Stdio emitscommand/args/env; HTTP/SSE emiturl/headers. Any other field reaches the entry throughx-amp, the same passthrough commands and skills already have; the builder enumerated a fixed set with no escape hatch until #634, so even an explicitx-ampkey was dropped. The field this unblocks isincludeTools, "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; onlyamp.mcpServersis 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 underx-amp.mcpServerson the skill spec (see Skills above) rather than emitting an MCP spec here. - Environments:
installwrites executable.agents/setup, which Amp runs while preparing a project orb or snapshot. Namedterminalsentries write.amp/services.yamlas supervised services, so long-running processes survive independently of the setup script. Each service requirescommand; 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 anx-amp.terminalsoverride can use nativecwd,port,env,health,portal,portals,review, andagentcontrols..agents/resumeis 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-generatedAGENT.mdat the configured root is renamed toAGENT.md.bak. A user-authoredAGENT.md(noGenerated by agnostic-aimarker) 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:
- Install with Amp's recommended direct installer:
curl -fsSL https://ampcode.com/install.sh | bash. If your environment requires npm, usenpm install -g @ampcode/cli. The VS Code extension reads the same files. - 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. - Confirm no command files came back:
test ! -d .agents/commands. - Validate the generated files:
python -m json.tool .amp/settings.json > /dev/nullandamp orb services ensureinside an orb. - Open the project; Amp indexes
AGENTS.mdunder "Project rules", loads each.agents/skills/<name>/SKILL.mdas a skill, runs.agents/setupwhile preparing a new snapshot, and supervises each declared service. - The MCP picker lists every
amp.mcpServersentry 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.
syncwrites the pointer body plus the sentinel-marked## Rulesblock 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.mdand stops there.AGENTS.mdis rank 7, behind Copilot's pointer-only entry-point at rank 5, so writing the rules toAGENTS.mdleft Zed with none of them whenever copilot was enabled too (target-audit 2026-08-27, #624)..rulesis rank 1 and nothing agnostic-ai emits can outrank it. Zed still callsAGENTS.mdits primary instruction file and.rulesa compatibility one, so a Zed release dropping.rulesmoves this back. Setoutputs.zed.rules-file: .rulesto 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; divergentx-zedoverrides surface through the collision check. Zed also documentsdisable-model-invocation("Set to true to hide from the agent's catalog (invocable via slash command or @-mention only)"): it reaches the emitted file viax-zed: {disable-model-invocation: true}, since this renderer merges onlyx-zedkeys. A plain top-leveldisable-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-fileis set (the merged document carries agent sections). - MCP: written into
.zed/settings.jsonundercontext_servers(Zed's key, notmcpServers). Stdio servers use a flatcommand/args/envshape; remote (HTTP / SSE) servers use a nativeurl/headersshape. A spec'sdisabled: trueemits as Zed's ownenabled: 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 ofContextServerSettingsContentincrates/settings_content/src/project.rscarries/// Whether the context server is enabled.over#[serde(default = "default_true")] enabled: bool, andcontext_serversis a field ofProjectSettingsContent, the struct.zed/settings.jsondeserializes into (target-audit 2026-08-27, #641). zed.dev/docs/ai/mcp names none of it; its onlyenabled-family key isenable_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 (timeouton either transport,oauthon an HTTP server,remoteon stdio and extension servers); those reach the file throughx-zed, since no other target documents a same-named field with the same meaning.timeoutis on both transports because theStdiovariant#[serde(flatten)]sContextServerCommand, 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. Seedisabledsupport by target. - Hooks: when
outputs.zed.tasks-fileis set, hook specs emit as Zed Tasks usingsh -c "<hook command>".WorktreeCreatewriteshooks: ["create_worktree"], so Zed runs the task after creating a linked worktree; import restores that event. Tasks without that hook import asOnDemandand run from the command palette. Any other documented Zed Task field passes through underx-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:
- Install Zed from zed.dev.
- 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. - Open the project. The agent panel reads
.rulesas project instructions and lists each.agents/skills/<name>/as a skill (@skill/ slash command). - The MCP picker shows each
context_servers.<name>ready. - The command palette runs every entry from
.zed/tasks.jsonas 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/; aWARP_SKILL_DIRSenv 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-diris 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. Thecommand: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 underx-warp;import warpcaptures them back the same way. - MCP: written into
.warp/.mcp.jsonunder the standardmcpServersmap. A stdio server'scommand/args/envcarry through, plusworking_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'scwdfield. A remote server (HTTP/SSE/WS) carriesurl/headers; Warp's remote-server table has no transport discriminant at all, so notypefield 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, androotsappear 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.disabledraises a coverage note instead of vanishing, and little is lost either way, since the same page says "project-scoped servers never auto-spawn" there.descriptionandrootsstay reachable throughx-warpfor anyone who wants them written anyway.import warpreads.warp/.mcp.jsonback, renamingworking_directorytocwd. An entry missing the field its table marks required (commandon the CLI Server table,urlon 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 onlyargsorenvproduced a server Warp lists and cannot launch. - Legacy rename: Warp's docs recommend
AGENTS.mdfor new projects but still fully supportWARP.md, and rank it first: "If bothWARP.mdandAGENTS.mdexist in the same directory,WARP.mdtakes priority" (docs.warp.dev/agents/capabilities/rules, target-audit 2026-09-08, #691). On first sync after upgrading, any agnostic-generatedWARP.mdat the configured root is renamed toWARP.md.bakso the newAGENTS.mdlayout takes over. A user-authoredWARP.md(noGenerated by agnostic-aimarker) is left untouched, but since it still outranksAGENTS.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:
- Install Warp from warp.dev.
- 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/*.yamlfor the provenance header,python -m json.tool .warp/.mcp.json > /dev/null. - 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 everymcpServers.<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 ownpackages/core/src/instruction-context.tson branchdev: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, sosyncwrites the file once instead of colliding. Before #623 the adapter wrote.opencode/AGENTS.mdto 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 nextsync; a hand-authored one is left alone. - Agents: native OpenCode agents at
.opencode/agents/<name>.md(plural dir; the singular is legacy): frontmatter filtered todescription,mode,model,temperature,permission, with arbitraryx-opencodekeys 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. Setoutputs.opencode.emit-skills-as-commands: trueto 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.jsonat the project root with a$schemalink and themcpmap. Stdio maps to{type: "local", command: [...], cwd}; HTTP/SSE/remote maps to{type: "remote", url, headers}.cwdis documented on the local-server table only ("Working directory for the MCP server process. Relative paths resolve from the workspace."), andtimeouton 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'sdisabled: truewrites"enabled": false, the key OpenCode's own MCP docs document ("You can also disable a server by settingenabledtofalse"); an enabled server gets no key at all, andimport opencodereadsenabled: falseback intodisabled: true. Any other documented field, including theoauthclient-credentials object for a "Pre-registered" remote server (same doc), reaches the entry throughx-opencode, the same passthrough commands and agents already have. Pre-existing non-managed keys (theme,model) are preserved; only$schemaandmcpare overwritten. Drift checks (sync --check,doctor) read the existing file, so user keys never report as drift anddoctor --fixkeeps them. - Settings: a settings spec's default
modelmerges into the sameopencode.json. Existing native keys survive.import opencoderestores the field tosettings/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:
- Install:
npm install -g sst/opencode(install docs). - Check the tree:
ls AGENTS.md .opencode/agents/ .opencode/skills/ .opencode/commands/ opencode.json,grep "Generated by agnostic-ai" .opencode/agents/*.mdfor the provenance header (it sits after the frontmatter),python -m json.tool opencode.json > /dev/null. - Launch
opencode. Every rule body fromAGENTS.mdis in context, every.opencode/agents/<name>.mdappears in the agent picker, every.opencode/skills/<name>/in the skills list, and every.opencode/commands/<name>.mdin the slash-command picker. - The MCP panel shows each
mcp.<name>fromopencode.jsonready, 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.mdfiles in the same root, so the nested form keeps Antigravity's restrictedmodeltier separate from their free-form model IDs (#717). Frontmatter carriesnameanddescription, 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 antigravityprefers 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 generictoolslist 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; setx-antigravity.toolsto write Antigravity's own names.modelis 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 throughx-antigravitytoo. - 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 toname+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.jsonunder a singlemcpServersobject (antigravity.google/docs/ide/mcp). Remote servers carryserverUrl; the vendor doc states the legacyurl/httpUrlfield names "are not supported," so this is a dedicated schema, not the sharedmcpServers-with-urlshape claude and cursor use. stdio servers carrycommand,args,env, andcwd; remote servers addheaders. Both transports acceptdisabledunder that literal name (seedisabledsupport by target), unlike codex and kilo which map it onto their ownenabled: 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 throughx-antigravityinstead, the same escape hatch Zed and Warp give their own unmapped fields.import antigravityreads.agents/mcp_config.jsonback, renamingserverUrlto the spec's genericurland preserving any other field underx-antigravitythe same way. - Hooks: merge into
.agents/hooks.json(override viaoutputs.antigravity.hooks-file): "Hooks are configured in ahooks.jsonfile 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 whereenabledlives: a spec'sdisabled: truewritesenabled: falseon its own definition ("Set tofalseto 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 literaldisabledthis 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 optionaltimeout(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'stranscriptPathresolves 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:
- Install Antigravity from the Google Antigravity public-preview download page.
- Check the tree:
ls .agent/AGENTS.md .agents/rules/ .agents/agents/ .agents/skills/,grep "Generated by agnostic-ai" .agents/rules/*.mdfor the provenance header (it sits after the frontmatter),test -f .agents/skills/*/SKILL.md, andpython -m json.tool .agents/mcp_config.json > /dev/nullwhen MCP specs exist. - Open the project; it surfaces
.agent/AGENTS.mdin the project-instructions panel with no "unrecognized file" warnings. - Open one of
.agents/rules/<name>.mdand verify the per-rule file is picked up; confirm each.agents/skills/<name>/SKILL.mdloads as a skill, and any.agents/mcp_config.jsonserver appears in the MCP panel. - Ask the agent to delegate to a custom subagent by name; each
.agents/agents/<name>/agent.mdprofile is selectable in the subagent panel and its task runs to Idle rather than hanging (a hang points at an unmappedtoolsname, so checkx-antigravity.toolsagainst the vendor's vocabulary). - 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:
- Install the Junie plugin in a JetBrains IDE (or the Junie CLI, docs).
- 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. - Ask Junie to list its guidelines; the rule bodies inlined in
.junie/AGENTS.mdapply, each.junie/agents/<name>.mdappears as a delegatable subagent, each.junie/skills/<name>/folder appears as a skill, and each.junie/commands/<name>.mdappears as a/nameslash 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:
- Install Kiro from kiro.dev.
- 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, andpython -m json.tool .kiro/hooks/*.json > /dev/nullfor each hook file. - 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>.mdprofile. - Trigger a hook's
triggerevent (e.g. save a file forPostFileSave); 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 ahookskey alongsidemcp, in oneMergeJSONFilewrite (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'sschema.json, whose$defs.HookConfigcarries 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, soPreToolUse,pretooluse,PRETOOLUSE,pre_tool_use, andPRE_TOOL_USEall work" (verified 2026-09-11, #731). This adapter applies that rule when it decides whether a spec is aPreToolUsehook, and always writes the canonicalPreToolUsekey, so the output shape never depends on how the spec spelled it. APreToolUseentry 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.commandis the only required field;timeoutis 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 crushreadshooks.PreToolUseback into hook specs: a named entry'snamebecomes both the spec'sname: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:
- Install:
brew install charmbracelet/tap/crush(or see the README). - Check the tree:
ls AGENTS.md .agents/skills/,python -m json.tool crush.json > /dev/null. - Launch
crush; the context loadsAGENTS.md, the skills list shows each.agents/skills/<name>/, eachmcp.<name>connects, and aPreToolUsehook'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/*.mdfile carriesdescription/globs/alwaysApplyYAML frontmatter, the same three-field activation matrix Cursor's.mdcrules use.alwaysApplydefaults totrue; atruerule omitsglobsentirely;alwaysApply: falsewith no explicitglobsfalls 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). Setx-trae.scene: git_messageon 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 carriesnameanddescription(both required), plus optionalmodel,tools,disallowedTools, andmcpServers; 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.toolspasses 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, plusSkill,LSP,TodoWrite, andmcp__<server>__<tool>), comma-joined into the string spelling the vendor documents rather than a YAML list.modelis 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-targetmodel:value, so a bare generic model drops with a coverage note. Name one for Trae withmodel: {trae: <id>}orx-trae.model;disallowedToolsandmcpServersreach the file throughx-traethe 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 carriesname+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
.mdper command under.trae/commands/<name>.md,name+descriptionfrontmatter 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,NameandDescription, matching what this adapter already emitted from real.trae/commands/*.mdfiles. Onlynameanddescriptionare 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.jsonunder a rootmcpServersmap, confirmed by a direct fetch of Trae's own MCP doc. Stdio entries carrycommand(required) plus optionalargs/env; HTTP entries carryurl(required) plus optionalheaders. Neither carries atypefield: Trae tells the two apart by which ofcommandorurlis present, so this adapter never writes one.disabledhas no documented per-server key either (only a project-level MCP toggle under Settings > MCP), so a spec'sdisabled: trueis stripped with a coverage note instead of written dead. The vendor doc cautions that a stdiocommandmust 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 integerversionenvelope ("The default value is 1, and currently only 1 is supported") around ahooksmap 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 onlycommandis supported"),command, andtimeout(seconds, default 30). A spec'sloop_limitalso emits, Trae's own group-level field capping how often aStophook may block the agent from stopping; leave it unset for the vendor default of 5. The matcher vocabulary is the trap here. Trae's hooktool_nametable is not its subagenttoolstable: it readsRead,Write,Edit,Glob,Grep,LS,RunCommand,WebSearch,WebFetch,AskUserQuestion,Skill, andmcp__<serverName>__<toolName>. The terminal tool isRunCommand, notBash, and there is noTodoWrite, so a Claude-stylematcher: Bashparses 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.matcheris read onPreToolUse,PostToolUse, andNotificationonly, and onNotificationit 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.jsonand.claude/settings.local.jsonin 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 syncingclaudeandtraetogether 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.ignorefile 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/#Foldercontext ("any ignored files or folders will not be included as context"). Unlike every other ignore target, it does not apply on save: "The.ignorefile 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 viaoutputs.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:
- Install Trae from trae.ai.
- 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. - Open the project; the Rules panel lists every
.trae/rules/*.mdwith no parse warnings. Each.trae/skills/<name>/loads as a skill, each.trae/commands/<name>.mdis invokable from chat, and Settings > MCP lists each.trae/mcp.jsonserver (project-level MCP toggled on). - Turn on Settings > Beta > Subagents > Enable Subagents Directory, then ask the Agent to delegate to one by name; each
.trae/agents/<name>.mdis routable and the frontmatter parses with no BOM or delimiter error. - 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#Workspacecontext.
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.nameanddescriptionare required frontmatter;model,tools,color,skills, andmcpServersare 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.toolsrenders as a comma-separated string (tools: Read, Grep, Bash), the only form the vendor doc shows, not a YAML list;import qodersplits 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 generictoolslist straight through safe here, unlike Kilo Code and Augment, whose own vocabularies differ and which drop the field with a coverage note instead.skillsandmcpServershave 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 plainname+description, the only keys the vendor doc shows; bundled sibling files (scripts, references, templates) copy byte-for-byte alongsideSKILL.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>.mdas 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) andname(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 writesnameand always writesdescription, 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.jsonunder the standardmcpServersmap (stdio:command/args/env/cwd, notype; 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 anoauthobject 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; onlymcpServersis overwritten. That holds for a JSONC file too: the vendor documents this path as "JSON format (supporting//comments)" (docs.qoder.com/cli/settings), sosyncstrips 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.jsonasmodel.name;permissions.allow,permissions.deny, andpermissions.askmap directly. The write merges with hooks, MCP servers, and unrelated native keys. Import restores those portable fields tosettings/imported.yaml.This adapter wrote the project-root
.mcp.jsonuntil #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 ownoauthandtimeoutshapes), 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 → mcpServersis 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.jsonleft by an older agnostic-ai still loads, and Qoder's precedence order puts it ahead of.qoder/settings.jsonfor 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. Seedisabledsupport by target.Hooks: merge into that same
.qoder/settings.json, under ahookskey alongsidemcpServers(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 sharedclaudehookswire 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, sinceevent: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'smatcherdocuments "Tool name (e.g.Bash,Write,Edit,Read,Glob,Grep; MCP tool names likemcp__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(alwayscommand), optionalargs,timeout(seconds, vendor default 600),statusMessage,async,asyncRewake,shell(bashorpowershell), andif(a permission-rule filter, e.g.Bash(git *)). All nine are documented with the same semanticsclaudehooks.CommandEntryalready models for Claude Code.argsswitches the entry to exec form:commandbecomes the path of a single executable and eachargselement 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 ignoresshellonceargsis set; a spec that sets both still writes both, andsyncprints a note saying the shell never runs. Qoder additionally documentsenv,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
hooksandmcpServershappens in one write, not two:MergeJSONFilere-reads.qoder/settings.jsonfrom 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:
- Install Qoder from qoder.com.
- 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/*.mdfor the provenance header,python -m json.tool .qoder/settings.json > /dev/null. - Open the project; the rules panel lists every
.qoder/rules/*.mdwith no parse warnings, the agent picker lists every.qoder/agents/*.md, each.qoder/skills/<name>/folder loads as a skill, each.qoder/commands/<name>.mdruns from/, the MCP picker shows eachmcpServers.<name>from.qoder/settings.jsonconnected (project-level servers need approval on first use), and a configured hook fires on its event (e.g. aPreToolUsehook 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 viaoutputs.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(alwayscommand), optionaltimeout(seconds, vendor default 60) andasync. Amatcheronly applies to the two ToolUse events. OpenHands names its own tools, and the vendor flags the trap itself ("tool names (e.g.,terminalvsBash)"), 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 onlyterminalis documented alongside*and regex, leaving no vendor-stated counterpart for the rest. - MCP: merges into
./config.tomlunder a[mcp]table with three arrays instead of atypefield:stdio_servers([[mcp.stdio_servers]]tables carryingname/command/args/env) andsse_servers/shttp_servers(shttp_serversis OpenHands' streamable-HTTP transport, the cross-tool spec'stype: 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-levelapi_keyand/or (shttp only)timeoutfield; TOML allows mixing both forms in one array, as OpenHands' own example does.timeout(int, 1-3600 seconds, default 60, vendor exampletimeout = 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 genericheadersfield has no equivalent here (OpenHands documents only the singleapi_keycredential, 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-tierconfig.tomlis managed (its[mcp]table is overwritten each sync); keep unmanaged OpenHands config elsewhere. - Environments: an environment spec's
installfield writes.openhands/setup.sh, the vendor's documented repository bootstrap script ("You can add a.openhands/setup.shfile, 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/bashshebang, then the provenance header, theninstallverbatim 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'sinstallwins.
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:
- Install OpenHands (docs).
- 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.tomlfor the provenance comment. - Launch OpenHands; the context loads
AGENTS.md, each.agents/skills/<name>/appears as a skill, a path-triggered rule's.agents/skills/<name>/SKILL.mdinjects only when a file matching itspaths:globs is touched, each[mcp]server fromconfig.tomlconnects,.openhands/setup.shruns at session start, and each.openhands/hooks.jsonentry fires on its event (checkOPENHANDS_EVENT_TYPEin 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
toolslist 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.BashbecomesExecute,WritebecomesCreate, andWebFetchbecomesFetchUrl, 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:TodoWriteandSkillare "always included for every droid ... You do not list them", so they drop without a note since the droid keeps them anyway;ExitSpecModeandGenerateDroid"cannot be enabled by a custom droid", so they drop like any unknown name; and the literaltools: allis 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. Setx-factory.toolsto 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. Seetoolssupport by target. - Commands: written to
.factory/commands/<name>.mdwithdescriptionandargument-hintfrontmatter. The body stays Markdown and$ARGUMENTSis 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:syncoverwrites it whole, the same as.factory/mcp.jsonbelow. 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, "Standalonehooks.jsonfiles 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.jsoncarries. Per entry:type(always"command", the vendor's field table documents no other value),command, and optionaltimeout(seconds, vendor default 60 when absent, not milliseconds).matcheris 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, andWebFetch, the same three namestools.go's translation table renames toExecute,Create, andFetchUrlfor thetoolsfrontmatter 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.jsonunder the standardmcpServersmap, the same shape Claude Code and Cursor use (stdio:command/args/env, notype; remote:type+url/headers). The file is managed:syncoverwrites 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 withdroid mcp removeor the/mcpmanager. To remove them, edit.factory/mcp.jsondirectly" (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-serverdisabledboolean (defaultfalse), unlike Claude Code, Cursor, and Copilot, so agnostic-ai passes a spec'sdisabled: truestraight through instead of stripping it. Both transports preservedisabledTools,timeout, andconnectTimeout(milliseconds), including explicit zero timeouts. Remote HTTP/SSE servers also acceptoauth: falseor an OAuth object withscopes,resource,authorizationServerIssuer,clientId,clientSecret,clientMetadataUrl,tokenEndpointAuthMethod, andcallbackPort.x-factoryoverrides each top-level option. These fields stay scoped to Factory. Seedisabledsupport by target. Atype: wsspec 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:
- Install the Factory CLI (subagents docs).
- Check the tree:
ls AGENTS.md .factory/droids/ .agents/skills/ .factory/hooks.json .factory/mcp.json,grep "Generated by agnostic-ai" .factory/droids/*.mdfor the provenance header (it sits after the frontmatter),python -m json.tool .factory/hooks.json > /dev/nullwhen hook specs exist,python -m json.tool .factory/mcp.json > /dev/null. - Launch
droid; each.factory/droids/<name>.mdappears in the droid picker, each.agents/skills/<name>/loads as a skill, eachmcpServers.<name>from.factory/mcp.jsonconnects, and/hooksshows each entry in.factory/hooks.jsonunder 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:
- Install Kilo Code (docs).
- 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/*.mdfor the provenance header. - Open the project; each
.kilo/rules/<name>.mdlisted inkilo.jsonc'sinstructionsarray appears in the loaded-rules list, each.kilo/agents/<name>.mdappears in the agent picker, each.agents/skills/<name>/folder loads as a skill, each.kilo/commands/<name>.mdruns as/<name>, and eachmcp.<name>fromkilo.jsoncconnects, 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:
- Sign in to Jules (docs).
- Check the tree:
ls AGENTS.md. - Point Jules at the repo; it reads
AGENTS.mdas 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:
- Install Goose (docs).
- Check the tree:
ls AGENTS.md .agents/agents/ .agents/skills/, plus.goosehintswhenoutputs.goose.rules-fileis set. - Launch
goose; it readsAGENTS.md(and.goosehintswhen present) as context, each.agents/agents/<name>.mdappears 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:
- Install the Augment Code extension (guidelines docs).
- Check the tree:
ls AGENTS.md .augment/rules/ .augment/agents/ .agents/skills/, plus.augment-guidelineswhenoutputs.augment.rules-fileis set and.augment/settings.jsonwhen MCP or hook specs are present. - Open the project; Augment reads
AGENTS.md,.augment/rules/,.augment/agents/,.agents/skills/, and (via Auggie CLI).augment/settings.json(and.augment-guidelineswhen present). - When hook specs exist, confirm each
.augment/settings.jsonhooks.<Event>entry loads:auggieprints no "invalid hook" warning at startup, and aPreToolUsehook against a script ending in.sh/.ps1/.cmd/.batactually 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).
| Target | Instructions | Rules | Hooks | Skills |
|---|---|---|---|---|
| claude | ~/.claude/CLAUDE.md | inlined | ~/.claude/settings.json | ~/.claude/skills/<name>/ |
| cursor | ~/.cursor/AGENTS.md (bridged) | inlined | ~/.cursor/hooks.json | ~/.cursor/skills/<name>/ |
| codex | ~/.codex/AGENTS.md | inlined | ~/.codex/hooks.json | ~/.agents/skills/<name>/ |
| gemini | ~/.gemini/GEMINI.md | inlined | ~/.gemini/settings.json | ~/.gemini/skills/<name>/ |
| qoder | ~/.qoder/AGENTS.md | inlined | ~/.qoder/settings.json | ~/.qoder/skills/<name>/ |
| copilot | ~/.copilot/copilot-instructions.md | inlined | - | ~/.copilot/skills/<name>/ |
| cline | ~/.agents/AGENTS.md | inlined | - | ~/.cline/skills/<name>/ |
| windsurf | ~/.config/devin/AGENTS.md | inlined | - | ~/.agents/skills/<name>/ |
| amp | ~/.config/amp/AGENTS.md | inlined | - | ~/.agents/skills/<name>/ |
| zed | ~/.config/zed/AGENTS.md | inlined | - | ~/.agents/skills/<name>/ |
| warp | ~/.agents/AGENTS.md | inlined | - | ~/.agents/skills/<name>/ |
| opencode | ~/.config/opencode/AGENTS.md | inlined | - | ~/.config/opencode/skills/<name>/ |
| antigravity | ~/.gemini/GEMINI.md | inlined | - | ~/.gemini/antigravity/skills/<name>/ |
| junie | ~/.junie/AGENTS.md | inlined | - | ~/.junie/skills/<name>/ |
| kiro | ~/.kiro/steering/AGENTS.md | inlined | - | ~/.kiro/skills/<name>/ |
| crush | ~/.config/crush/CRUSH.md | inlined | - | ~/.config/crush/skills/<name>/ |
| factory | ~/.factory/AGENTS.md | inlined | - | ~/.factory/skills/<name>/ |
| kilo | ~/.config/kilo/AGENTS.md | inlined | - | ~/.kilo/skills/<name>/ |
| goose | ~/.config/goose/.goosehints | inlined | - | ~/.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.