plugins/lisa/skills/cross-pollinate/SKILL.md
Detect a host project's locally-authored coding-agent definitions (skills, subagents, rules, commands, hooks, MCP) and make each available in the formats of the OTHER agents the project supports, as declared in .lisa.config.json. Any-to-any, provenance-tracked, idempotent, never clobbers hand-edited output.
npx skillsauth add codyswanngt/lisa cross-pollinateInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A host project that installs Lisa may hand-author a definition for one coding
agent — a .claude/skills/foo, a .cursor/rules/bar.mdc, an MCP server only
Claude sees. /lisa:cross-pollinate detects those locally-authored definitions
and regenerates each in the formats of the other agents the project supports,
so the whole fleet stays in parity with what a developer wrote for any single one.
any agent's format -> Claude-format IR -> every OTHER configured agent
Claude format is the canonical intermediate representation (IR) because every Lisa generator already sources from it. So translation is always: normalize the detected definition up to Claude-format IR, then fan that IR out to each target agent using the documented mappings below. Never translate format A → B directly — that multiplies error and loses fidelity on every hop.
The agents a project supports come from .lisa.config.json harness
(claude | codex | cursor | agy | copilot | opencode | both | fleet;
all is an alias for fleet). Only emit to agents that harness includes.
.lisa/cross-pollination.lock.json (committed) is the source of truth for what
this skill generated. Location heuristics cannot work: a generated Cursor
rule sits in the same .cursor/rules/ directory as a hand-authored one. The
lockfile is the only reliable way to enforce the four invariants:
target is NEVER treated as a source on
the next run. Without this, today's output becomes tomorrow's input and
translations ping-pong forever.generatedHash, a human edited it. Stop and report;
do not overwrite. They may have intentionally diverged, or want to adopt the
edit as a new source.lisa apply.)Lockfile shape:
{
"version": 1,
"entries": {
"skill:security-review": {
"source": { "agent": "claude", "path": ".claude/skills/security-review", "hash": "…" },
"targets": [ { "agent": "codex", "path": ".claude/skills/security-review/agents/openai.yaml", "generatedHash": "…" } ]
}
}
}
The logicalId is <kind>:<name> — stable across formats. It links a source to
its targets and detects collisions: the same logicalId authored
independently in two agents (neither generated). On a collision, NEVER
auto-translate over either side — report it and let the human pick the source of
truth.
The deterministic core (scan, provenance, loop/GC/drift/conflict detection, and the skill + MCP emitters) is a bundled engine. Always run it first — it does the safe, mechanical work and tells you exactly what is left for you to translate by hand.
# dry-run (default): report only, writes nothing
node "${CLAUDE_PLUGIN_ROOT}/scripts/cross-pollinate.mjs" "$PROJECT_ROOT" --json
# apply the deterministic emits + update the lockfile
node "${CLAUDE_PLUGIN_ROOT}/scripts/cross-pollinate.mjs" "$PROJECT_ROOT" --write
In the Lisa source repo itself the path is
node plugins/lisa/scripts/cross-pollinate.mjs (built) or
node plugins/src/base/scripts/cross-pollinate.mjs (source).
The JSON report has four lists you act on:
emits — already handled by --write (skills, MCP). Verify, don't redo.conflicts — STOP. Report each to the user; do not translate.orphans — GC'd by --write. Confirm the removals look right.pending — your job. Definitions whose target format the engine does not
emit deterministically. Translate each per the mappings below, then record
provenance (see "Recording provenance").pending translation)Normalize the source to Claude-format IR first, then emit per target:
.claude/rules/<name>.md; Cursor reads .cursor/rules/<name>.mdc with YAML
frontmatter (description from the first H1, alwaysApply: true) and intra-rule
link extensions rewritten. You do not translate these — verify the engine's
emits.pending): project rules are delivered via
AGENTS.md, not a per-rule file. MERGE the rule content into AGENTS.md under a
stable marked section rather than writing a separate file (so re-runs replace,
not append). agy never gets a duplicate rule file (rules-once).pending): .github/copilot-instructions.md (inline). Merge into a
marked section, don't scatter.agents/<name>.md (or .claude/agents/).agents/<name>.agent.md (note the .agent.md suffix)..claude/commands/…).PreToolUse→preToolUse/beforeSubmitPrompt,
UserPromptSubmit→userPromptSubmitted, Stop→agentStop/stop, …).SessionStart/SubagentStart — only PreToolUse (e.g.
block-no-verify) is portable. Everything else: dropped — unsupported.codex exec — install into
~/.codex/hooks.json, not a bundled file.SubagentStart/empty-matcher
entry is present — drop that event wholesale..mcp.json. Cursor: .cursor/mcp.json (same JSON shape).mcpServers object in the plugin manifest (bundled
.mcp.json is ignored). Codex: TOML / .codex-plugin pointer.
agy/OpenCode: user-global installer / .opencode. The engine reports these
as pending; translate per the target's documented shape or report dropped.pending itemsAfter you write a target by hand, it MUST be recorded in
.lisa/cross-pollination.lock.json or the next run will treat it as a source
(loop) or fail to GC it. Re-run the engine after staging your translated files —
on its next pass it adopts and hashes any target already referenced — OR add the
entry yourself following the lockfile shape above (compute generatedHash as the
sha256 of the written file/dir). Prefer the engine; only hand-edit the lock if
the engine cannot infer the mapping.
Report, concisely:
.lisa/cross-pollination.lock.json was updated and the working tree
otherwise contains only intended changesharness includes.development
Prepare a machine — a fresh laptop or a throwaway container — to run coding agents, before any repository exists. Detects which of Lisa's supported agents (Claude Code, Codex, Cursor, OpenCode, Antigravity, Copilot) are already installed, asks which credential manager the machine uses (Bitwarden, 1Password, Doppler, Vault, AWS, or none), and installs only what is missing, each by its vendor's own preferred method. Idempotent, headless by default, and emits a Dockerfile for a spin-up/spin-down environment. Run it on a new machine, in a container, or before cloning anything.
tools
Provision and verify a remote execution environment for a host project — Codex Cloud today, other remote surfaces as they are added. Generates a repository-owned setup script that installs the declared toolchain, materializes secrets through lisa-secrets-access, and runs the project's own hook. Provisions by API where one exists, by driving the vendor console where one does not, and by emitting exact config otherwise — then proves the result with the same read-back regardless of which tier did the work. Use before dispatching any work with executionEnv.
tools
Bring a developer's machine in line with the toolchain the project declares. Reports every tool in remoteEnv.tools that is missing, outdated, or unpinned for this platform, and installs the missing ones into ~/.local/bin from the same pinned, checksummed entries the remote surfaces use — but only when asked. Same manifest, same pins, same installers as lisa-setup-remote-env; what differs is consent and that the pin is a floor rather than an equality. Run it on a fresh checkout, after a manifest change, or when a tool fails at the moment of use.
tools
Route one unit of work to a remote execution surface. Reads the executionEnv parameter (local by default, codex-cloud or claude-web today), verifies the environment is provisioned and bound to this repository, submits a thin skill invocation, records the task identifier to .lisa/remote-dispatch.json, and exits without polling. Routing only — the remote runs the identical skill from the identical repository. Composable and inline: other skills invoke it via the Skill tool rather than users calling it directly.