6 Commits

Author SHA1 Message Date
john 76292c8393 Make more efficient
ai/code-review AI review reported findings
hello-world/pipeline/head This commit looks good
2026-05-03 21:25:21 +01:00
john b504842293 Switching to x.ai for reviews
ai/code-review No issues found
hello-world/pipeline/head This commit looks good
2026-05-03 21:22:07 +01:00
john 4973316155 Update (#2)
ai/code-review AI review reported findings
hello-world/pipeline/head This commit looks good
Reviewed-on: #2
Co-authored-by: John Burton <john.burton@jbmail.com>
Co-committed-by: John Burton <john.burton@jbmail.com>
2026-05-03 09:15:56 +00:00
john e34b3570ab Update code
ai/code-review No issues found
hello-world/pipeline/pr-master This commit looks good
hello-world/pipeline/head This commit looks good
2026-05-03 09:50:29 +01:00
john 5db8dd5bc7 Provoke changes
ai/code-review AI review reported findings
hello-world/pipeline/pr-master This commit looks good
2026-05-03 09:47:44 +01:00
john a791e4c15c Update
hello-world/pipeline/head This commit looks good
ai/code-review AI review reported findings
hello-world/pipeline/pr-master This commit looks good
2026-05-03 09:41:47 +01:00
4 changed files with 178 additions and 12 deletions
Vendored
+5 -2
View File
@@ -1,8 +1,11 @@
pipeline {
agent any
environment {
OPENAI_API_KEY = credentials('OPENAI_API_KEY')
CODEX_MODEL = 'gpt-4'
XAI_API_KEY = credentials('XAI_API_KEY')
GITEA_TOKEN = credentials('GITEA_TOKEN')
GITEA_URL = 'https://git.jb9.uk'
XAI_MODEL = 'grok-4.3'
AI_REVIEW_FAIL_ON_FINDINGS = 'false'
}
stages {
+22
View File
@@ -1,5 +1,27 @@
# Hello World C++ Project
## Jenkins AI Review
The Jenkins pipeline can run an x.ai-powered review and report back to Gitea.
Required Jenkins secrets and variables:
- `XAI_API_KEY`
- `GITEA_TOKEN`
- `GITEA_URL`
Optional variables:
- `AI_REVIEW_MODEL`
- `XAI_MODEL`
- `XAI_BASE_URL`
- `AI_REVIEW_FAIL_ON_FINDINGS`
- `GITEA_REPO_OWNER`
- `GITEA_REPO_NAME`
- `GITEA_PR_NUMBER`
The default review model is `grok-4.3`, using the OpenAI Python SDK against `https://api.x.ai/v1`.
## Build
```bash
+143 -7
View File
@@ -1,12 +1,15 @@
#!/usr/bin/env python3
import json
import os
import subprocess
import sys
from typing import Optional
from urllib import error, parse, request
MAX_DIFF_CHARS = 12000
STATUS_CONTEXT = "ai/code-review"
def run_git_command(*args: str) -> str:
@@ -59,6 +62,110 @@ def collect_diff() -> str:
return ""
def get_current_commit() -> Optional[str]:
return os.getenv("GIT_COMMIT") or try_git_command("rev-parse", "HEAD")
def parse_repo_from_remote() -> tuple[Optional[str], Optional[str]]:
remote_url = try_git_command("remote", "get-url", "origin")
if not remote_url:
return None, None
cleaned = remote_url.strip()
if cleaned.endswith(".git"):
cleaned = cleaned[:-4]
if cleaned.startswith("git@") and ":" in cleaned:
cleaned = cleaned.split(":", 1)[1]
elif "://" in cleaned:
parsed = parse.urlparse(cleaned)
cleaned = parsed.path.lstrip("/")
parts = [part for part in cleaned.split("/") if part]
if len(parts) < 2:
return None, None
return parts[-2], parts[-1]
def get_gitea_repo() -> tuple[Optional[str], Optional[str]]:
owner = os.getenv("GITEA_REPO_OWNER")
name = os.getenv("GITEA_REPO_NAME")
if owner and name:
return owner, name
return parse_repo_from_remote()
def post_gitea_json(api_path: str, payload: dict) -> None:
base_url = os.getenv("GITEA_URL")
token = os.getenv("GITEA_TOKEN")
if not base_url or not token:
return
url = f"{base_url.rstrip('/')}{api_path}"
data = json.dumps(payload).encode("utf-8")
req = request.Request(
url,
data=data,
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
"Accept": "application/json",
},
method="POST",
)
try:
with request.urlopen(req) as response:
response.read()
except error.HTTPError as exc:
details = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"Gitea API request failed: HTTP {exc.code} {details}") from exc
except error.URLError as exc:
raise RuntimeError(f"Gitea API request failed: {exc.reason}") from exc
def publish_commit_status(state: str, description: str) -> None:
base_url = os.getenv("GITEA_URL")
token = os.getenv("GITEA_TOKEN")
if not base_url or not token:
return
owner, repo = get_gitea_repo()
commit = get_current_commit()
if not owner or not repo or not commit:
return
build_url = os.getenv("BUILD_URL")
payload = {
"state": state,
"context": STATUS_CONTEXT,
"description": description[:255],
}
if build_url:
payload["target_url"] = build_url
post_gitea_json(f"/api/v1/repos/{owner}/{repo}/statuses/{commit}", payload)
def publish_pr_comment(body: str) -> None:
base_url = os.getenv("GITEA_URL")
token = os.getenv("GITEA_TOKEN")
if not base_url or not token:
return
owner, repo = get_gitea_repo()
pr_number = os.getenv("GITEA_PR_NUMBER") or os.getenv("CHANGE_ID")
if not owner or not repo or not pr_number:
return
post_gitea_json(
f"/api/v1/repos/{owner}/{repo}/issues/{pr_number}/comments",
{"body": body},
)
def build_prompt(diff_text: str) -> str:
truncated_diff = diff_text[:MAX_DIFF_CHARS]
suffix = ""
@@ -68,6 +175,8 @@ def build_prompt(diff_text: str) -> str:
return (
"Review the following git diff. Focus on correctness, regressions, missing validation, "
"build/test issues, and security concerns. "
"No need to comment on removed code unless it seems like it would cause a problem. "
"Do not review the scripts in the scripts directory, as they are not part of the main codebase. "
"Return either 'No issues found.' or a short flat list where each item includes severity, file, and issue.\n\n"
f"{truncated_diff}{suffix}"
)
@@ -81,12 +190,19 @@ def request_review(diff_text: str) -> str:
"The 'openai' package is not installed. Install it with 'pip install openai'."
) from exc
api_key = os.getenv("OPENAI_API_KEY")
api_key = os.getenv("XAI_API_KEY") or os.getenv("OPENAI_API_KEY")
if not api_key:
raise RuntimeError("OPENAI_API_KEY is not set.")
raise RuntimeError("XAI_API_KEY is not set.")
model = os.getenv("CODEX_MODEL") or os.getenv("OPENAI_MODEL") or "gpt-4.1"
client = OpenAI(api_key=api_key)
model = (
os.getenv("AI_REVIEW_MODEL")
or os.getenv("XAI_MODEL")
or os.getenv("CODEX_MODEL")
or os.getenv("OPENAI_MODEL")
or "grok-4.3"
)
base_url = os.getenv("XAI_BASE_URL") or "https://api.x.ai/v1"
client = OpenAI(api_key=api_key, base_url=base_url)
response = client.responses.create(
model=model,
@@ -117,15 +233,28 @@ def request_review(diff_text: str) -> str:
return response.output_text.strip()
def classify_review(review: str) -> tuple[str, str]:
normalized = (review or "").strip().lower()
if normalized == "no issues found.":
return "success", "No issues found"
return "failure", "AI review reported findings"
def main() -> int:
diff_text = collect_diff()
if not diff_text:
print("No git changes detected. Skipping AI review.")
message = "No git changes detected. Skipping AI review."
print(message)
publish_commit_status("success", "No changes to review")
return 0
try:
review = request_review(diff_text)
except Exception as exc:
try:
publish_commit_status("error", "AI review failed")
except Exception as gitea_exc:
print(f"Gitea reporting failed: {gitea_exc}", file=sys.stderr)
print(f"AI review failed: {exc}", file=sys.stderr)
return 1
@@ -133,8 +262,15 @@ def main() -> int:
print(review or "No issues found.")
fail_on_findings = os.getenv("AI_REVIEW_FAIL_ON_FINDINGS", "false").lower() == "true"
normalized = (review or "").strip().lower()
no_findings = normalized == "no issues found."
state, description = classify_review(review)
try:
publish_commit_status(state, description)
publish_pr_comment(f"AI review result:\n\n{review or 'No issues found.'}")
except Exception as exc:
print(f"Gitea reporting failed: {exc}", file=sys.stderr)
no_findings = state == "success"
if fail_on_findings and not no_findings:
return 1
+8 -3
View File
@@ -1,10 +1,15 @@
#include <iostream>
int add_3(int cx) {
return 2 + cx;
const std::string& get_greeting() {
return "Hello, world!";
}
// Add the number of things.
int add_6(int cx) {
return 6 + cx;
}
int main() {
std::cout << "Hello, world!" << add_3(5) << std::endl;
std::cout << "Hello, world!" << add_6(5) << std::endl;
return 0;
}