commit 7121d02bc23daf30367f8bd25d303b3e53d81f43
parent d4af8772603364f08127cc4295c7aaebdcdad026
Author: Yash Dive <70193427+yashdive@users.noreply.github.com>
Date: Fri, 27 Feb 2026 15:37:35 -0500
moved GitHub review logic from bash to python code (#150)
* Test vet workflow
* Test vet workflow
* Test vet workflow
* Test vet workflow
* Test vet workflow
* Test vet workflow
* Test vet workflow
* changed github review code to python from bash
* modified run.py
* modified run.py
* test precommit
* test
* testing
* Anthropic model updates (#147)
Co-authored-by: Andrew Laack <andrew@laack.co>
Co-authored-by: OpenCode <opencode@users.noreply.github.com>
* Updated gemini definitions (#145)
* Updated gemini definitions
* Removed models
* formatting
* Verified all defined models work correctly right now.
* Tests
* Fix vet identified issue
---------
Co-authored-by: Andrew Laack <andrew@laack.co>
* added new openrouter provider configuration
* resolving changes in python runner migration
* reverting unintended config changes
---------
Co-authored-by: andrewlaack-collab <andrew.laack@imbue.com>
Co-authored-by: Andrew Laack <andrew@laack.co>
Co-authored-by: OpenCode <opencode@users.noreply.github.com>
Diffstat:
5 files changed, 180 insertions(+), 47 deletions(-)
diff --git a/.github/workflows/vet.yml b/.github/workflows/vet.yml
@@ -13,7 +13,7 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
env:
- ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- uses: actions/checkout@v4
with:
@@ -22,3 +22,4 @@ jobs:
- uses: ./
with:
build-from-source: true
+ config: ci
diff --git a/.vet/models.json b/.vet/models.json
@@ -93,4 +93,4 @@
}
}
}
-}
+}+
\ No newline at end of file
diff --git a/action.yml b/action.yml
@@ -108,4 +108,4 @@ runs:
Additional context (not necessarily part of the goal):
${{ github.event.pull_request.body }}
- run: ${{ github.action_path }}/action/run.sh
+ run: python "${{ github.action_path }}/action/run.py"
diff --git a/action/run.py b/action/run.py
@@ -0,0 +1,175 @@
+import json
+import os
+import subprocess
+import sys
+from pathlib import Path
+from typing import Tuple
+
+import httpx
+
+# reusing SyncLocalGitRepo from vet.git to compute merge base
+from vet.git import SyncLocalGitRepo
+
+
+def get_env(name: str, required: bool = True) -> str:
+ value = os.environ.get(name)
+ if required and not value:
+ print(f"::error::{name} environment variable not set")
+ sys.exit(1)
+ return value or ""
+
+
+def compute_merge_base(base_ref: str, head_sha: str) -> str:
+ repo = SyncLocalGitRepo(Path("."))
+ try:
+ return repo.get_merge_base(f"origin/{base_ref}", head_sha)
+ except Exception:
+ print(f"::error::Failed to compute merge base between origin/{base_ref} and {head_sha}")
+ sys.exit(1)
+
+
+def build_vet_args(goal: str, merge_base: str) -> list[str]:
+ args = [
+ goal,
+ "--quiet",
+ "--output-format",
+ "github",
+ "--base-commit",
+ merge_base,
+ ]
+
+ # Multi-value flags (space-splitting like bash)
+ multi_value_envs = {
+ "INPUT_ENABLED_ISSUE_CODES": "--enabled-issue-codes",
+ "INPUT_DISABLED_ISSUE_CODES": "--disabled-issue-codes",
+ "INPUT_EXTRA_CONTEXT": "--extra-context",
+ }
+
+ # Single-value flags
+ single_value_envs = {
+ "INPUT_MODEL": "--model",
+ "INPUT_CONFIDENCE_THRESHOLD": "--confidence-threshold",
+ "INPUT_MAX_WORKERS": "--max-workers",
+ "INPUT_MAX_SPEND": "--max-spend",
+ "INPUT_TEMPERATURE": "--temperature",
+ "INPUT_CONFIG": "--config",
+ }
+
+ # Agentic flag
+ if os.environ.get("INPUT_AGENTIC") == "true":
+ args.append("--agentic")
+
+ # Handle single-value flags
+ for env_key, flag in single_value_envs.items():
+ value = os.environ.get(env_key)
+ if value:
+ args.extend([flag, value])
+
+ # Handle multi-value flags (space-splitting like bash)
+ for env_key, flag in multi_value_envs.items():
+ value = os.environ.get(env_key)
+ if value:
+ args.append(flag)
+ args.extend(value.split())
+
+ return args
+
+
+def run_vet(args: list[str]) -> Tuple[dict, int]:
+ result = subprocess.run(
+ ["vet"] + args,
+ stdout=subprocess.PIPE,
+ stderr=None,
+ text=True,
+ )
+
+ status = result.returncode
+
+ if status not in (0, 10):
+ print(f"::error::Vet failed with exit code {status}")
+ sys.exit(status)
+
+ try:
+ review_json = json.loads(result.stdout)
+ except json.JSONDecodeError:
+ print("::error::Failed to parse vet JSON output")
+ print(result.stdout)
+ sys.exit(1)
+
+ return review_json, status
+
+
+def post_review(review_json: dict, repo: str, pr_number: str, token: str):
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "Accept": "application/vnd.github+json",
+ }
+
+ review_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}/reviews"
+ comment_url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
+
+ with httpx.Client(timeout=10.0) as client:
+ # Try review post
+ try:
+ response = client.post(review_url, json=review_json, headers=headers)
+ if response.status_code in (200, 201):
+ return
+ except httpx.HTTPError:
+ pass
+
+ # Fallback to comment
+ body_parts = [review_json.get("body", "")]
+ for comment in review_json.get("comments", []):
+ path = comment.get("path")
+ line = comment.get("line")
+ text = comment.get("body", "")
+ body_parts.append(f"**{path}:{line}**\n\n{text}")
+
+ comment_body = "\n\n---\n\n".join(body_parts)
+
+ try:
+ fallback_response = client.post(
+ comment_url,
+ json={"body": comment_body},
+ headers=headers,
+ )
+ if fallback_response.status_code in (200, 201):
+ return
+ except httpx.HTTPError:
+ pass
+
+ # both failing results in workflow failure
+ print("::error::Failed to post GitHub review and fallback comment")
+ sys.exit(1)
+
+
+def main():
+ goal = get_env("INPUT_GOAL")
+ base_ref = get_env("INPUT_BASE_REF")
+ head_sha = get_env("INPUT_HEAD_SHA")
+ pr_number = get_env("INPUT_PR_NUMBER")
+ repo = get_env("GITHUB_REPOSITORY")
+ token = get_env("GH_TOKEN")
+
+ fail_on_issues = os.environ.get("INPUT_FAIL_ON_ISSUES") == "true"
+
+ merge_base = compute_merge_base(base_ref, head_sha)
+
+ args = build_vet_args(goal, merge_base)
+
+ review_json, status = run_vet(args)
+
+ # Inject commit_id (replaces jq logic)
+ review_json["commit_id"] = head_sha
+
+ post_review(review_json, repo, pr_number, token)
+
+ # Replicate fail-on-issues behavior
+ if fail_on_issues and status == 10:
+ sys.exit(1)
+
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/action/run.sh b/action/run.sh
@@ -1,44 +0,0 @@
-#!/usr/bin/env bash
-set +e
-
-MERGE_BASE=$(git merge-base "origin/${INPUT_BASE_REF}" "${INPUT_HEAD_SHA}")
-if [ $? -ne 0 ]; then
- echo "::error::Failed to compute merge base between origin/${INPUT_BASE_REF} and ${INPUT_HEAD_SHA}"
- exit 1
-fi
-
-ARGS=("${INPUT_GOAL}" --quiet --output-format github --base-commit "${MERGE_BASE}")
-
-[[ "${INPUT_AGENTIC}" == "true" ]] && ARGS+=(--agentic)
-[[ -n "${INPUT_MODEL}" ]] && ARGS+=(--model "${INPUT_MODEL}")
-[[ -n "${INPUT_CONFIDENCE_THRESHOLD}" ]] && ARGS+=(--confidence-threshold "${INPUT_CONFIDENCE_THRESHOLD}")
-[[ -n "${INPUT_MAX_WORKERS}" ]] && ARGS+=(--max-workers "${INPUT_MAX_WORKERS}")
-[[ -n "${INPUT_MAX_SPEND}" ]] && ARGS+=(--max-spend "${INPUT_MAX_SPEND}")
-[[ -n "${INPUT_TEMPERATURE}" ]] && ARGS+=(--temperature "${INPUT_TEMPERATURE}")
-[[ -n "${INPUT_CONFIG}" ]] && ARGS+=(--config "${INPUT_CONFIG}")
-
-[[ -n "${INPUT_ENABLED_ISSUE_CODES}" ]] && ARGS+=(--enabled-issue-codes ${INPUT_ENABLED_ISSUE_CODES})
-[[ -n "${INPUT_DISABLED_ISSUE_CODES}" ]] && ARGS+=(--disabled-issue-codes ${INPUT_DISABLED_ISSUE_CODES})
-[[ -n "${INPUT_EXTRA_CONTEXT}" ]] && ARGS+=(--extra-context ${INPUT_EXTRA_CONTEXT})
-
-vet "${ARGS[@]}" > "${RUNNER_TEMP}/review.json"
-status=$?
-
-if [ "$status" -ne 0 ] && [ "$status" -ne 10 ]; then
- echo "::error::Vet failed with exit code ${status}"
- exit "$status"
-fi
-
-jq --arg sha "${INPUT_HEAD_SHA}" \
- '. + {commit_id: $sha}' "${RUNNER_TEMP}/review.json" > "${RUNNER_TEMP}/review-final.json"
-
-gh api "repos/${GITHUB_REPOSITORY}/pulls/${INPUT_PR_NUMBER}/reviews" \
- --method POST --input "${RUNNER_TEMP}/review-final.json" > /dev/null || \
- gh pr comment "${INPUT_PR_NUMBER}" \
- --body "$(jq -r '[.body] + [.comments[] | "**\(.path):\(.line)**\n\n\(.body)"] | join("\n\n---\n\n")' "${RUNNER_TEMP}/review-final.json")"
-
-if [[ "${INPUT_FAIL_ON_ISSUES}" == "true" ]] && [ "$status" -eq 10 ]; then
- exit 1
-fi
-
-exit 0