Skip to content

feat(guard): support Codex CLI and Cursor alongside Claude Code#164

Merged
Pama-Lee merged 1 commit into
mainfrom
feat/guard-codex-cursor
Jul 11, 2026
Merged

feat(guard): support Codex CLI and Cursor alongside Claude Code#164
Pama-Lee merged 1 commit into
mainfrom
feat/guard-codex-cursor

Conversation

@Pama-Lee

Copy link
Copy Markdown
Collaborator

ordo guard only spoke Claude Code's PreToolUse hook protocol. Both other mainstream coding agents now ship an equivalent pre-shell-execution hook — Codex CLI's PreToolUse is protocol-identical to Claude Code's, Cursor's beforeShellExecution uses its own flat shape — so the policy engine itself was already agent-agnostic; only the wire format at the edges needed adapting.

What changed

  • New Agent enum (claude / codex / cursor, default claude — zero behavior change for existing Claude Code users, verified: every pre-existing test passes unchanged).
  • ordo guard hook --agent <agent>: parses each agent's event shape into one canonical HookEvent, so a policy written once behaves identically everywhere it's reachable (Cursor events synthesize tool: "Bash" since it only sees shell). Emits each agent's native decision envelope:
    • Claude Code / Codex CLI share hookSpecificOutput (PASS → silent stdout, agent's own permission flow applies).
    • Cursor's schema documents allow/deny/ask but no "no opinion" outcome, so its PASS maps to an explicit allow rather than relying on undocumented empty-stdout behavior.
  • ordo guard init --agent <a,b,c> (repeatable or comma-separated, default claude): registers each selected agent's hook in its own config file — .claude/settings*.json, .codex/hooks.json, .cursor/hooks.json. Claude/Codex share settings.rs's existing merge logic (their hooks.json shape is structurally identical, confirmed against both agents' docs); Cursor gets a dedicated merge function for its flatter shape (top-level "version", no nested "hooks"/"type" wrapper). JSON output keeps the original single-object "hook" field (Claude's registration specifically) for backward compatibility, plus a new "hooks" array covering all selected agents.
  • Docs: README, the npm package README, and the en/zh guard.md docs page — all previously said "Claude Code" as the scope.

Verification

  • Protocol details (exact JSON schemas, config file locations/shapes) pulled from the official Cursor and Codex CLI docs, not guessed.
  • cargo test -p ordo-cli — 47 tests, including new unit tests (event parsing/conversion, per-agent decision envelopes, registration idempotency for both new config shapes) and new end-to-end integration tests through the compiled binary (tests/guard_hook.rs): multi-agent init writes the right shape per agent and is idempotent across re-runs, hook --agent codex/--agent cursor parse + decide + emit correctly for deny/ask/allow/pass.
  • Manually smoke-tested the compiled binary: guard init --agent claude,codex,cursor in a scratch repo, inspected the three resulting config files, ran guard hook through each agent's protocol by hand and confirmed the JSON output.
  • cargo clippy -p ordo-cli --all-targets and cargo fmt --check clean; cargo check --workspace clean.

Known upstream limitations (documented, not something this PR can work around)

  • Codex CLI's PreToolUse currently only fires for Bash (Read/Write/Edit/MCP calls don't trigger it yet upstream).
  • Cursor's beforeShellExecution only ever sees shell commands — no file-edit hook exists, so file_path-based rules are unreachable there.

ordo guard only spoke Claude Code's PreToolUse hook protocol. Both other
mainstream coding agents now ship an equivalent pre-shell-execution hook
(Codex CLI's PreToolUse is protocol-identical to Claude Code's; Cursor's
beforeShellExecution uses its own flat event/decision shape), so the
policy engine itself was already agent-agnostic — only the wire format
at the edges needed adapting.

- New Agent enum (claude/codex/cursor, default claude — zero behavior
  change for existing Claude Code users).
- ordo guard hook --agent <agent>: parses each agent's event shape into
  one canonical HookEvent, so a policy written once behaves identically
  everywhere it's reachable. Emits each agent's native decision envelope
  (Claude/Codex share hookSpecificOutput; Cursor's beforeShellExecution
  schema has no documented 'no opinion' outcome, so its PASS maps to an
  explicit allow rather than silence).
- ordo guard init --agent <a,b,c> (repeatable or comma-separated,
  default claude): registers each selected agent's hook in its own
  config file. Claude/Codex share settings.rs's merge logic (their
  hooks.json shape is structurally identical); Cursor gets a dedicated
  merge function for its flatter shape. JSON output keeps the original
  single-object "hook" field (Claude's registration specifically) for
  backward compatibility, plus a new "hooks" array covering all of
  them.

Verified end-to-end through the compiled binary across all three agents
(init writing the right config shape, hook parsing + emitting the right
protocol for allow/deny/ask/pass) — not just unit-level. cargo test/
clippy/fmt clean; existing Claude Code tests pass unchanged.

Docs: README, npm package README, and the en/zh guard docs page updated
— the docs previously stated 'Claude Code' as the scope.
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ordo_docs Ready Ready Preview, Comment Jul 11, 2026 10:16am

@Pama-Lee
Pama-Lee merged commit 852aebe into main Jul 11, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants