diff --git a/config/roborev/config.template.toml b/config/roborev/config.template.toml index a2075d682..722f9c66b 100644 --- a/config/roborev/config.template.toml +++ b/config/roborev/config.template.toml @@ -4,4 +4,12 @@ enabled = true poll_interval = "1m" # Never cancel a running CI review just to post an early result. batch_timeout = "0" -repos = ["__ROBOREV_CI_REPOS__"] +repos = ["__ROBOREV_REPOS__"] + +[agent_hook] +# Surface any open failed review without automatically invoking a fixer. +# Keep turn and commit triggers explicitly disabled. +turn_threshold = 0 +commit_threshold = 0 +failed_review_threshold = 1 +instruction = "Inspect open RoboRev findings read-only. Report proposed changes and ask the user for explicit approval. Do not edit files, run a fixer, commit, or close reviews before approval." diff --git a/config/roborev/hydrate.sh b/config/roborev/hydrate.sh index f615b7d7f..30e62c343 100644 --- a/config/roborev/hydrate.sh +++ b/config/roborev/hydrate.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Hydrate roborev config from .env secrets -# ROBOREV_CI_REPOS: comma-separated list of repos (e.g. "org/repo1,org/repo2") +# ROBOREV_REPOS: comma-separated list of repos (e.g. "org/repo1,org/repo2") # shellcheck source=/dev/null set -euo pipefail @@ -15,29 +15,69 @@ if [ -f "$ENV_FILE" ]; then set +a fi -ROBOREV_CI_REPOS="${ROBOREV_CI_REPOS:-}" +ROBOREV_REPOS="${ROBOREV_REPOS:-}" +if [ -z "$ROBOREV_REPOS" ] && [ -n "${ROBOREV_CI_REPOS:-}" ]; then + ROBOREV_REPOS="$ROBOREV_CI_REPOS" + echo "Warning: ROBOREV_CI_REPOS is deprecated; rename it to ROBOREV_REPOS" >&2 +fi -if [ -z "$ROBOREV_CI_REPOS" ]; then - echo "Warning: ROBOREV_CI_REPOS not set, skipping roborev hydration" >&2 +if [ -z "$ROBOREV_REPOS" ]; then + echo "Warning: ROBOREV_REPOS not set, skipping roborev hydration" >&2 exit 0 fi # Build TOML array from comma-separated list -IFS=',' read -ra REPOS <<<"$ROBOREV_CI_REPOS" +IFS=',' read -ra REPOS <<<"$ROBOREV_REPOS" +VALID_REPOS=() TOML_REPOS="" -for i in "${!REPOS[@]}"; do - repo="$(echo "${REPOS[$i]}" | xargs)" - if [ "$i" -gt 0 ]; then +for value in "${REPOS[@]}"; do + repo="$(echo "$value" | xargs)" + owner="${repo%%/*}" + name="${repo#*/}" + if [[ ! $repo =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || + [ "$owner" = "." ] || [ "$owner" = ".." ] || + [ "$name" = "." ] || [ "$name" = ".." ]; then + echo "Warning: ignoring invalid RoboRev repository: $repo" >&2 + continue + fi + if [ "${#VALID_REPOS[@]}" -gt 0 ]; then TOML_REPOS+=", " fi TOML_REPOS+="\"${repo}\"" + VALID_REPOS+=("$repo") done +if [ "${#VALID_REPOS[@]}" -eq 0 ]; then + echo "Warning: ROBOREV_REPOS contains no valid repositories, skipping roborev hydration" >&2 + exit 0 +fi + mkdir -p "$CONFIG_DIR" @sed@ \ - -e "s|\"__ROBOREV_CI_REPOS__\"|${TOML_REPOS}|g" \ + -e "s|\"__ROBOREV_REPOS__\"|${TOML_REPOS}|g" \ "$TEMPLATE" >"$CONFIG" chmod 600 "$CONFIG" echo "Generated roborev config at $CONFIG" >&2 + +ROBOREV_BIN="${HOME}/.local/bin/roborev" +if [ ! -x "$ROBOREV_BIN" ]; then + echo "Warning: RoboRev binary not found; skipping Git hook installation" >&2 + exit 0 +fi + +for repo in "${VALID_REPOS[@]}"; do + repo_path="${HOME}/ghq/github.com/${repo}" + if [ ! -d "$repo_path" ]; then + echo "Warning: RoboRev repository checkout not found: $repo_path" >&2 + continue + fi + + if ! ( + cd "$repo_path" + "$ROBOREV_BIN" install-hook --binary "$ROBOREV_BIN" + ); then + echo "Warning: failed to install RoboRev Git hook in $repo_path" >&2 + fi +done diff --git a/spec/roborev_hydrate_spec.sh b/spec/roborev_hydrate_spec.sh index b76921f77..31050faa4 100644 --- a/spec/roborev_hydrate_spec.sh +++ b/spec/roborev_hydrate_spec.sh @@ -22,15 +22,15 @@ When run bash -c "grep 'TEMPLATE=' '$SCRIPT'" The output should include '@template@' End -It 'sources ROBOREV_CI_REPOS from dotfiles .env' -When run bash -c "grep -E 'ROBOREV_CI_REPOS|ENV_FILE=' '$SCRIPT'" -The output should include 'ROBOREV_CI_REPOS' +It 'sources ROBOREV_REPOS from dotfiles .env' +When run bash -c "grep -E 'ROBOREV_REPOS|ENV_FILE=' '$SCRIPT'" +The output should include 'ROBOREV_REPOS' The output should include 'dotfiles/.env' End -It 'substitutes __ROBOREV_CI_REPOS__ placeholder via sed' -When run bash -c "grep '__ROBOREV_CI_REPOS__' '$SCRIPT'" -The output should include '__ROBOREV_CI_REPOS__' +It 'substitutes __ROBOREV_REPOS__ placeholder via sed' +When run bash -c "grep '__ROBOREV_REPOS__' '$SCRIPT'" +The output should include '__ROBOREV_REPOS__' End End @@ -52,19 +52,35 @@ End Describe 'hydration' setup_hydrate() { TEMP_HOME=$(mktemp -d) - mkdir -p "$TEMP_HOME/dotfiles" + mkdir -p \ + "$TEMP_HOME/dotfiles" \ + "$TEMP_HOME/.local/bin" \ + "$TEMP_HOME/ghq/github.com/org/repo1" \ + "$TEMP_HOME/ghq/github.com/org/repo2" cat >"$TEMP_HOME/template.toml" <<'TOML' [ci] enabled = true poll_interval = "5m" -repos = ["__ROBOREV_CI_REPOS__"] +repos = ["__ROBOREV_REPOS__"] + +[agent_hook] +turn_threshold = 0 +commit_threshold = 0 +failed_review_threshold = 1 +instruction = "Inspect open RoboRev findings read-only. Report proposed changes and ask the user for explicit approval. Do not edit files, run a fixer, commit, or close reviews before approval." TOML cat >"$TEMP_HOME/dotfiles/.env" <<'ENV' -ROBOREV_CI_REPOS=org/repo1,org/repo2 +ROBOREV_REPOS=../evil,foo/..,org/repo1,org/repo2 ENV + cat >"$TEMP_HOME/.local/bin/roborev" <<'BASH' +#!/usr/bin/env bash +printf '%s|%s\n' "$PWD" "$*" >>"${HOME}/roborev-hook.log" +BASH + chmod +x "$TEMP_HOME/.local/bin/roborev" + PREPROCESSED_SCRIPT="$TEMP_HOME/hydrate.sh" sed \ -e 's|@sed@|sed|g' \ @@ -85,7 +101,28 @@ When run bash -c 'HOME="'"$TEMP_HOME"'" bash "'"$PREPROCESSED_SCRIPT"'" >/dev/nu The status should be success The output should include '"org/repo1"' The output should include '"org/repo2"' -The output should not include '__ROBOREV_CI_REPOS__' +The output should not include '"../evil"' +The output should not include '"foo/.."' +The output should not include '__ROBOREV_REPOS__' +End + +It 'hydrates the complete approval gate' +When run bash -c 'HOME="'"$TEMP_HOME"'" bash "'"$PREPROCESSED_SCRIPT"'" >/dev/null 2>&1; cat "'"$TEMP_HOME"'/.roborev/config.toml"' +The status should be success +The output should include 'turn_threshold = 0' +The output should include 'commit_threshold = 0' +The output should include 'failed_review_threshold = 1' +The output should include 'Inspect open RoboRev findings read-only.' +The output should include 'ask the user for explicit approval.' +The output should include 'Do not edit files, run a fixer, commit, or close reviews before approval.' +The output should not include 'roborev-fix' +End + +It 'installs native hooks in each configured local checkout' +When run bash -c 'HOME="'"$TEMP_HOME"'" bash "'"$PREPROCESSED_SCRIPT"'" >/dev/null 2>&1; cat "'"$TEMP_HOME"'/roborev-hook.log"' +The status should be success +The output should include "$TEMP_HOME/ghq/github.com/org/repo1|install-hook --binary $TEMP_HOME/.local/bin/roborev" +The output should include "$TEMP_HOME/ghq/github.com/org/repo2|install-hook --binary $TEMP_HOME/.local/bin/roborev" End It 'restricts config file permissions to the owner' @@ -102,7 +139,7 @@ setup_no_repos() { [ci] enabled = true poll_interval = "5m" -repos = ["__ROBOREV_CI_REPOS__"] +repos = ["__ROBOREV_REPOS__"] TOML : >"$TEMP_HOME/dotfiles/.env" @@ -122,10 +159,10 @@ cleanup_no_repos() { Before 'setup_no_repos' After 'cleanup_no_repos' -It 'skips hydration when ROBOREV_CI_REPOS is unset' -When run bash -c 'unset ROBOREV_CI_REPOS; HOME="'"$TEMP_HOME"'" bash "'"$PREPROCESSED_SCRIPT"'"' +It 'skips hydration when ROBOREV_REPOS is unset' +When run bash -c 'unset ROBOREV_REPOS ROBOREV_CI_REPOS; HOME="'"$TEMP_HOME"'" bash "'"$PREPROCESSED_SCRIPT"'"' The status should be success -The error should include 'ROBOREV_CI_REPOS not set' +The error should include 'ROBOREV_REPOS not set' The path "$TEMP_HOME/.roborev/config.toml" should not be exist End End