Skip to content
Closed
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
88 changes: 86 additions & 2 deletions config/shared/hooks/roborev-agent.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,95 @@
#!/usr/bin/env bash
# Feed agent-hook events to the RoboRev daemon running on this host.
# Start the full PR panel independently so the normal quick CI comment remains
# fast while the complete panel produces a second comment for the same PR.
# shellcheck disable=SC2016
set -euo pipefail

ROBOREV_BIN="${HOME}/.local/bin/roborev"
ROBOREV_BIN="${ROBOREV_BIN:-${HOME}/.local/bin/roborev}"
GH_BIN="${GH_BIN:-gh}"
GIT_BIN="${GIT_BIN:-git}"
JQ_BIN="${JQ_BIN:-jq}"
SERVER_ADDR="${ROBOREV_SERVER_ADDR:-127.0.0.1:7373}"
STATE_DIR="${ROBOREV_AGENT_FULL_REVIEW_STATE_DIR:-${HOME}/.roborev/agent-full-reviews}"

run_full_pr_review() {
local cwd="$1" repo_path repo_key config_file pr_json pr_number pr_state
local base_oid head_oid current_head merge_base lock_dir review_output comment_body

repo_path="$($GIT_BIN -C "$cwd" rev-parse --show-toplevel 2>/dev/null)" || return 0
repo_key="${repo_path//\//_}"
config_file="${repo_path}/.roborev.toml"
[ -f "$config_file" ] || return 0
grep -Eq '^\[review\.panels\.full\][[:space:]]*$' "$config_file" || return 0
pr_json="$(cd "$repo_path" && "$GH_BIN" pr view \
--json number,state,baseRefOid,headRefOid 2>/dev/null)" || return 0
pr_number="$(printf '%s' "$pr_json" | "$JQ_BIN" -r '.number // empty')"
pr_state="$(printf '%s' "$pr_json" | "$JQ_BIN" -r '.state // empty')"
base_oid="$(printf '%s' "$pr_json" | "$JQ_BIN" -r '.baseRefOid // empty')"
head_oid="$(printf '%s' "$pr_json" | "$JQ_BIN" -r '.headRefOid // empty')"
current_head="$($GIT_BIN -C "$repo_path" rev-parse HEAD)"
[[ $pr_number =~ ^[0-9]+$ ]] || return 0
[ "$pr_state" = "OPEN" ] && [ "$current_head" = "$head_oid" ] || return 0
[[ $base_oid =~ ^[0-9a-fA-F]{40}$ && $head_oid =~ ^[0-9a-fA-F]{40}$ ]] || return 0
merge_base="$($GIT_BIN -C "$repo_path" merge-base "$base_oid" "$head_oid" 2>/dev/null)" || return 0
[[ $merge_base =~ ^[0-9a-fA-F]{40}$ ]] || return 0

lock_dir="${STATE_DIR}/${repo_key}/${head_oid}.enqueued"
mkdir -p "$(dirname "$lock_dir")"
mkdir "$lock_dir" 2>/dev/null || return 0

if ! review_output="$($ROBOREV_BIN --server "$SERVER_ADDR" review --wait --quiet \
--repo "$repo_path" --sha "${merge_base}..${head_oid}" --panel full)"; then
rmdir "$lock_dir"
return 1
fi

pr_json="$(cd "$repo_path" && "$GH_BIN" pr view "$pr_number" \
--json state,headRefOid 2>/dev/null)" || return 0
[ "$(printf '%s' "$pr_json" | "$JQ_BIN" -r '.state // empty')" = "OPEN" ] || return 0
[ "$(printf '%s' "$pr_json" | "$JQ_BIN" -r '.headRefOid // empty')" = "$head_oid" ] || return 0

comment_body="$(printf '## RoboRev Full Panel (`%s`)\n\n%s' "${head_oid:0:12}" "$review_output")"
if ! (cd "$repo_path" && "$GH_BIN" pr comment "$pr_number" --body "$comment_body") >/dev/null; then
rmdir "$lock_dir"
return 1
fi
}

launch_full_pr_review() {
local cwd="$1" log_file
if [ "${ROBOREV_AGENT_HOOK_SYNC:-0}" = "1" ]; then
run_full_pr_review "$cwd"
return
fi
mkdir -p "$STATE_DIR"
log_file="${STATE_DIR}/full-review.log"
nohup "$0" --full-pr-review "$cwd" </dev/null >>"$log_file" 2>&1 &
}

if [ "${1:-}" = "--full-pr-review" ]; then
run_full_pr_review "${2:-}"
exit 0
fi

if [ ! -x "$ROBOREV_BIN" ]; then
exit 0
fi

exec "$ROBOREV_BIN" --server "127.0.0.1:7373" agent-hook run
INPUT="$(cat)"
HOOK_OUTPUT="$(printf '%s' "$INPUT" | "$ROBOREV_BIN" --server "$SERVER_ADDR" agent-hook run)" || true

if command -v "$JQ_BIN" >/dev/null 2>&1; then
EVENT_NAME="$(printf '%s' "$INPUT" | "$JQ_BIN" -r '.hook_event_name // .hookEventName // empty' 2>/dev/null || true)"
CWD="$(printf '%s' "$INPUT" | "$JQ_BIN" -r '.cwd // empty' 2>/dev/null || true)"
TOOL_COMMAND="$(printf '%s' "$INPUT" | "$JQ_BIN" -r '.tool_input.command // .tool_input.cmd // .toolInput.command // empty' 2>/dev/null || true)"
if [ -n "$CWD" ]; then
case "$EVENT_NAME:$TOOL_COMMAND" in
Stop:* | PostToolUse:*git\ push* | PostToolUse:*gh\ pr\ create*)
launch_full_pr_review "$CWD" || true
;;
esac
fi
fi

printf '%s\n' "$HOOK_OUTPUT"
118 changes: 115 additions & 3 deletions spec/roborev_hooks_spec.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,121 @@
#!/usr/bin/env bash
# shellcheck disable=SC2016,SC2329

Describe 'config/shared/hooks/roborev-agent.sh'
It 'uses the local RoboRev daemon'
When run grep -F '127.0.0.1:7373' "$PWD/config/shared/hooks/roborev-agent.sh"
The output should include '127.0.0.1:7373'
SCRIPT="$PWD/config/shared/hooks/roborev-agent.sh"

setup_hook() {
TEMP_HOME=$(mktemp -d)
mkdir -p "$TEMP_HOME/bin" "$TEMP_HOME/repo"
CALLS="$TEMP_HOME/calls"
: >"$CALLS"
printf '[review.panels.full]\nmembers = ["opencode"]\n' >"$TEMP_HOME/repo/.roborev.toml"

cat >"$TEMP_HOME/bin/roborev" <<'SH'
#!/usr/bin/env bash
if [[ " $* " == *" agent-hook run "* ]]; then
cat >/dev/null
printf '{"continue":true}\n'
elif [[ " $* " == *" review "* ]]; then
printf 'roborev %s\n' "$*" >>"$CALLS"
[ "${ROBOREV_REVIEW_FAIL:-0}" = "1" ] && exit 1
printf 'No issues found.\n'
fi
SH
cat >"$TEMP_HOME/bin/git" <<'SH'
#!/usr/bin/env bash
if [[ " $* " == *" --show-toplevel "* ]]; then
printf '%s\n' "$REPO_PATH"
elif [[ " $* " == *" merge-base "* ]]; then
printf '%s\n' "$BASE_OID"
else
printf '%s\n' "$HEAD_OID"
fi
SH
cat >"$TEMP_HOME/bin/gh" <<'SH'
#!/usr/bin/env bash
if [ "${1:-}" = "pr" ]; then
if [ "${2:-}" = "view" ]; then
printf '{"number":42,"state":"OPEN","baseRefOid":"%s","headRefOid":"%s"}\n' "$BASE_OID" "$HEAD_OID"
else
printf 'gh %s\n' "$*" >>"$CALLS"
fi
fi
SH
chmod +x "$TEMP_HOME/bin/roborev" "$TEMP_HOME/bin/git" "$TEMP_HOME/bin/gh"

export CALLS
export REPO_PATH="$TEMP_HOME/repo"
export BASE_OID=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
export HEAD_OID=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
export ROBOREV_BIN="$TEMP_HOME/bin/roborev"
export GIT_BIN="$TEMP_HOME/bin/git"
export GH_BIN="$TEMP_HOME/bin/gh"
export JQ_BIN=jq
export ROBOREV_AGENT_HOOK_SYNC=1
Comment thread
indent-zero[bot] marked this conversation as resolved.
export ROBOREV_AGENT_FULL_REVIEW_STATE_DIR="$TEMP_HOME/state"
}

cleanup_hook() {
rm -rf "$TEMP_HOME"
}

Before 'setup_hook'
After 'cleanup_hook'

It 'uses the local RoboRev daemon and preserves hook output'
Data '{"session_id":"s1","hook_event_name":"PostToolUse","cwd":"/tmp","tool_input":{"command":"true"}}'
When run bash "$SCRIPT"
The status should be success
The output should eq '{"continue":true}'
End

It 'starts one full panel after a pushed PR and posts a separate comment'
Data '{"session_id":"s1","hook_event_name":"PostToolUse","cwd":"__REPO__","tool_input":{"command":"git push origin HEAD"}}'
When run bash -c 'sed "s|__REPO__|$REPO_PATH|" | bash "$1"; printf "calls:\n"; cat "$CALLS"' _ "$SCRIPT"
The status should be success
The output should include '{"continue":true}'
The output should include '--sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb --panel full'
The output should include 'gh pr comment 42 --body ## RoboRev Full Panel'
End

It 'does not comment when the full panel fails'
export ROBOREV_REVIEW_FAIL=1
Data '{"session_id":"s1","hook_event_name":"Stop","cwd":"__REPO__"}'
When run bash -c 'sed "s|__REPO__|$REPO_PATH|" | bash "$1" >/dev/null || true; grep -c -- "gh pr comment" "$CALLS" || true' _ "$SCRIPT"
The status should be success
The output should include '0'
End

It 'preserves hook output when the full panel fails'
export ROBOREV_REVIEW_FAIL=1
Data '{"session_id":"s1","hook_event_name":"Stop","cwd":"__REPO__"}'
When run bash -c 'sed "s|__REPO__|$REPO_PATH|" | bash "$1"' _ "$SCRIPT"
The status should be success
The output should eq '{"continue":true}'
End

It 'runs the production background path'
unset ROBOREV_AGENT_HOOK_SYNC
Data '{"session_id":"s1","hook_event_name":"Stop","cwd":"__REPO__"}'
When run bash -c 'sed "s|__REPO__|$REPO_PATH|" | bash "$1" >/dev/null; for _ in {1..50}; do grep -q "gh pr comment" "$CALLS" && break; sleep 0.05; done; grep -c "gh pr comment" "$CALLS"' _ "$SCRIPT"
The status should be success
The output should eq '1'
End

It 'deduplicates repeated Stop events for the same PR head'
When run bash -c 'payload="{\"session_id\":\"s1\",\"hook_event_name\":\"Stop\",\"cwd\":\"$REPO_PATH\"}"; printf "%s" "$payload" | bash "$1" >/dev/null; printf "%s" "$payload" | bash "$1"; grep -c -- "--panel full" "$CALLS"' _ "$SCRIPT"
The status should be success
The output should include '{"continue":true}'
The output should include '1'
End

It 'skips repositories that do not define the full panel'
rm -f "$REPO_PATH/.roborev.toml"
Data '{"session_id":"s1","hook_event_name":"Stop","cwd":"__REPO__"}'
When run bash -c 'sed "s|__REPO__|$REPO_PATH|" | bash "$1" >/dev/null; grep -c -- "--panel full" "$CALLS" || true' _ "$SCRIPT"
The status should be success
The output should eq '0'
End

Describe 'config/factory/activate-settings.sh'
Expand Down
Loading