179 lines
5.8 KiB
Bash
Executable File
179 lines
5.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# prose-ai-review.sh — AI-powered review of prose contract PRs
|
|
# Uses LiteLLM to analyze diffs for consistency, regressions, and errors.
|
|
# Posts review comments via Gitea API.
|
|
set -euo pipefail
|
|
|
|
LITELLM_URL="${LITELLM_URL:-}"
|
|
LITELLM_KEY="${LITELLM_KEY:-}"
|
|
GITEA_TOKEN="${GITEA_TOKEN:-$(grep GITEA_TOKEN /root/.pi/agent/extensions/telegram/.env 2>/dev/null | cut -d= -f2 || echo '')}"
|
|
GITEA_API="https://git.sysloggh.net/api/v1"
|
|
|
|
# Get PR context from Gitea Actions environment
|
|
# For push events: GITEA_REPOSITORY, GITEA_SHA
|
|
REPO="${GITEA_REPOSITORY:-}"
|
|
BASE_REF="${GITEA_BASE_REF:-refs/heads/master}"
|
|
HEAD_REF="${GITEA_HEAD_REF:-}"
|
|
SHA="${GITEA_SHA:-}"
|
|
|
|
# Get the diff — works for both PR events (base...head) and push events (HEAD~1)
|
|
echo "=== Fetching diff ==="
|
|
if [ -n "$HEAD_REF" ] && [ "$HEAD_REF" != "$BASE_REF" ]; then
|
|
DIFF=$(git diff origin/$BASE_REF...origin/$HEAD_REF -- "*.prose.md" 2>/dev/null | head -5000)
|
|
elif [ -n "$SHA" ]; then
|
|
DIFF=$(git diff $SHA~1..$SHA -- "*.prose.md" 2>/dev/null | head -5000)
|
|
else
|
|
DIFF=$(git diff HEAD~1 -- "*.prose.md" 2>/dev/null | head -5000)
|
|
fi
|
|
|
|
if [ -z "$DIFF" ]; then
|
|
echo "No prose contract changes in this PR. Skipping AI review."
|
|
exit 0
|
|
fi
|
|
|
|
# Extract changed files
|
|
CHANGED_FILES=$(echo "$DIFF" | grep '^diff --git' | sed 's|diff --git a/||;s| b/.*||' | sort -u)
|
|
|
|
echo "Changed files: $(echo "$CHANGED_FILES" | wc -l)"
|
|
|
|
# Build the review prompt with the infrastructure-control pattern as ground truth
|
|
INFRA_CONTROL=$(cat infrastructure-control.prose.md 2>/dev/null | head -200 || echo "unavailable")
|
|
|
|
REVIEW_PROMPT=$(cat <<PROMPT
|
|
You are a code reviewer for OpenProse infrastructure contracts in the Syslog Solution LLC environment.
|
|
|
|
## INFRASTRUCTURE GROUND TRUTH
|
|
|
|
The infrastructure-control.prose.md contract is the canonical reference for the cluster topology:
|
|
|
|
**Proxmox Cluster "Tabiri" (6 nodes):**
|
|
- amdpve (192.168.68.15): tanko, tdunna, baggy, scottdenya
|
|
- minipve (192.168.68.12): adguard, authentik, gitea, syslog-api, infisical-vault
|
|
- storepve (192.168.68.6): docker-vm, ra-h-os, PBS, media, jdownloader, zulip
|
|
- acerpve (192.168.68.9): llm-gpu
|
|
- ocupve (192.168.68.5): ocu-llm
|
|
- hwepve (192.168.68.4): abiba, kagentz, mumuni
|
|
|
|
**CT IDs (verified 2026-07-24 against PVE API):**
|
|
100:abiba 102:adguard 104:authentik 105:kagentz 106:ra-h-os
|
|
107:pbs 108:media 110:gitea 111:tdunna 112:tanko
|
|
113:baggy 114:mumuni 115:scottdenya 116:syslog-api 117:zulip
|
|
118:jdownloader 119:infisical-vault
|
|
|
|
**NO CT 122, CT 123, or .19 exist in the cluster.**
|
|
|
|
**CRITICAL RULES (never regress):**
|
|
1. NO /grafana/ nginx route — it was tried and reverted on 2026-07-02. Grafana is direct LAN at :3001.
|
|
2. NO .19 IP — Zulip is CT 117 on storepve.
|
|
3. NO CT 122/123 — Tanko=CT 112, Mumuni=CT 114.
|
|
4. Strix Halo :8080 is FIREWALLED to .116 only — cannot be probed from abiba (.24).
|
|
5. abiba-zulip PM2 process is DECOMMISSIONED (2026-07-04) — abiba uses Telegram only.
|
|
|
|
**Docker on CT 116 (8 containers):**
|
|
harness-litellm, harness-router, harness-nginx, harness-postgres,
|
|
harness-redis, harness-dashboard, harness-grafana, harness-prometheus
|
|
|
|
## DIFF TO REVIEW
|
|
|
|
$DIFF
|
|
|
|
## REVIEW INSTRUCTIONS
|
|
|
|
Check the diff for:
|
|
1. Does it introduce any contradictions with the ground truth above?
|
|
2. Does it re-introduce any previously-fixed issues (Grafana /grafana/ route, stale CT IDs, .19)?
|
|
3. Are any IPs or hostnames wrong?
|
|
4. Does the frontmatter have valid kind, name, and description fields?
|
|
5. Is anything inconsistent with other contracts in the repo?
|
|
|
|
Respond with a concise review in this format:
|
|
|
|
**Verdict:** APPROVED / CHANGES_REQUESTED
|
|
|
|
**Issues found:**
|
|
- [issue 1]
|
|
- [issue 2]
|
|
|
|
**Summary:** (one paragraph summary of the changes)
|
|
PROMPT
|
|
)
|
|
|
|
echo "=== Sending to LiteLLM for review ==="
|
|
|
|
# Skip if LiteLLM is not configured (secrets not available)
|
|
if [ -z "${LITELLM_URL:-}" ] || [ -z "${LITELLM_KEY:-}" ]; then
|
|
echo "⚠️ LiteLLM secrets not configured — skipping AI review"
|
|
echo " Set LITELLM_URL and LITELLM_KEY as Gitea repository secrets"
|
|
exit 0
|
|
fi
|
|
RESPONSE=$(curl -sf -X POST "$LITELLM_URL/v1/chat/completions" \
|
|
-H "Authorization: Bearer $LITELLM_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(python3 -c "
|
|
import json, sys
|
|
print(json.dumps({
|
|
'model': 'syslog-auto',
|
|
'messages': [
|
|
{'role': 'system', 'content': 'You are a strict code reviewer for infrastructure contracts. Be concise. Never approve changes that contradict the ground truth.'},
|
|
{'role': 'user', 'content': '''$REVIEW_PROMPT'''}
|
|
],
|
|
'temperature': 0.1,
|
|
'max_tokens': 1500
|
|
}))
|
|
")" 2>/dev/null)
|
|
|
|
if [ -z "$RESPONSE" ]; then
|
|
echo "❌ LiteLLM review API call failed"
|
|
exit 1
|
|
fi
|
|
|
|
REVIEW_BODY=$(echo "$RESPONSE" | python3 -c "
|
|
import json, sys
|
|
d = json.load(sys.stdin)
|
|
print(d['choices'][0]['message']['content'])
|
|
" 2>/dev/null)
|
|
|
|
if [ -z "$REVIEW_BODY" ]; then
|
|
echo "❌ Failed to parse AI response"
|
|
echo "Raw: $RESPONSE"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== AI REVIEW RESULT ==="
|
|
echo "$REVIEW_BODY"
|
|
echo ""
|
|
|
|
# Determine verdict
|
|
if echo "$REVIEW_BODY" | grep -qi "verdict.*CHANGES_REQUESTED"; then
|
|
VERDICT="CHANGES_REQUESTED"
|
|
EVENT="REQUEST_CHANGES"
|
|
else
|
|
VERDICT="APPROVED"
|
|
EVENT="APPROVE"
|
|
fi
|
|
|
|
# Post review to Gitea PR
|
|
echo "=== Posting review to PR #$PR_NUMBER ==="
|
|
if [ -n "$GITEA_TOKEN" ]; then
|
|
curl -sf -X POST "$GITEA_API/repos/$REPO/pulls/$PR_NUMBER/reviews" \
|
|
-H "Authorization: token $GITEA_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(python3 -c "
|
|
import json
|
|
print(json.dumps({
|
|
'body': '$REVIEW_BODY',
|
|
'event': '$EVENT'
|
|
}))
|
|
")" 2>/dev/null || echo "⚠️ Could not post review via API (token may be missing)"
|
|
|
|
echo "✅ AI review posted: $VERDICT"
|
|
else
|
|
echo "⚠️ GITEA_TOKEN not set — review displayed above but not posted to PR"
|
|
fi
|
|
|
|
# Exit with error if changes requested (blocks merge)
|
|
if [ "$VERDICT" = "CHANGES_REQUESTED" ]; then
|
|
exit 1
|
|
fi
|