docs(agents): correct backend AGENTS.md + add ai-review workflow

This commit is contained in:
Crypto Rug Munch 2026-07-07 22:20:00 +07:00
parent 27184c704d
commit 8491635bf2
3 changed files with 202 additions and 23 deletions

127
.github/scripts/ai_review.py vendored Normal file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""Post an AI-generated review comment on a pull request from a diff file.
Environment variables:
AI_REVIEW_API_KEY API key for the chat-completions endpoint.
AI_REVIEW_MODEL Model string (default: openrouter/openai/gpt-4o-mini).
AI_REVIEW_BASE_URL Base URL for the endpoint (default: https://openrouter.ai/api/v1).
GITHUB_TOKEN GitHub token used to post the PR comment.
PR_NUMBER Pull request number to comment on.
Usage:
python3 .github/scripts/ai_review.py /path/to/pr.diff
"""
from __future__ import annotations
import json
import os
import sys
import urllib.error
import urllib.request
from pathlib import Path
DEFAULT_MODEL = "openrouter/openai/gpt-4o-mini"
DEFAULT_BASE_URL = "https://openrouter.ai/api/v1"
MAX_DIFF_CHARS = 30_000
SYSTEM_PROMPT = (
"You are a senior code reviewer for a FastAPI crypto-intelligence backend. "
"Review the provided diff for correctness, security, maintainability, and "
"conformance to the project standards. Keep the response concise, actionable, "
"and focused on the most important issues. If the diff is large, prioritize "
"architecture and security concerns."
)
def _call_llm(diff_text: str) -> str:
api_key = os.environ.get("AI_REVIEW_API_KEY")
if not api_key:
return "AI_REVIEW_API_KEY not set; skipping AI review."
base_url = os.environ.get("AI_REVIEW_BASE_URL", DEFAULT_BASE_URL).rstrip("/")
model = os.environ.get("AI_REVIEW_MODEL", DEFAULT_MODEL)
payload = {
"model": model,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Pull request diff:\n\n```diff\n{diff_text}\n```"},
],
"temperature": 0.2,
"max_tokens": 2_000,
}
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
f"{base_url}/chat/completions",
data=data,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=120) as resp:
result = json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
return f"AI review API request failed: HTTP {exc.code} - {exc.read().decode('utf-8', errors='ignore')}"
except Exception as exc:
return f"AI review API request failed: {exc}"
try:
return result["choices"][0]["message"]["content"]
except (KeyError, IndexError, TypeError):
return f"AI review returned unexpected response: {result}"
def _post_comment(body: str) -> None:
pr_number = os.environ.get("PR_NUMBER")
token = os.environ.get("GITHUB_TOKEN")
repo = os.environ.get("GITHUB_REPOSITORY")
if not pr_number or not token or not repo:
print("PR_NUMBER/GITHUB_TOKEN/GITHUB_REPOSITORY not set; printing review to stdout.")
print(body)
return
payload = json.dumps({"body": body}).encode("utf-8")
req = urllib.request.Request(
f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments",
data=payload,
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=60) as resp:
print(f"Posted AI review comment: {resp.status}")
except urllib.error.HTTPError as exc:
print(f"Failed to post comment: HTTP {exc.code}", file=sys.stderr)
print(exc.read().decode("utf-8", errors="ignore"), file=sys.stderr)
def main() -> int:
diff_path = Path(sys.argv[1]) if len(sys.argv) > 1 else None
if diff_path:
diff_text = diff_path.read_text(encoding="utf-8", errors="ignore")
else:
diff_text = sys.stdin.read()
if not diff_text.strip():
print("No diff provided; skipping AI review.")
return 0
if len(diff_text) > MAX_DIFF_CHARS:
diff_text = diff_text[:MAX_DIFF_CHARS] + "\n\n... [diff truncated for token limits]"
review = _call_llm(diff_text)
_post_comment(review)
return 0
if __name__ == "__main__":
raise SystemExit(main())

30
.github/workflows/ai-review.yml vendored Normal file
View file

@ -0,0 +1,30 @@
name: AI PR Review
on:
pull_request:
types: [opened, synchronize]
branches: [main]
permissions:
contents: read
pull-requests: write
jobs:
ai-review:
name: AI PR Review
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get PR diff
run: git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr.diff
- name: Run AI review
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AI_REVIEW_API_KEY: ${{ secrets.AI_REVIEW_API_KEY }}
AI_REVIEW_MODEL: ${{ vars.AI_REVIEW_MODEL || 'openrouter/openai/gpt-4o-mini' }}
PR_NUMBER: ${{ github.event.number }}
run: python3 .github/scripts/ai_review.py /tmp/pr.diff

View file

@ -13,12 +13,12 @@ See [CLEANUP.md](CLEANUP.md) for cleanup history.
# AGENTS.md — rmi-backend
# Every AI agent working on backend code reads this first.
# Last updated: 2026-06-29
# Last updated: 2026-07-07
## What This Is
rmi-backend — FastAPI crypto intelligence backend. 757+ routes, 221 MCP tools,
89 DataBus chains, 39.4M wallet labels, 21 SENTINEL scanner modules.
rmi-backend — FastAPI crypto intelligence backend. 466+ API routes, 200+ MCP tools,
89+ DataBus chains, 39.4M wallet labels (ClickHouse), 20+ SENTINEL scanner modules.
## Before You Start
@ -32,23 +32,38 @@ Read these files in order:
## The Server
ALL work happens on Talos (100.104.130.92).
ALL source edits happen on **Talos** in the Forgejo clone:
```
ssh netcup
cd /root/backend
cd /srv/work/repos/rmi-backend
```
Deploy artifacts are built from this repo and run on Talos at `/root/backend/`
(compose, data volumes, `.secrets/`). The running `rmi-backend` container is built
from the Forgejo image — do not edit code in `/root/backend/`.
Quick health checks on Talos:
```
docker logs rmi-backend --tail 100
docker restart rmi-backend
curl localhost:8000/health
```
Code in /root/backend is Docker volume-mounted — changes go live on restart.
## Testing
Local (development):
```
pytest tests/unit/ -q -m "not integration"
pytest tests/unit/test_X.py -q
```
In the deployed container (when you need full runtime deps):
```
docker exec rmi-backend pytest -p no:pytest-brownie -v
docker exec rmi-backend pytest tests/unit/test_X.py -p no:pytest-brownie -v
```
The `-p no:pytest-brownie` is mandatory — eth-brownie tries to connect to ganache.
@ -56,34 +71,41 @@ The `-p no:pytest-brownie` is mandatory — eth-brownie tries to connect to gana
## Linting
```
ruff check app/ --fix
ruff format app/
mypy app/ --strict
ruff check app/ tests/
ruff format app/ tests/
make mypy-gate # scoped gate for clean modules
make typecheck # full codebase, informational only
```
## MCP Tools
mcp_discover, status_check, analytics_query, wallet_labels,
eth_labels_tool, token_security, scam_intel, news_search,
rag_query, report_generate, x402_marketplace (+ 210 more)
rag_query, report_generate, x402_marketplace (+ 200 more)
List: GET /mcp/tools Call: POST /mcp/call/{tool_id} Info: GET /mcp/info
## Adding a New Scanner Module
1. Create app/scanners/my_detector.py (fetches from first-party APIs only)
2. Add import to app/scanners/__init__.py
3. Add runner function in app/scanners/sentinel_pipeline.py
4. Add endpoint to app/routers/x402_tools.py
5. Add pricing to x402_enforcement.py
1. Create `app/domains/scanners/my_detector.py` (fetches from first-party APIs only)
2. Wire it into `app/domains/scanners/sentinel_pipeline.py` or the relevant orchestrator
3. Add endpoint/tool registration in `app/domains/x402/router.py` or `app/routers/`
4. Add pricing to `app/domains/billing/x402/config.py` if it is a paid tool
CRITICAL: Never call our own x402 endpoints internally.
## Adding a New DataBus Chain
1. Create provider in app/databus/providers/
2. Register in app/databus/chains.py
3. Register provider class in app/databus/registry.py
1. Create provider function in `app/domains/databus/providers/<category>.py`
2. Register it in `app/domains/databus/providers/chains.py`
3. Re-export from `app.domains.databus.providers` if it is public
## x402 Payment Facilitators
Facilitator selection is dynamic and driven by `app/domains/billing/x402/config.py` and the
`app.domains.x402` router. Historic docstring claims about a fixed number of "active" or
"offline" facilitators are not authoritative; the current supported networks and tokens are
defined in code and deployment config.
## Rules
@ -100,7 +122,7 @@ CRITICAL: Never call our own x402 endpoints internally.
## Git
Internal primary: `root@talos:/srv/git/rmi-backend.git`
Push `talos main` on every commit.
Internal primary: `ssh://git@git.rugmunch.io:2222/RugMunchMedia/rmi-backend`
Push `feat/scanners-p4-8-cont` (and other feature branches) on every commit.
External remotes: GitHub (LLC), GitLab, HuggingFace (see ~/AGENTS.md)
External remotes: Codeberg, GitLab (GitHub is currently disabled — account flagged 2026-07-06).