Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ Resolution order: `--config <path>` → `$RA_CONFIG` → `./ra.toml` →
| `git` | Runs native `git` with argv-safe arguments; ACP hosts use the terminal reverse-call with shell quoting. This path does not use RTK. |
| `gh` | Runs native GitHub CLI (`gh`) with argv-safe arguments; ACP hosts use the terminal reverse-call with shell quoting. This path does not use RTK. |
| `jq` | Runs jq filters against inline JSON or a JSON file with argv-safe stdin and a bounded JSON envelope. |
| `mergiraf` | Runs mergiraf merge, solve, and languages actions with argv-safe arguments and bounded JSON output. |
| `mise` | Runs mise tasks/tests with argv-safe arguments and bounded JSON output. |
| `just` | Runs just recipes with argv-safe arguments and bounded JSON output. |
| `wrkflw` | Runs wrkflw local GitHub Actions validation/execution with argv-safe arguments and bounded JSON output. |
Expand Down Expand Up @@ -230,18 +231,20 @@ native because it is structured code search rather than a plain shell
command. `bash` remains the fallback for project scripts, tests, and
one-off command pipelines.

Native `git` / `gh` / `jq` prioritize argv safety over RTK rewriting. If a
Native `git` / `gh` / `jq` / `mergiraf` prioritize argv safety over RTK rewriting. If a
high-volume native CLI command needs RTK output compression, run it
through `bash` instead so the existing RTK rewrite path can apply.
`jq` requires the system `jq` binary on `PATH`; missing jq returns
structured install guidance instead of an opaque spawn error.
Native `mise` / `just` / `wrkflw` are intended for test-first task and
workflow loops such as `mise run test`, `just test`, and local GitHub
Actions validation. They run local binaries with argv-safe arguments,
optional `cwd`/timeout controls, bounded JSON output, and structured
missing-binary guidance. These tools spawn locally even when an ACP host is
attached, so ACP terminal permission prompts do not wrap them; use
`[tools].builtin` and PreToolUse/PostToolUse hooks to govern availability.
`mergiraf` requires the system `mergiraf` binary on `PATH`; missing
mergiraf returns structured install guidance. Native `mise` / `just` /
`wrkflw` are intended for test-first task and workflow loops such as
`mise run test`, `just test`, and local GitHub Actions validation. They
run local binaries with argv-safe arguments, optional `cwd`/timeout
controls, bounded JSON output, and structured missing-binary guidance.
These tools spawn locally even when an ACP host is attached, so ACP
terminal permission prompts do not wrap them; use `[tools].builtin` and
PreToolUse/PostToolUse hooks to govern availability.
`webfetch_fetch` and `webfetch_crawl` run
`npm exec --yes --package=github:trotsky1997/webfetch-cli -- webfetch-cli`
under the hood; if `npm` is missing, the tools return structured install
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/mergiraf-native-support/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-02
63 changes: 63 additions & 0 deletions openspec/changes/mergiraf-native-support/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Design

## Context

Ra has an established pattern for native CLI wrapper tools: `git`, `gh`, `jq`, `mise`, `just`, and `wrkflw` all share the same shape — argv-safe process spawning, a bounded JSON result envelope, and structured missing-binary guidance. The `bash` tool handles arbitrary shell work but bypasses the catalog's structured schema and safety guarantees.

Mergiraf is a syntax-aware git merge driver. Agents working on git-heavy tasks (merge, rebase, cherry-pick) need to invoke `mergiraf merge`, `mergiraf solve`, and `mergiraf languages`. Today that works only through `bash`, which provides no structure and gives no actionable message when `mergiraf` is absent from PATH.

The `mergiraf` tool should follow exactly the same conventions as `jq` and `wrkflw`, staying narrow and making no attempt to replicate mergiraf's internal logic in Rust.

## Goals / Non-Goals

**Goals:**

- Wrap the `mergiraf` binary with argv-safe process spawning and no shell interpolation.
- Expose `merge` (git merge-driver invocation), `solve` (resolve conflicts in a file), and `languages` (list supported extensions) as structured sub-actions.
- Return a consistent JSON envelope: `ok`, `tool`, `action`, `exit_code`, `stdout`, `stderr`, `truncated`, and optional `error`.
- Return `error.kind: "missing_mergiraf"` with install guidance when the binary is absent.
- Bound all process output via `max_output_bytes`.
- Register via `default_builtins` so the tool obeys `[tools] builtin` allow-list semantics.
- Document in `README.md` and `spec/tools.md`; add focused integration tests.

**Non-Goals:**

- Do not implement mergiraf's tree-sitter parsing or conflict resolution logic in Rust.
- Do not expose every mergiraf flag in v1 — keep the schema narrow (the key flags for the merge-driver and solve paths).
- Do not remove `bash` as the fallback for advanced or experimental mergiraf invocations.
- Do not configure `.gitconfig` or `.gitattributes` on the user's behalf.

## Decisions

### Use the system `mergiraf` binary

Invoke `mergiraf` from PATH rather than vendoring or reimplementing it. This matches every other Ra native CLI wrapper and keeps the implementation small and correct. If the binary is missing, return `ok: false` with `error.kind: "missing_mergiraf"` and installation guidance (`cargo install mergiraf` or distro package).

Alternative considered: a Rust crate embedding mergiraf logic. Ruled out — broadens scope enormously and risks drift from upstream behavior.

### Three actions: `merge`, `solve`, `languages`

- `merge`: maps to `mergiraf merge <base> <ours> <theirs> [flags]` — the git merge-driver path. Accepts the three required file paths plus optional `--language`, `--compact`, and `--allow-parse-errors`.
- `solve`: maps to `mergiraf solve <file>` — resolves conflict markers in a file that already has them. Simpler invocation, single path argument.
- `languages`: maps to `mergiraf languages --gitattributes` — read-only, no file args.

Alternative considered: exposing arbitrary `args` to cover all mergiraf subcommands. Ruled out — reintroduces the shell-escape surface this tool is meant to avoid; advanced use stays in `bash`.

### JSON envelope consistent with existing wrappers

Fields: `ok: bool`, `tool: "mergiraf"`, `action: string`, `exit_code: int | null`, `stdout: string`, `stderr: string`, `truncated: bool`, `error?: {kind, message, install_hint}`. The `truncated` flag clips combined output before returning, following the `jq` and `webfetch` patterns.

### ACP hosts: spawn locally like `mise`/`just`/`wrkflw`

The tool spawns the local `mergiraf` binary directly even when an ACP host is attached, matching the pattern established by the other native CLIs. ACP terminal permission prompts do not wrap the spawn; `[tools].builtin` and PreToolUse/PostToolUse hooks remain the governance mechanism.

## Risks / Trade-offs

- Missing `mergiraf` on PATH → return structured install guidance; this is expected behavior for optional native tools.
- `merge` action writes to the `ours` file in-place (mergiraf's standard behavior) → callers must be aware the file is mutated; tests should use temp dirs.
- Narrow v1 flag surface may not cover `--compact` or `--allow-parse-errors` edge cases → these can be added in a follow-up change without breaking the envelope.
- mergiraf exits non-zero when conflicts remain unresolved → `ok: false` is correct here; callers should inspect `exit_code` to distinguish "conflicts remain" from "binary missing".

## Open Questions

None blocking implementation. The install guidance string (cargo vs. binary release) can be finalized during implementation by checking the upstream release method.
30 changes: 30 additions & 0 deletions openspec/changes/mergiraf-native-support/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Add Native mergiraf Support

## Why

Ra agents frequently trigger git operations (merge, rebase, cherry-pick) that produce conflict markers in files. Today agents fall back to generic `bash` to invoke `mergiraf` — forfeiting structured error handling, missing-binary guidance, and argv safety. Adding a native `mergiraf` tool brings syntax-aware merge conflict resolution into Ra's built-in tool catalog with the same reliability guarantees as `jq`, `git`, and other native wrappers.

## What Changes

- Add a native built-in `mergiraf` tool that exposes the core mergiraf subcommands (`merge`, `solve`, `languages`) as structured operations.
- Execute mergiraf through argv-safe process spawning — no shell interpolation.
- Return a stable JSON envelope with exit status, stdout, stderr, truncation state, and structured missing-binary guidance.
- Bound all returned output through `max_output_bytes`.
- Register the tool in `default_builtins`, document it in `README.md` and `spec/tools.md`, add focused integration tests.

## Capabilities

### New Capabilities

- `mergiraf-tool`: Native Ra built-in that wraps the `mergiraf` CLI for syntax-aware merge conflict resolution. Covers `merge` (git merge-driver invocation), `solve` (resolve conflicts in a file with existing markers), and `languages` (list supported extensions in gitattributes format). Returns a bounded JSON envelope consistent with existing native CLI wrapper conventions.

### Modified Capabilities

- `tools`: Add the `mergiraf` built-in tool entry to the existing tool catalog requirements.

## Impact

- Affected code: `src/tools/` (new `mergiraf.rs`), tool registration in `src/tools/mod.rs`, `src/config.rs` if tool metadata is config-driven.
- Affected docs: `README.md` built-in tools table, `spec/tools.md`.
- Runtime dependency: `mergiraf` binary must be on `PATH`; missing binary returns structured guidance, not an opaque spawn error.
- No breaking changes. `bash` remains available as the general fallback; existing allow-list semantics are unchanged.
106 changes: 106 additions & 0 deletions openspec/changes/mergiraf-native-support/specs/mergiraf-tool/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
## ADDED Requirements

### Requirement: mergiraf Tool Catalog Registration

Ra SHALL include `mergiraf` in the default built-in catalog when `[tools].builtin` is empty.

#### Scenario: Empty allow-list exposes mergiraf tool

- **WHEN** Ra builds the default built-in tool catalog with an empty `[tools].builtin` allow-list
- **THEN** the catalog includes `mergiraf`

#### Scenario: Non-empty allow-list remains exact

- **WHEN** Ra builds the default built-in tool catalog with `[tools].builtin` containing only `mergiraf`
- **THEN** the catalog contains `mergiraf` and omits unspecified tools

### Requirement: mergiraf merge Action

Ra SHALL provide a `mergiraf` tool with a `merge` action that invokes `mergiraf merge <base> <ours> <theirs>` with argv-safe process spawning and no shell interpolation.

#### Scenario: merge preserves argv boundaries for required files

- **WHEN** the agent calls `mergiraf` with `action: "merge"`, `base`, `ours`, and `theirs` paths
- **THEN** Ra invokes the `mergiraf` binary with `merge`, `base`, `ours`, and `theirs` as separate argv entries

#### Scenario: merge accepts optional language override

- **WHEN** the agent calls `mergiraf` with `action: "merge"` and `language: "java"`
- **THEN** Ra appends `--language` and `java` as separate argv entries

#### Scenario: merge accepts compact flag

- **WHEN** the agent calls `mergiraf` with `action: "merge"` and `compact: true`
- **THEN** Ra appends `--compact` to the argv

#### Scenario: merge result envelope on clean merge

- **WHEN** mergiraf exits with code 0
- **THEN** Ra returns JSON with `ok: true`, `action: "merge"`, `exit_code: 0`, `stdout`, `stderr`, and `truncated`

#### Scenario: merge result envelope on unresolved conflicts

- **WHEN** mergiraf exits with a non-zero code indicating remaining conflicts
- **THEN** Ra returns JSON with `ok: false`, the non-zero `exit_code`, `stdout`, `stderr`, and `truncated`

### Requirement: mergiraf solve Action

Ra SHALL provide a `mergiraf` tool with a `solve` action that invokes `mergiraf solve <file>` with argv-safe process spawning.

#### Scenario: solve passes file path as single argv entry

- **WHEN** the agent calls `mergiraf` with `action: "solve"` and a `file` path
- **THEN** Ra invokes the `mergiraf` binary with `solve` and the file path as separate argv entries

#### Scenario: solve result envelope on success

- **WHEN** `mergiraf solve` exits with code 0
- **THEN** Ra returns JSON with `ok: true`, `action: "solve"`, `exit_code: 0`, `stdout`, `stderr`, and `truncated`

### Requirement: mergiraf languages Action

Ra SHALL provide a `mergiraf` tool with a `languages` action that invokes `mergiraf languages --gitattributes` and returns the supported extension list.

#### Scenario: languages returns gitattributes format

- **WHEN** the agent calls `mergiraf` with `action: "languages"`
- **THEN** Ra invokes the `mergiraf` binary with `languages` and `--gitattributes` as separate argv entries
- **AND** Ra returns the output in the standard JSON envelope

### Requirement: mergiraf Missing Binary Guidance

Ra SHALL return a structured error when the `mergiraf` binary is not found on PATH, without breaking agent startup.

#### Scenario: Missing binary returns structured guidance

- **WHEN** the `mergiraf` binary is not found on PATH
- **THEN** Ra returns JSON with `ok: false`, no `exit_code`, and `error.kind: "missing_mergiraf"` with an `install_hint`

#### Scenario: Missing binary does not block catalog startup

- **WHEN** `mergiraf` is absent from PATH and Ra starts
- **THEN** Ra still registers the tool in the catalog and the startup succeeds

### Requirement: mergiraf Output Bounding

Ra SHALL bound all process output returned from `mergiraf` invocations via `max_output_bytes`.

#### Scenario: Output within bounds is returned in full

- **WHEN** combined stdout and stderr are within `max_output_bytes`
- **THEN** Ra returns the full output with `truncated: false`

#### Scenario: Output exceeding bounds is truncated

- **WHEN** combined stdout and stderr exceed `max_output_bytes`
- **THEN** Ra clips the output and returns `truncated: true`

### Requirement: mergiraf ACP Host Isolation

Ra SHALL spawn the `mergiraf` binary locally even when an ACP host is attached, matching the behavior of other native CLI wrappers.

#### Scenario: ACP host does not wrap mergiraf spawns

- **WHEN** an ACP host is attached
- **THEN** `mergiraf` still spawns the local binary directly
- **AND** ACP terminal permission prompts do not wrap those local spawns
15 changes: 15 additions & 0 deletions openspec/changes/mergiraf-native-support/specs/tools/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## MODIFIED Requirements

### Requirement: Native Task Workflow Tool Catalog

Ra SHALL include `mise`, `just`, `wrkflw`, and `mergiraf` in the default built-in catalog when `[tools].builtin` is empty.

#### Scenario: Empty allow-list exposes task workflow tools

- **WHEN** Ra builds the default built-in tool catalog with an empty `[tools].builtin` allow-list
- **THEN** the catalog includes `mise`, `just`, `wrkflw`, and `mergiraf`

#### Scenario: Non-empty allow-list remains exact

- **WHEN** Ra builds the default built-in tool catalog with `[tools].builtin` containing only `mise`
- **THEN** the catalog contains `mise` and omits unspecified tools
31 changes: 31 additions & 0 deletions openspec/changes/mergiraf-native-support/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Tasks

## 1. Tool Contract

- [x] 1.1 Define the `mergiraf` tool input schema with an `action` discriminant (`merge` | `solve` | `languages`), action-specific required fields (`base`/`ours`/`theirs` for merge, `file` for solve), optional flags (`language`, `compact`, `allow_parse_errors`), and `max_output_bytes`.
- [x] 1.2 Define the stable result envelope: `ok`, `tool`, `action`, `exit_code`, `stdout`, `stderr`, `truncated`, and optional `error` (`kind`, `message`, `install_hint`).

## 2. Implementation

- [x] 2.1 Add `MergirafTool` under `src/tools/` using argv-safe process spawning (no shell interpolation) and action-specific argument construction.
- [x] 2.2 Implement the `merge` action: spawn `mergiraf merge <base> <ours> <theirs>` with optional `--language`, `--compact`, and `--allow-parse-errors` argv entries.
- [x] 2.3 Implement the `solve` action: spawn `mergiraf solve <file>`.
- [x] 2.4 Implement the `languages` action: spawn `mergiraf languages --gitattributes`.
- [x] 2.5 Implement stdout/stderr budgeting using `max_output_bytes`, setting `truncated: true` when output is clipped.
- [x] 2.6 Return `error.kind: "missing_mergiraf"` with an `install_hint` when the binary is absent; do not panic or break catalog startup.
- [x] 2.7 Register `mergiraf` in `tools::default_builtins` and verify it obeys `[tools].builtin` allow-list semantics.

## 3. Documentation

- [x] 3.1 Add `mergiraf` row to the built-in tools table in `README.md`.
- [x] 3.2 Document the `mergiraf` schema, all three actions, result envelope, and error cases in `spec/tools.md`.
- [x] 3.3 Update `spec/ra.toml.example` and any init templates that enumerate built-in tools.

## 4. Tests

- [x] 4.1 Add catalog registration tests: default (empty allow-list includes `mergiraf`) and exact allow-list behavior.
- [x] 4.2 Add `merge` action tests: required argv construction, `--language` and `--compact` flag mapping, and argv boundary preservation.
- [x] 4.3 Add `solve` action tests: file path as single argv entry, success and non-zero exit envelopes.
- [x] 4.4 Add `languages` action tests: `--gitattributes` flag appended, output in envelope.
- [x] 4.5 Add tests for missing-binary guidance and output truncation.
- [x] 4.6 Run focused tool tests and the full Rust test suite (`cargo test`).
3 changes: 2 additions & 1 deletion spec/ra.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ api_key_env = "ANTHROPIC_API_KEY"
# git — run native git with argv-safe arguments
# gh — run native GitHub CLI with argv-safe arguments
# jq — run jq filters against inline JSON or a JSON file
# mergiraf — syntax-aware merge conflict resolution
# grep — structured text search
# glob — structured file discovery
# ls — structured directory listing
Expand All @@ -56,7 +57,7 @@ api_key_env = "ANTHROPIC_API_KEY"
# tmux_wait — block until a tmux event, hook expression, program result, or sleep timeout resolves
# An empty list (or omitted section) ships every built-in tool.
[tools]
# builtin = ["read", "write", "edit", "bash", "ast_grep", "git", "gh", "jq", "grep", "glob", "ls", "fuzzy", "apply_patch", "webfetch_fetch", "webfetch_crawl", "openspec", "tmux_run", "tmux_send", "tmux_capture", "tmux_kill", "tmux_listen", "tmux_wait"]
# builtin = ["read", "write", "edit", "bash", "ast_grep", "git", "gh", "jq", "mergiraf", "grep", "glob", "ls", "fuzzy", "apply_patch", "webfetch_fetch", "webfetch_crawl", "openspec", "tmux_run", "tmux_send", "tmux_capture", "tmux_kill", "tmux_listen", "tmux_wait"]
builtin = []

# ─── Skills (Claude Code / agentskills.io) ──────────────────────────
Expand Down
Loading
Loading