This document is the contract for adding rafter integration for a new agent CLI / IDE (e.g. a future "Foobar Code", a new fork of Cursor, a new agent runtime). Follow it and the integration ships in both Node and Python with verify-able coverage on day one. Skip steps and the gap surfaces in the next parity audit.
Background. rafter ships dual implementations (Node + Python) that must stay in lockstep, plus a documentation surface (
recipes/,shared-docs/CLI_SPEC.md, README) and a runtime contract (rafter agent verify). Adding a platform means changing all of them in one PR. The 2026-04-30 audit (rf-guvb) found that without a single document explaining this, every new platform shipped with at least one gap (Continue.dev hooks were a silent no-op, Aider's MCP append was a silent no-op, Windsurf's hooks file was never read by the IDE).
Before writing any code, answer these five questions about the new platform. Each "yes" implies code in both impls, a registry entry, a verify check, and a recipe section. Each "no" should be documented, not dropped — the recipe should explain why the platform doesn't get that integration.
| Question | If yes, ship | If no, document |
|---|---|---|
| Hooks: does the platform have a documented pre/post-tool-use hook surface? Cite the URL. | Hook installer + <platform>.hooks ComponentSpec + matcher matching the platform's documented schema |
Recipe says "no hook surface — context only". Don't write a hook file the platform won't read (rf-cia phase b lesson). |
| Skills / rules: does the platform have a workspace-or-user persistent-rules primitive? | Per-skill rule files + <platform>.rules ComponentSpec, one file per skill from AGENT_SKILLS |
Recipe documents the closest analog (e.g. Aider has only read: lists, OpenClaw is wrong-category) |
Instruction file: does the platform read a workspace-root file like AGENTS.md / CLAUDE.md / GEMINI.md? |
installGlobalInstructions branch with the marker-block injection pattern |
Skip. Note in recipe that the platform doesn't have one. |
| MCP: does the platform have native MCP support (config path? schema?)? | MCP installer + <platform>.mcp ComponentSpec writing the rafter mcp serve entry |
Recipe says "no MCP — rafter CLI must be invoked directly". Don't append unknown YAML keys (rf-du2o lesson). |
| Sub-agent: does the platform have a first-class sub-agent primitive? Today only Claude Code does. | <platform>.subagent ComponentSpec + body file |
Skip. Re-evaluate quarterly. |
A "yes" without docs citing the schema URL is not a yes — that's the research gap that produced rf-cia in the first place. Verify schemas against the platform's current docs before writing the installer.
For a new platform <P> (e.g. cleo, foobar):
-
node/src/commands/agent/init.ts- Install function(s):
install<P>Hooks,install<P>Mcp,install<P>Rules, etc. Only the ones that apply. --with-<p>option in theagent initcommand builder.- Detection:
has<P> = scope === "user" && fs.existsSync(<P-config-dir>). - Opt-in flag:
wantP = opts.with<P> || opts.all(drop&& !opts.localif the platform has a project-scope install). - Install branch:
if (wantP && (hasP || opts.local)) { installP*(root); ... }. - "Detected environments" + "Restart
<P>to load …" prompts.
- Install function(s):
-
node/src/commands/agent/components.ts- One
ComponentSpecper surface (<p>.hooks,<p>.mcp,<p>.rules, etc.) withid,platform,kind,description,detectDir,path,isInstalled,install,uninstall. - Append each to the registry list in
getComponentRegistry().
- One
-
node/src/commands/agent/verify.tscheck<P>(): CheckResultthat returns{ name, passed, detail, optional: true }. Alwaysoptional: true— verify exits 1 only on hard failures (Config / Gitleaks).- Append to
results: CheckResult[]increateVerifyCommand. - If the platform has a hook surface, plan a follow-up to add a
--probebranch (see "Verification gate" below).
- Resource templates (if rules / skills):
node/resources/<p>-rules/<skill>.md(one per skill inAGENT_SKILLS). Static files; copied at install time, no runtime templating. Use the platform's documented YAML frontmatter.
Mirror every change in Python:
-
python/rafter_cli/commands/agent.py_install_<p>_*functions matching the Node ones.--with-<p>typer option, detection, opt-in, install branch, restart hint.
-
python/rafter_cli/commands/agent_components.py_<p>_hooks(),_<p>_rules(), etc. returningComponentSpecdataclasses, registered in_REGISTRY.
-
python/rafter_cli/commands/agent.pyverify section_check_<p>(): _CheckResultmirroring the Node logic.- Append to
resultsin theverify()command body.
- Resource templates:
python/rafter_cli/resources/<p>-rules/<skill>.md. Identical content to the Node templates (the templates ship as static files in both packages — sync drift is checked by the integration tests).
-
node/tests/agent-components.test.ts- Add
<p>.hooks/<p>.rules/<p>.mcpto the expected component-id list.
- Add
-
node/tests/platform-integration.test.ts- New
describe("<P> ... (--with-<p>)")block: rule-file presence, frontmatter shape, MCP entry shape, idempotency on reinstall, AGENTS.md (if applicable). - Append assertions to the "All 8 platforms config validation" combined test.
- New
-
python/tests/test_agent_init.pyclass TestInstall<P>Rules(or equivalent) mirroring the Node tests.
-
python/tests/test_agent_components.py- Add
<p>.*ids to the registry shape test.
- Add
-
python/tests/test_agent_verify.pyclass TestCheck<P>mirroring the Node verify checks.
-
recipes/<p>.md— what gets installed, scope (user vs--local), manual setup, verify command. Recipe must match installer reality. If you write a hook file the platform doesn't read, the recipe must not claim hooks are installed. - README "Supported Platforms" section — add the platform with one-line description.
-
shared-docs/CLI_SPEC.md— add the platform to the verify check table; document the--with-<p>flag if the option set is non-obvious. -
shared-docs/PLATFORM_PARITY_AUDIT.md— flip the row for the new platform from "n/a" to the new state. -
CHANGELOG.md— entry under[Unreleased]describing what got installed and why.
- Both Node and Python full test suites pass on the touched files.
- Live smoke:
rafter agent init --local --with-<p>(Node + Python builds) writes the expected files in a clean tmp dir. rafter agent verify(Node + Python) reports the new check;--jsonincludes it;--proberuns end-to-end on platforms with hook surfaces.- The platform parity audit row matches the install reality.
Document the hook schema before writing any code.
- Find the canonical hook reference (e.g.
developers.openai.com/codex/hooks,geminicli.com/docs/hooks/reference,cursor.com/docs/agent/hooks). - Confirm the file path the platform actually reads (cf. the Continue.dev
~/.continue/settings.jsonno-op: that path was never in the schema). - Confirm the matcher syntax (regex against tool names? exact match? glob? what tool names does the platform use?).
- Confirm exit-code semantics (most agent platforms:
2= block,0+ JSON = structured response).
Cite the URL inline in the install function's docstring. If the hook docs don't exist in the platform's docs, the install is a no-op — don't ship it.
| Platform | Source | Pattern |
|---|---|---|
| Claude Code | Anthropic docs | Bash, Write|Edit |
| Codex CLI | OpenAI docs | Bash|apply_patch (rf-ovql) |
| Cursor | Cursor docs | three events: preToolUse, postToolUse, beforeShellExecution |
| Gemini CLI | Google Gemini docs | run_shell_command|write_file|replace|edit (rf-044o) |
| Windsurf | (no hook surface) | n/a — pruned in rf-0vr3 |
| Continue.dev | (no hook surface) | n/a — pruned in rf-cia phase b |
| Aider | (no hook surface) | n/a — Aider has no plugin/hook system |
Most agent platforms now ship some flavor of "context that the agent fetches when relevant." We adopt the platform's own primitive:
| Platform | Primitive | Frontmatter |
|---|---|---|
| Claude Code | .claude/skills/<name>/SKILL.md |
name, description, allowed-tools |
| Codex CLI | ~/.agents/skills/<name>/SKILL.md |
same as Claude (the skill format is shared via the .agents/ convention) |
| Gemini CLI | ~/.agents/skills/ + gemini skills link runtime registration |
(rf-yit lesson: file-presence ≠ runtime registration) |
| Cursor | .cursor/rules/<name>.mdc |
description, alwaysApply, globs |
| Windsurf | .windsurf/rules/<name>.md |
trigger: model_decision, description |
| Continue.dev | .continue/rules/<name>.md |
name, description, alwaysApply |
| Aider | read: list in .aider.conf.yml (single file pointer) |
n/a — Aider has no rule frontmatter |
Always one rule per skill (per AGENT_SKILLS), not one consolidated rule. Each rule body should be a pointer to the canonical skill file (Read .claude/skills/<n>/SKILL.md), not a copy — the canonical content lives in one place and the rule's job is just to make the agent fetch it on the right trigger.
AGENTS.md is the cross-platform standard — both Codex and Windsurf read it natively. CLAUDE.md is Claude Code's, GEMINI.md is Gemini's. Use the existing marker-block injection (<!-- rafter:start --> ... <!-- rafter:end -->) so user content is preserved across reinstalls.
If a platform supports AGENTS.md, add the platform to installGlobalInstructions (Node) / _install_global_instructions (Python) so a single AGENTS.md write covers both that platform and Codex.
If the platform has documented MCP support:
- Find the config path (varies wildly:
.mcp.json,~/.continue/config.json,~/.codeium/windsurf/mcp_config.json,~/.cursor/mcp.json,~/.gemini/settings.jsonmcpServers). - Find the schema (array
[{name, command, args}]vs object{<name>: {command, args}}— Continue.dev accepts both). - Use
RAFTER_MCP_ENTRY({ command: "rafter", args: ["mcp", "serve"] }) verbatim.
If the platform has no documented MCP support (Aider), do not append an unknown YAML key — Aider silently ignores them. The rf-du2o post-mortem covers this in detail.
Today only Claude Code has a first-class sub-agent primitive (.claude/agents/<name>.md). Cursor reads .claude/agents/ too, so a single sub-agent file ships to both. Re-evaluate quarterly — if Codex or Gemini ship a sub-agent surface, add it.
Every change ships in both Node and Python in the same PR. Versions are pinned together (node/package.json ↔ python/pyproject.toml); CI validate-release.yml enforces version match.
If you're tempted to ship Node-only and "follow up with Python" — don't. The audit doc has a recurring entry called "Python missing" because that PR never lands. Mirror as you go.
The shared resource templates (skill content, rule body, sub-agent body) live as static files under node/resources/ and python/rafter_cli/resources/. Copy via fs.copyFileSync (Node) / importlib.resources.files(...).read_text() (Python). No runtime templating — the install path is just file copy. Differences between the two trees are caught by the platform-integration.test.ts "All 8 platforms" assertions.
Every new platform must come with:
- File-presence tests —
tests/agent-components.test.tsasserts the registry lists the new IDs;tests/platform-integration.test.tsassertsagent init --with-<p>writes the expected files; Python mirrors both. - A verify check —
check<P>/_check_<p>that goes from "platform dir exists" → "config file exists" → "rafter entry present" withoptional: trueat every failure (verify exit 1 is reserved for hard infra failures, not platform absence). - Where the platform has a hook surface, a
--probebranch — extend--probe(rf-65zg) to synthesize the platform's documented hook stdin payload and assert~/.rafter/audit.jsonlrecords the interception. The probe is the only thing that catches the rf-luk-style "wrote file but the hook command itself doesn't fire" failure.
Why probes matter. Three of the four hook-surface gaps caught in 2026 (Continue.dev
settings.jsonno-op, Windsurfhooks.jsonno-op, Aidermcp-server-command:no-op) had passing file-presence tests at the time the gap shipped. Only a runtime probe distinguishes "wrote the file" from "the platform actually consumes it." Adding a--probebranch for any new platform with a hook surface is non-negotiable.
If the platform can't be driven headlessly (most IDEs can't), document the manual probe steps in recipes/<p>.md so a maintainer can run them by hand.
Suppose Cleo (an imaginary new agent IDE) ships:
- A workspace rules system at
.cleo/rules/<name>.mdwith frontmattermode: auto | always | manual. - A workspace instruction file at
CLEO.md(workspace root, marker-block-friendly). - MCP support at
~/.cleo/mcp.jsonwith object-formatmcpServers. - A documented
preCommandhook event in~/.cleo/hooks.jsonmatched by tool name (regex). - No sub-agent primitive yet.
A complete Cleo PR touches:
node/resources/cleo-rules/
rafter.md
rafter-secure-design.md
rafter-code-review.md
rafter-skill-review.md
node/src/commands/agent/init.ts # installCleoHooks, installCleoMcp,
# installCleoRules, --with-cleo flag,
# detection of ~/.cleo, install branch,
# CLEO.md branch added to installGlobalInstructions
node/src/commands/agent/components.ts # cleo.hooks, cleo.rules, cleo.mcp
# ComponentSpecs + registry registration
node/src/commands/agent/verify.ts # checkCleo
node/tests/agent-components.test.ts # cleo.* in expected-id list
node/tests/platform-integration.test.ts # describe("Cleo (--with-cleo)") block
# + combined "All 8 platforms" updates
python/rafter_cli/resources/cleo-rules/ # mirror of node/resources/cleo-rules/
python/rafter_cli/commands/agent.py # _install_cleo_*, --with-cleo,
# _check_cleo, _install_global_instructions
# branch for CLEO.md
python/rafter_cli/commands/agent_components.py # _cleo_*, registry entries
python/tests/test_agent_init.py # TestInstallCleoRules
python/tests/test_agent_components.py # cleo.* in expected-id list
python/tests/test_agent_verify.py # TestCheckCleo
recipes/cleo.md # what gets installed, manual setup, verify
README.md # add Cleo to Supported Platforms
shared-docs/CLI_SPEC.md # add Cleo to verify check table
shared-docs/PLATFORM_PARITY_AUDIT.md # add Cleo row to the matrix
CHANGELOG.md # [Unreleased] entry
A reasonable Cleo PR title: feat(rf-XXXX): Cleo deep support — per-skill rules, AGENTS.md-style CLEO.md, hooks, MCP.
If --probe is being extended in the same PR, add a probeCleo function in verify.ts (Node) / _probe_cleo in Python that synthesizes Cleo's documented preCommand payload and asserts audit.jsonl. Otherwise, file a discovered-from bead noting the probe is a follow-up.
The contract above describes the steady state. A few platforms deviate, and those deviations are documented:
- OpenClaw is in the registry as a category mismatch (it's a personal-AI-assistant platform, not an in-IDE coding agent). The skill-install shape we ship doesn't match what OpenClaw actually consumes. Tracked under
rf-0ligfor an activity / users check before further investment. - Aider has no hook surface, no MCP, and no skill primitive. Its entire integration is a
read: [RAFTER.md]entry in.aider.conf.yml(rf-du2o). The "Verification gate" probe doesn't apply. - Windsurf has no hook surface (rf-0vr3). The integration is rules + AGENTS.md + MCP only.
- Continue.dev has no hook surface (rf-cia phase b). The integration is rules + MCP only.
- Gemini CLI requires the
gemini skills linkruntime-registration step on top of file-presence (rf-yit). Without it, the skill files exist on disk but the platform can't see them.
These exceptions belong in shared-docs/PLATFORM_PARITY_AUDIT.md matrix rows, not in this contract — the contract is the steady-state target; the audit doc is the running tally of where we deviate.
shared-docs/PLATFORM_PARITY_AUDIT.md— the per-platform state matrix.shared-docs/CLI_SPEC.md— the canonical CLI flag and output contract.recipes/— per-platform integration guides (must match installer reality).rafter agent verify [--probe] [--json]— the runtime contract that catches drift.