vet

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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:
M.github/workflows/vet.yml | 3++-
M.vet/models.json | 3++-
Maction.yml | 2+-
Aaction/run.py | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Daction/run.sh | 44--------------------------------------------
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