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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "revdiff",
"source": "./",
"description": "Review diffs, files, and documents with inline annotations in a TUI overlay",
"version": "0.7.6",
"version": "0.8.0",
"author": {
"name": "umputun"
}
Expand All @@ -18,7 +18,7 @@
"name": "revdiff-planning",
"source": "./plugins/revdiff-planning",
"description": "Automatic plan review with revdiff",
"version": "0.2.3",
"version": "0.3.0",
"author": {
"name": "umputun"
}
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "revdiff",
"version": "0.7.6",
"version": "0.8.0",
"description": "Review diffs, files, and documents with inline annotations in a TUI overlay",
"author": {
"name": "umputun",
Expand Down
10 changes: 7 additions & 3 deletions .claude-plugin/skills/revdiff/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,17 @@ The script outputs structured fields:

### Step 2: Launch Review

Run the launcher script:
Run the launcher through the override-chain resolver:

```bash
${CLAUDE_SKILL_DIR}/scripts/launch-revdiff.sh [base] [against] [--staged] [--only=file1] [--all-files] [--exclude=prefix]
"$("${CLAUDE_SKILL_DIR}/scripts/resolve-launcher.sh" launch-revdiff.sh "${CLAUDE_PLUGIN_DATA}")" [base] [against] [--staged] [--only=file1] [--all-files] [--exclude=prefix]
```

**IMPORTANT — long-running command**: The launcher blocks until the user finishes reviewing in the TUI overlay, which can exceed the default bash tool timeout on many harnesses. Set the bash timeout parameter to the **maximum your harness allows** (e.g. 1800000 or higher on OpenCode). Do NOT use `run_in_background` for this — background-task handling is unreliable for interactive TUI launchers (processes may be killed unprompted, and polling loops can leave the session idle after the review finishes). If the review outlasts the timeout cap, the fallback in Step 3 handles it.
The resolver and launcher MUST run in the same bash invocation — the resolver runs as a sub-shell substitution so the resolved path is consumed immediately as the executable. The resolver checks `user → bundled` (see `references/install.md` for override paths) and prints the first-found absolute path. Fall-through to the bundled launcher is the default when no overrides exist.

**Failure mode**: if the resolver fails (no launcher in any layer), the command substitution produces an empty string and bash reports `: command not found` with exit 127. The resolver's stderr (`error: launcher not found in override chain: launch-revdiff.sh`) is preserved on the same output stream — check it to confirm the override path is correct (executable bit set, file present in one of the two layers).

**IMPORTANT — long-running command**: The launcher blocks until the user finishes reviewing in the TUI overlay, which can exceed the default bash tool timeout on many harnesses. Set the bash timeout parameter to the **maximum your harness allows** (e.g. 1800000 or higher on OpenCode). The resolver itself returns in milliseconds — the timeout cap applies to the launcher only. Do NOT use `run_in_background` for this — background-task handling is unreliable for interactive TUI launchers (processes may be killed unprompted, and polling loops can leave the session idle after the review finishes). If the review outlasts the timeout cap, the fallback in Step 3 handles it.

The script:
- Detects available terminal (tmux → Zellij → kitty → wezterm/Kaku → cmux → ghostty → iTerm2 → Emacs vterm)
Expand Down
30 changes: 30 additions & 0 deletions .claude-plugin/skills/revdiff/references/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,33 @@ Automatically opens revdiff when Claude exits plan mode for interactive annotati
```bash
/plugin install revdiff-planning@umputun-revdiff
```

### Overrides

The diff-review skill resolves `launch-revdiff.sh` through a two-layer chain (first-found wins). Drop your own launcher into the user layer to customize how revdiff opens (separate window, alternate split layout, custom terminal multiplexer) without forking the plugin.

| Layer | Path | Scope |
|---|---|---|
| User | `${CLAUDE_PLUGIN_DATA}/scripts/launch-revdiff.sh` | every project (per-user, lives under `~/.claude/plugins/data/<plugin-id>/`) |
| Bundled | `${CLAUDE_SKILL_DIR}/scripts/launch-revdiff.sh` | default — ships with the plugin, used when no override is present |

There is no project-level (`.claude/...`) override layer by design: the resolver is shared with the `revdiff-planning` hook, which fires automatically on every `ExitPlanMode`, and a repo-controlled executable layer would let an untrusted repo run arbitrary code on routine Claude actions. The diff-review skill keeps the same two-layer shape for symmetry.

The override file must be **executable** (`chmod +x`). A non-executable file in the user layer is treated as absent — the resolver falls through to the bundled default rather than erroring. Using `chmod -x` is a quick way to disable an override without deleting the file.

To start from the bundled launcher as a template:

```bash
mkdir -p "${CLAUDE_PLUGIN_DATA}/scripts"
cp "${CLAUDE_SKILL_DIR}/scripts/launch-revdiff.sh" "${CLAUDE_PLUGIN_DATA}/scripts/launch-revdiff.sh"
chmod +x "${CLAUDE_PLUGIN_DATA}/scripts/launch-revdiff.sh"
# edit "${CLAUDE_PLUGIN_DATA}/scripts/launch-revdiff.sh" to taste
```

**Customizing the launcher**: production overrides should start from a copy of the bundled `launch-revdiff.sh` and modify only the terminal branch you care about. The bundled launcher already handles the tricky parts — shell-quoting positional args via the `sq()` helper, output-file lifecycle, sentinel polling, env-var propagation. Writing a thin wrapper from scratch is fragile: naive use of `$*` or `$@` inside `sh -c "..."` does not preserve arguments containing spaces, quotes, or globs.

For example, to open revdiff in a fresh kitty window instead of an overlay, copy the bundled launcher and replace the existing `kitty @ launch` overlay block with a `kitty --detach --title "revdiff"` invocation, reusing the existing `$REVDIFF_CMD` variable (which is already correctly quoted) — do **not** rebuild the command line from `$*`.

The override receives the same positional arguments the bundled launcher does (`[base] [against] [--staged] [--only=file1] [--all-files] [--exclude=prefix]`). Read stdout into the calling skill the same way the bundled launcher does — print captured annotations to stdout on exit.

**Failure mode**: if the resolver finds no launcher in any layer (user / bundled), the skill's command substitution produces an empty string and bash reports `: command not found` with exit 127. The resolver's stderr (`error: launcher not found in override chain: launch-revdiff.sh`) is preserved — check it to confirm the override file is present and executable in one of the two layers above.
33 changes: 33 additions & 0 deletions .claude-plugin/skills/revdiff/scripts/resolve-launcher.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# resolve launcher script through two-layer override chain (user → bundled)
# usage: resolve-launcher.sh <launcher-name> [data-dir]
# outputs absolute path of the first-found executable launcher
#
# No project (.claude/...) layer by design: this script can be invoked
# automatically by hooks (e.g. revdiff-planning's ExitPlanMode), and a
# repo-controlled executable layer would let an untrusted repo run
# arbitrary code on routine Claude actions. User layer + bundled only.
set -euo pipefail

name="${1:-}"
if [ -z "$name" ]; then
echo "error: usage: resolve-launcher.sh <launcher-name> [data-dir]" >&2
exit 1
fi
data_dir="${2:-${CLAUDE_PLUGIN_DATA:-}}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

abspath() { (cd "$(dirname "$1")" && printf '%s/%s\n' "$(pwd)" "$(basename "$1")"); }

# user layer
if [ -n "$data_dir" ] && [ -f "$data_dir/scripts/$name" ] && [ -x "$data_dir/scripts/$name" ]; then
abspath "$data_dir/scripts/$name"
exit 0
fi
# bundled default
if [ -f "$SCRIPT_DIR/$name" ] && [ -x "$SCRIPT_DIR/$name" ]; then
abspath "$SCRIPT_DIR/$name"
exit 0
fi
echo "error: launcher not found in override chain: $name" >&2
exit 1
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ node_modules/
# debug
__debug_bin*

# python bytecode
__pycache__/
*.pyc

# ralphex progress logs
.ralphex/progress/
.pi/
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ TUI for reviewing diffs, files, and documents with inline annotations, built wit
- `install.md` — installation methods and plugin setup
- `config.md` — options, colors, chroma styles
- `usage.md` — examples, key bindings, output format
- **Launcher override chain**: both Claude plugins resolve their launcher script via `resolve-launcher.sh` through `user → bundled` layers (first executable wins). User layer is `${CLAUDE_PLUGIN_DATA}/scripts/<launcher>`. There is **no project-level (`.claude/...`) layer by design** — the planning hook fires automatically on `ExitPlanMode` in any repo, and a repo-controlled executable layer would let an untrusted repo run arbitrary code on routine Claude actions. The diff-review resolver keeps the same two-layer shape for symmetry (single mental model, shared resolver). The override chain is **Claude-only** — pi (no `CLAUDE_PLUGIN_DATA` in runtime) and codex (no plugin-data path) ignore it; codex users edit `~/.codex/skills/revdiff/scripts/launch-revdiff.sh` directly to customize.
- **Testing locally**: `claude --plugin-dir .claude-plugin` loads the diff-review skill from this checkout without going through the marketplace; `claude --plugin-dir plugins/revdiff-planning` does the same for the planning hook. Use `/reload-plugins` to pick up file edits mid-session.

## Codex Skills
- Codex skills live at `plugins/codex/skills/` — two skills: `revdiff` (diff review) and `revdiff-plan` (plan review via last Codex assistant message)
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ The plugin includes built-in reference documentation and can answer questions ab

The plugin supports the full review loop: annotate → plan → fix → re-review until no more annotations remain.

**Custom launchers:** both Claude plugins resolve their launcher script through a two-layer chain (user → bundled), so you can drop a custom launcher at `${CLAUDE_PLUGIN_DATA}/scripts/launch-revdiff.sh` without forking the plugin. There is no project-level (`.claude/...`) override layer by design — the planning hook fires automatically on every `ExitPlanMode`, and a repo-controlled executable layer would let an untrusted repo run arbitrary code on routine Claude actions. See `.claude-plugin/skills/revdiff/references/install.md` for the diff-review skill and [plugins/revdiff-planning/README.md](plugins/revdiff-planning/README.md) for the planning hook.

### Plan Review Plugin

A separate `revdiff-planning` plugin automatically opens revdiff when Claude exits plan mode, letting you annotate the plan before approving it. If you add annotations, Claude revises the plan and asks again — looping until you're satisfied.
Expand Down Expand Up @@ -194,7 +196,7 @@ pi install https://github.com/umputun/revdiff
- Requires the `revdiff` binary on `PATH`
- Set `REVDIFF_BIN=/absolute/path/to/revdiff` if pi can't find the binary
- Same-terminal mode is the default: pi temporarily suspends, revdiff takes over the terminal, and pi resumes on exit
- Optional overlay mode (`--pi-overlay` or `REVDIFF_PI_MODE=overlay`) reuses the existing `launch-revdiff.sh` script from the Claude plugin integration
- Optional overlay mode (`--pi-overlay` or `REVDIFF_PI_MODE=overlay`) reuses the existing `launch-revdiff.sh` script from the Claude plugin integration; pi invokes the bundled script directly and does not honor Claude-plugin overrides (`CLAUDE_PLUGIN_DATA` is not set in the pi runtime)
- Optional post-edit reminders are available via `/revdiff-reminders on` and suggest running `/revdiff` or `/revdiff-rerun` after agent edits
- In the repo, the pi-specific resources live under `plugins/pi/` to keep harness integrations clearly separated

Expand Down
Loading