centralize guides and rails under mosaic with runtime compatibility links

This commit is contained in:
Jason Woltje
2026-02-17 11:39:52 -06:00
parent a1c2efef1c
commit 4eac2c76e6
85 changed files with 10785 additions and 0 deletions

265
rails/codex/README.md Normal file
View File

@@ -0,0 +1,265 @@
# Codex CLI Review Scripts
AI-powered code review and security review scripts using OpenAI's Codex CLI.
These scripts provide **independent** code analysis separate from Claude sessions, giving you a second AI perspective on code changes to catch issues that might be missed.
## Prerequisites
```bash
# Install Codex CLI
npm i -g @openai/codex
# Verify installation
codex --version
# Authenticate (first run)
codex # Will prompt for ChatGPT account or API key
# Verify jq is installed (for JSON processing)
jq --version
```
## Scripts
### `codex-code-review.sh`
General code quality review focusing on:
- **Correctness** — logic errors, edge cases, error handling
- **Code Quality** — complexity, duplication, naming, dead code
- **Testing** — coverage, test quality
- **Performance** — N+1 queries, blocking operations, resource cleanup
- **Dependencies** — deprecated packages
- **Documentation** — comments, public API docs
**Output:** Structured JSON with findings categorized as `blocker`, `should-fix`, or `suggestion`.
### `codex-security-review.sh`
Security vulnerability review focusing on:
- **OWASP Top 10** — injection, broken auth, XSS, CSRF, SSRF, etc.
- **Secrets Detection** — hardcoded credentials, API keys, tokens
- **Injection Flaws** — SQL, NoSQL, OS command, LDAP
- **Auth/Authz Gaps** — missing checks, privilege escalation, IDOR
- **Data Exposure** — logging sensitive data, information disclosure
- **Supply Chain** — vulnerable dependencies, typosquatting
**Output:** Structured JSON with findings categorized as `critical`, `high`, `medium`, or `low` with CWE IDs and OWASP categories.
## Usage
### Review Uncommitted Changes
```bash
# Code review
~/.mosaic/rails/codex/codex-code-review.sh --uncommitted
# Security review
~/.mosaic/rails/codex/codex-security-review.sh --uncommitted
```
### Review a Pull Request
```bash
# Review and post findings as a PR comment
~/.mosaic/rails/codex/codex-code-review.sh -n 42
# Security review and post to PR
~/.mosaic/rails/codex/codex-security-review.sh -n 42
```
### Review Against Base Branch
```bash
# Code review changes vs main
~/.mosaic/rails/codex/codex-code-review.sh -b main
# Security review changes vs develop
~/.mosaic/rails/codex/codex-security-review.sh -b develop
```
### Review a Specific Commit
```bash
~/.mosaic/rails/codex/codex-code-review.sh -c abc123f
~/.mosaic/rails/codex/codex-security-review.sh -c abc123f
```
### Save Results to File
```bash
# Save JSON output
~/.mosaic/rails/codex/codex-code-review.sh --uncommitted -o review-results.json
~/.mosaic/rails/codex/codex-security-review.sh --uncommitted -o security-results.json
```
## Options
Both scripts support the same options:
| Option | Description |
|--------|-------------|
| `-n, --pr <number>` | PR number (auto-enables posting to PR) |
| `-b, --base <branch>` | Base branch to diff against (default: main) |
| `-c, --commit <sha>` | Review a specific commit |
| `-o, --output <path>` | Write JSON results to file |
| `--post-to-pr` | Post findings as PR comment (requires -n) |
| `--uncommitted` | Review uncommitted changes (staged + unstaged + untracked) |
| `-h, --help` | Show help |
## Woodpecker CI Integration
Automated PR reviews in CI pipelines.
### Setup
1. **Copy the pipeline template to your repo:**
```bash
cp ~/.mosaic/rails/codex/woodpecker/codex-review.yml your-repo/.woodpecker/
```
2. **Copy the schemas directory:**
```bash
cp -r ~/.mosaic/rails/codex/schemas your-repo/.woodpecker/
```
3. **Add Codex API key to Woodpecker:**
- Go to your repo in Woodpecker CI
- Settings → Secrets
- Add secret: `codex_api_key` with your OpenAI API key
4. **Commit and push:**
```bash
cd your-repo
git add .woodpecker/
git commit -m "feat: Add Codex AI review pipeline"
git push
```
### Pipeline Behavior
- **Triggers on:** Pull requests
- **Runs:** Code review + Security review in parallel
- **Fails if:**
- Code review finds blockers
- Security review finds critical or high severity issues
- **Outputs:** Structured JSON results in CI logs
## Output Format
### Code Review JSON
```json
{
"summary": "Overall assessment...",
"verdict": "approve|request-changes|comment",
"confidence": 0.85,
"findings": [
{
"severity": "blocker",
"title": "SQL injection vulnerability",
"file": "src/api/users.ts",
"line_start": 42,
"line_end": 45,
"description": "User input directly interpolated into SQL query",
"suggestion": "Use parameterized queries"
}
],
"stats": {
"files_reviewed": 5,
"blockers": 1,
"should_fix": 3,
"suggestions": 8
}
}
```
### Security Review JSON
```json
{
"summary": "Security assessment...",
"risk_level": "high",
"confidence": 0.90,
"findings": [
{
"severity": "high",
"title": "Hardcoded API key",
"file": "src/config.ts",
"line_start": 10,
"description": "API key hardcoded in source",
"cwe_id": "CWE-798",
"owasp_category": "A02:2021-Cryptographic Failures",
"remediation": "Move to environment variables or secrets manager"
}
],
"stats": {
"files_reviewed": 5,
"critical": 0,
"high": 1,
"medium": 2,
"low": 3
}
}
```
## Platform Support
Works with both **GitHub** and **Gitea** via the shared `~/.mosaic/rails/git/` infrastructure:
- Auto-detects platform from git remote
- Posts PR comments using `gh` (GitHub) or `tea` (Gitea)
- Unified interface across both platforms
## Architecture
```
codex-code-review.sh
codex-security-review.sh
common.sh
↓ sources
../git/detect-platform.sh (platform detection)
../git/pr-review.sh (post PR comments)
↓ uses
gh (GitHub) or tea (Gitea)
```
## Troubleshooting
### "codex: command not found"
```bash
npm i -g @openai/codex
```
### "jq: command not found"
```bash
# Arch Linux
sudo pacman -S jq
# Debian/Ubuntu
sudo apt install jq
```
### "Error: Not inside a git repository"
Run the script from inside a git repository.
### "No changes found to review"
The specified mode (--uncommitted, --base, etc.) found no changes to review.
### "Codex produced no output"
Check your Codex API key and authentication:
```bash
codex # Re-authenticate if needed
```
## Model Configuration
By default, scripts use the model configured in `~/.codex/config.toml`:
- **Model:** `gpt-5.3-codex` (recommended for code review)
- **Reasoning effort:** `high`
For best results, use `gpt-5.2-codex` or newer for strongest review accuracy.
## See Also
- `~/.mosaic/guides/code-review.md` — Manual code review checklist
- `~/.mosaic/rails/git/` — Git helper scripts (issue/PR management)
- OpenAI Codex CLI docs: https://developers.openai.com/codex/cli/

238
rails/codex/codex-code-review.sh Executable file
View File

@@ -0,0 +1,238 @@
#!/bin/bash
# codex-code-review.sh - Run an AI-powered code quality review using Codex CLI
# Usage: codex-code-review.sh [OPTIONS]
#
# Runs codex exec in read-only sandbox mode with a structured code review prompt.
# Outputs findings as JSON and optionally posts them to a PR.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
# Defaults
PR_NUMBER=""
BASE_BRANCH="main"
COMMIT_SHA=""
OUTPUT_FILE=""
POST_TO_PR=false
UNCOMMITTED=false
REVIEW_MODE=""
show_help() {
cat <<'EOF'
Usage: codex-code-review.sh [OPTIONS]
Run an AI-powered code quality review using OpenAI Codex CLI.
Options:
-n, --pr <number> PR number (auto-enables posting findings to PR)
-b, --base <branch> Base branch to diff against (default: main)
-c, --commit <sha> Review a specific commit
-o, --output <path> Write JSON results to file
--post-to-pr Post findings as PR comment (requires -n)
--uncommitted Review uncommitted changes (staged + unstaged + untracked)
-h, --help Show this help
Examples:
# Review uncommitted changes
codex-code-review.sh --uncommitted
# Review a PR and post findings as a comment
codex-code-review.sh -n 42
# Review changes against main, save JSON
codex-code-review.sh -b main -o review.json
# Review a specific commit
codex-code-review.sh -c abc123f
EOF
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-n|--pr)
PR_NUMBER="$2"
POST_TO_PR=true
REVIEW_MODE="pr"
shift 2
;;
-b|--base)
BASE_BRANCH="$2"
REVIEW_MODE="base"
shift 2
;;
-c|--commit)
COMMIT_SHA="$2"
REVIEW_MODE="commit"
shift 2
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
--post-to-pr)
POST_TO_PR=true
shift
;;
--uncommitted)
UNCOMMITTED=true
REVIEW_MODE="uncommitted"
shift
;;
-h|--help)
show_help
;;
*)
echo "Unknown option: $1" >&2
echo "Run with --help for usage" >&2
exit 1
;;
esac
done
# Validate
if [[ -z "$REVIEW_MODE" ]]; then
echo "Error: Specify a review mode: --uncommitted, --base <branch>, --commit <sha>, or --pr <number>" >&2
exit 1
fi
if [[ "$POST_TO_PR" == true && -z "$PR_NUMBER" ]]; then
echo "Error: --post-to-pr requires -n <pr_number>" >&2
exit 1
fi
check_codex
check_jq
# Verify we're in a git repo
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
echo "Error: Not inside a git repository" >&2
exit 1
fi
# Get the diff context
echo "Gathering diff context..." >&2
case "$REVIEW_MODE" in
uncommitted) DIFF_CONTEXT=$(build_diff_context "uncommitted" "") ;;
base) DIFF_CONTEXT=$(build_diff_context "base" "$BASE_BRANCH") ;;
commit) DIFF_CONTEXT=$(build_diff_context "commit" "$COMMIT_SHA") ;;
pr) DIFF_CONTEXT=$(build_diff_context "pr" "$PR_NUMBER") ;;
esac
if [[ -z "$DIFF_CONTEXT" ]]; then
echo "No changes found to review." >&2
exit 0
fi
# Build the review prompt
REVIEW_PROMPT=$(cat <<'PROMPT'
You are an expert code reviewer. Review the following code changes thoroughly.
Focus on issues that are ACTIONABLE and IMPORTANT. Do not flag trivial style issues.
## Review Checklist
### Correctness
- Code does what it claims to do
- Edge cases are handled
- Error conditions are managed properly
- No obvious bugs or logic errors
### Code Quality
- Functions are focused and reasonably sized
- No unnecessary complexity
- DRY - no significant duplication
- Clear naming for variables and functions
- No dead code or commented-out code
### Testing
- Tests exist for new functionality
- Tests cover happy path AND error cases
- No flaky tests introduced
### Performance
- No obvious N+1 queries
- No blocking operations in hot paths
- Resource cleanup (connections, file handles)
### Dependencies
- No deprecated packages
- No unnecessary new dependencies
### Documentation
- Complex logic has explanatory comments
- Public APIs are documented
## Severity Guide
- **blocker**: Must fix before merge (bugs, correctness issues, missing error handling)
- **should-fix**: Important but not blocking (code quality, minor issues)
- **suggestion**: Optional improvements (nice-to-haves)
Only report findings you are confident about (confidence > 0.7).
If the code looks good, say so — don't manufacture issues.
PROMPT
)
# Set up temp files for output and diff
TEMP_OUTPUT=$(mktemp /tmp/codex-review-XXXXXX.json)
TEMP_DIFF=$(mktemp /tmp/codex-diff-XXXXXX.txt)
trap 'rm -f "$TEMP_OUTPUT" "$TEMP_DIFF"' EXIT
SCHEMA_FILE="$SCRIPT_DIR/schemas/code-review-schema.json"
# Write diff to temp file
echo "$DIFF_CONTEXT" > "$TEMP_DIFF"
echo "Running Codex code review..." >&2
echo " Diff size: $(wc -l < "$TEMP_DIFF") lines" >&2
# Build full prompt with diff reference
FULL_PROMPT="${REVIEW_PROMPT}
Here are the code changes to review:
\`\`\`diff
$(cat "$TEMP_DIFF")
\`\`\`"
# Run codex exec with prompt from stdin to avoid arg length limits
echo "$FULL_PROMPT" | codex exec \
--sandbox read-only \
--output-schema "$SCHEMA_FILE" \
-o "$TEMP_OUTPUT" \
- 2>&1 | while IFS= read -r line; do
echo " [codex] $line" >&2
done
# Check output was produced
if [[ ! -s "$TEMP_OUTPUT" ]]; then
echo "Error: Codex produced no output" >&2
exit 1
fi
# Validate JSON
if ! jq empty "$TEMP_OUTPUT" 2>/dev/null; then
echo "Error: Codex output is not valid JSON" >&2
cat "$TEMP_OUTPUT" >&2
exit 1
fi
# Save output if requested
if [[ -n "$OUTPUT_FILE" ]]; then
cp "$TEMP_OUTPUT" "$OUTPUT_FILE"
echo "Results saved to: $OUTPUT_FILE" >&2
fi
# Post to PR if requested
if [[ "$POST_TO_PR" == true && -n "$PR_NUMBER" ]]; then
echo "Posting findings to PR #$PR_NUMBER..." >&2
post_to_pr "$PR_NUMBER" "$TEMP_OUTPUT" "code"
echo "Posted review to PR #$PR_NUMBER" >&2
fi
# Always print results to stdout
print_results "$TEMP_OUTPUT" "code"

View File

@@ -0,0 +1,235 @@
#!/bin/bash
# codex-security-review.sh - Run an AI-powered security vulnerability review using Codex CLI
# Usage: codex-security-review.sh [OPTIONS]
#
# Runs codex exec in read-only sandbox mode with a security-focused review prompt.
# Outputs findings as JSON and optionally posts them to a PR.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
# Defaults
PR_NUMBER=""
BASE_BRANCH="main"
COMMIT_SHA=""
OUTPUT_FILE=""
POST_TO_PR=false
UNCOMMITTED=false
REVIEW_MODE=""
show_help() {
cat <<'EOF'
Usage: codex-security-review.sh [OPTIONS]
Run an AI-powered security vulnerability review using OpenAI Codex CLI.
Options:
-n, --pr <number> PR number (auto-enables posting findings to PR)
-b, --base <branch> Base branch to diff against (default: main)
-c, --commit <sha> Review a specific commit
-o, --output <path> Write JSON results to file
--post-to-pr Post findings as PR comment (requires -n)
--uncommitted Review uncommitted changes (staged + unstaged + untracked)
-h, --help Show this help
Examples:
# Security review uncommitted changes
codex-security-review.sh --uncommitted
# Security review a PR and post findings
codex-security-review.sh -n 42
# Security review against main, save JSON
codex-security-review.sh -b main -o security.json
# Security review a specific commit
codex-security-review.sh -c abc123f
EOF
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-n|--pr)
PR_NUMBER="$2"
POST_TO_PR=true
REVIEW_MODE="pr"
shift 2
;;
-b|--base)
BASE_BRANCH="$2"
REVIEW_MODE="base"
shift 2
;;
-c|--commit)
COMMIT_SHA="$2"
REVIEW_MODE="commit"
shift 2
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
--post-to-pr)
POST_TO_PR=true
shift
;;
--uncommitted)
UNCOMMITTED=true
REVIEW_MODE="uncommitted"
shift
;;
-h|--help)
show_help
;;
*)
echo "Unknown option: $1" >&2
echo "Run with --help for usage" >&2
exit 1
;;
esac
done
# Validate
if [[ -z "$REVIEW_MODE" ]]; then
echo "Error: Specify a review mode: --uncommitted, --base <branch>, --commit <sha>, or --pr <number>" >&2
exit 1
fi
if [[ "$POST_TO_PR" == true && -z "$PR_NUMBER" ]]; then
echo "Error: --post-to-pr requires -n <pr_number>" >&2
exit 1
fi
check_codex
check_jq
# Verify we're in a git repo
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
echo "Error: Not inside a git repository" >&2
exit 1
fi
# Get the diff context
echo "Gathering diff context..." >&2
case "$REVIEW_MODE" in
uncommitted) DIFF_CONTEXT=$(build_diff_context "uncommitted" "") ;;
base) DIFF_CONTEXT=$(build_diff_context "base" "$BASE_BRANCH") ;;
commit) DIFF_CONTEXT=$(build_diff_context "commit" "$COMMIT_SHA") ;;
pr) DIFF_CONTEXT=$(build_diff_context "pr" "$PR_NUMBER") ;;
esac
if [[ -z "$DIFF_CONTEXT" ]]; then
echo "No changes found to review." >&2
exit 0
fi
# Build the security review prompt
REVIEW_PROMPT=$(cat <<'PROMPT'
You are an expert application security engineer performing a security-focused code review.
Your goal is to identify vulnerabilities, security anti-patterns, and data exposure risks.
## Security Review Scope
### OWASP Top 10 (2021)
- A01: Broken Access Control — missing authorization checks, IDOR, privilege escalation
- A02: Cryptographic Failures — weak algorithms, plaintext secrets, missing encryption
- A03: Injection — SQL, NoSQL, OS command, LDAP, XPath injection
- A04: Insecure Design — missing threat modeling, unsafe business logic
- A05: Security Misconfiguration — debug mode, default credentials, unnecessary features
- A06: Vulnerable Components — known CVEs in dependencies
- A07: Authentication Failures — weak auth, missing MFA, session issues
- A08: Data Integrity Failures — deserialization, unsigned updates
- A09: Logging Failures — sensitive data in logs, missing audit trails
- A10: SSRF — unvalidated URLs, internal service access
### Additional Checks
- Hardcoded secrets, API keys, tokens, passwords
- Insecure direct object references
- Missing input validation at trust boundaries
- Cross-Site Scripting (XSS) — reflected, stored, DOM-based
- Cross-Site Request Forgery (CSRF) protection
- Insecure file handling (path traversal, unrestricted upload)
- Race conditions and TOCTOU vulnerabilities
- Information disclosure (stack traces, verbose errors)
- Supply chain risks (typosquatting, dependency confusion)
## Severity Guide
- **critical**: Exploitable vulnerability with immediate impact (RCE, auth bypass, data breach)
- **high**: Significant vulnerability requiring prompt fix (injection, XSS, secrets exposure)
- **medium**: Vulnerability with limited exploitability or impact (missing headers, weak config)
- **low**: Minor security concern or hardening opportunity (informational, defense-in-depth)
## Rules
- Include CWE IDs when applicable
- Include OWASP category when applicable
- Provide specific remediation steps for every finding
- Only report findings you are confident about
- Do NOT flag non-security code quality issues
- If no security issues found, say so clearly
PROMPT
)
# Set up temp files for output and diff
TEMP_OUTPUT=$(mktemp /tmp/codex-security-XXXXXX.json)
TEMP_DIFF=$(mktemp /tmp/codex-diff-XXXXXX.txt)
trap 'rm -f "$TEMP_OUTPUT" "$TEMP_DIFF"' EXIT
SCHEMA_FILE="$SCRIPT_DIR/schemas/security-review-schema.json"
# Write diff to temp file
echo "$DIFF_CONTEXT" > "$TEMP_DIFF"
echo "Running Codex security review..." >&2
echo " Diff size: $(wc -l < "$TEMP_DIFF") lines" >&2
# Build full prompt with diff reference
FULL_PROMPT="${REVIEW_PROMPT}
Here are the code changes to security review:
\`\`\`diff
$(cat "$TEMP_DIFF")
\`\`\`"
# Run codex exec with prompt from stdin to avoid arg length limits
echo "$FULL_PROMPT" | codex exec \
--sandbox read-only \
--output-schema "$SCHEMA_FILE" \
-o "$TEMP_OUTPUT" \
- 2>&1 | while IFS= read -r line; do
echo " [codex] $line" >&2
done
# Check output was produced
if [[ ! -s "$TEMP_OUTPUT" ]]; then
echo "Error: Codex produced no output" >&2
exit 1
fi
# Validate JSON
if ! jq empty "$TEMP_OUTPUT" 2>/dev/null; then
echo "Error: Codex output is not valid JSON" >&2
cat "$TEMP_OUTPUT" >&2
exit 1
fi
# Save output if requested
if [[ -n "$OUTPUT_FILE" ]]; then
cp "$TEMP_OUTPUT" "$OUTPUT_FILE"
echo "Results saved to: $OUTPUT_FILE" >&2
fi
# Post to PR if requested
if [[ "$POST_TO_PR" == true && -n "$PR_NUMBER" ]]; then
echo "Posting findings to PR #$PR_NUMBER..." >&2
post_to_pr "$PR_NUMBER" "$TEMP_OUTPUT" "security"
echo "Posted security review to PR #$PR_NUMBER" >&2
fi
# Always print results to stdout
print_results "$TEMP_OUTPUT" "security"

191
rails/codex/common.sh Executable file
View File

@@ -0,0 +1,191 @@
#!/bin/bash
# common.sh - Shared utilities for Codex review scripts
# Source this file from review scripts: source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
set -e
CODEX_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GIT_SCRIPT_DIR="$CODEX_SCRIPT_DIR/../git"
# Source platform detection
source "$GIT_SCRIPT_DIR/detect-platform.sh"
# Check codex is installed
check_codex() {
if ! command -v codex &>/dev/null; then
echo "Error: codex CLI not found. Install with: npm i -g @openai/codex" >&2
exit 1
fi
}
# Check jq is installed (needed for JSON processing)
check_jq() {
if ! command -v jq &>/dev/null; then
echo "Error: jq not found. Install with your package manager." >&2
exit 1
fi
}
# Build the codex exec command args for the review mode
# Arguments: $1=mode (--uncommitted|--base|--commit), $2=value (branch/sha)
build_diff_context() {
local mode="$1"
local value="$2"
local diff_text=""
case "$mode" in
uncommitted)
diff_text=$(git diff HEAD 2>/dev/null; git diff --cached 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null | while read -r f; do echo "=== NEW FILE: $f ==="; cat "$f" 2>/dev/null; done)
;;
base)
diff_text=$(git diff "${value}...HEAD" 2>/dev/null)
;;
commit)
diff_text=$(git show "$value" 2>/dev/null)
;;
pr)
# For PRs, we need to fetch the PR diff
detect_platform
if [[ "$PLATFORM" == "github" ]]; then
diff_text=$(gh pr diff "$value" 2>/dev/null)
elif [[ "$PLATFORM" == "gitea" ]]; then
# tea doesn't have a direct pr diff command, use git
local pr_base
pr_base=$(tea pr list --fields index,base --output simple 2>/dev/null | grep "^${value}" | awk '{print $2}')
if [[ -n "$pr_base" ]]; then
diff_text=$(git diff "${pr_base}...HEAD" 2>/dev/null)
else
# Fallback: fetch PR info via API
local repo_info
repo_info=$(get_repo_info)
local remote_url
remote_url=$(git remote get-url origin 2>/dev/null)
local host
host=$(echo "$remote_url" | sed -E 's|.*://([^/]+).*|\1|; s|.*@([^:]+).*|\1|')
diff_text=$(curl -s "https://${host}/api/v1/repos/${repo_info}/pulls/${value}" \
-H "Authorization: token $(tea login list --output simple 2>/dev/null | head -1 | awk '{print $2}')" \
2>/dev/null | jq -r '.diff_url // empty')
if [[ -n "$diff_text" && "$diff_text" != "null" ]]; then
diff_text=$(curl -s "$diff_text" 2>/dev/null)
else
diff_text=$(git diff "main...HEAD" 2>/dev/null)
fi
fi
fi
;;
esac
echo "$diff_text"
}
# Format JSON findings as markdown for PR comments
# Arguments: $1=json_file, $2=review_type (code|security)
format_findings_as_markdown() {
local json_file="$1"
local review_type="$2"
if [[ ! -f "$json_file" ]]; then
echo "Error: JSON file not found: $json_file" >&2
return 1
fi
local summary verdict confidence
summary=$(jq -r '.summary' "$json_file")
confidence=$(jq -r '.confidence' "$json_file")
if [[ "$review_type" == "code" ]]; then
verdict=$(jq -r '.verdict' "$json_file")
local blockers should_fix suggestions files_reviewed
blockers=$(jq -r '.stats.blockers' "$json_file")
should_fix=$(jq -r '.stats.should_fix' "$json_file")
suggestions=$(jq -r '.stats.suggestions' "$json_file")
files_reviewed=$(jq -r '.stats.files_reviewed' "$json_file")
cat <<EOF
## Codex Code Review
**Verdict:** ${verdict} | **Confidence:** ${confidence} | **Files reviewed:** ${files_reviewed}
**Findings:** ${blockers} blockers, ${should_fix} should-fix, ${suggestions} suggestions
### Summary
${summary}
EOF
else
local risk_level critical high medium low files_reviewed
risk_level=$(jq -r '.risk_level' "$json_file")
critical=$(jq -r '.stats.critical' "$json_file")
high=$(jq -r '.stats.high' "$json_file")
medium=$(jq -r '.stats.medium' "$json_file")
low=$(jq -r '.stats.low' "$json_file")
files_reviewed=$(jq -r '.stats.files_reviewed' "$json_file")
cat <<EOF
## Codex Security Review
**Risk Level:** ${risk_level} | **Confidence:** ${confidence} | **Files reviewed:** ${files_reviewed}
**Findings:** ${critical} critical, ${high} high, ${medium} medium, ${low} low
### Summary
${summary}
EOF
fi
# Output findings
local finding_count
finding_count=$(jq '.findings | length' "$json_file")
if [[ "$finding_count" -gt 0 ]]; then
echo "### Findings"
echo ""
jq -r '.findings[] | "#### [\(.severity | ascii_upcase)] \(.title)\n- **File:** `\(.file)`\(if .line_start then " (L\(.line_start)\(if .line_end and .line_end != .line_start then "-L\(.line_end)" else "" end))" else "" end)\n- \(.description)\(if .suggestion then "\n- **Suggestion:** \(.suggestion)" else "" end)\(if .cwe_id then "\n- **CWE:** \(.cwe_id)" else "" end)\(if .owasp_category then "\n- **OWASP:** \(.owasp_category)" else "" end)\(if .remediation then "\n- **Remediation:** \(.remediation)" else "" end)\n"' "$json_file"
else
echo "*No issues found.*"
fi
echo "---"
echo "*Reviewed by Codex ($(codex --version 2>/dev/null || echo "unknown"))*"
}
# Post review findings to a PR
# Arguments: $1=pr_number, $2=json_file, $3=review_type (code|security)
post_to_pr() {
local pr_number="$1"
local json_file="$2"
local review_type="$3"
local markdown
markdown=$(format_findings_as_markdown "$json_file" "$review_type")
detect_platform
# Determine review action based on findings
local action="comment"
if [[ "$review_type" == "code" ]]; then
local verdict
verdict=$(jq -r '.verdict' "$json_file")
action="$verdict"
else
local risk_level
risk_level=$(jq -r '.risk_level' "$json_file")
case "$risk_level" in
critical|high) action="request-changes" ;;
medium) action="comment" ;;
low|none) action="comment" ;;
esac
fi
# Post the review
"$GIT_SCRIPT_DIR/pr-review.sh" -n "$pr_number" -a "$action" -c "$markdown"
}
# Print review results to stdout
# Arguments: $1=json_file, $2=review_type (code|security)
print_results() {
local json_file="$1"
local review_type="$2"
format_findings_as_markdown "$json_file" "$review_type"
}

View File

@@ -0,0 +1,84 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"summary": {
"type": "string",
"description": "Brief overall assessment of the code changes"
},
"verdict": {
"type": "string",
"enum": ["approve", "request-changes", "comment"],
"description": "Overall review verdict"
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the review (0-1)"
},
"findings": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"severity": {
"type": "string",
"enum": ["blocker", "should-fix", "suggestion"],
"description": "Finding severity: blocker (must fix), should-fix (important), suggestion (optional)"
},
"title": {
"type": "string",
"description": "Short title describing the issue"
},
"file": {
"type": "string",
"description": "File path where the issue was found"
},
"line_start": {
"type": "integer",
"description": "Starting line number"
},
"line_end": {
"type": "integer",
"description": "Ending line number"
},
"description": {
"type": "string",
"description": "Detailed explanation of the issue"
},
"suggestion": {
"type": "string",
"description": "Suggested fix or improvement"
}
},
"required": ["severity", "title", "file", "line_start", "line_end", "description", "suggestion"]
}
},
"stats": {
"type": "object",
"additionalProperties": false,
"properties": {
"files_reviewed": {
"type": "integer",
"description": "Number of files reviewed"
},
"blockers": {
"type": "integer",
"description": "Count of blocker findings"
},
"should_fix": {
"type": "integer",
"description": "Count of should-fix findings"
},
"suggestions": {
"type": "integer",
"description": "Count of suggestion findings"
}
},
"required": ["files_reviewed", "blockers", "should_fix", "suggestions"]
}
},
"required": ["summary", "verdict", "confidence", "findings", "stats"]
}

View File

@@ -0,0 +1,96 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"summary": {
"type": "string",
"description": "Brief overall security assessment of the code changes"
},
"risk_level": {
"type": "string",
"enum": ["critical", "high", "medium", "low", "none"],
"description": "Overall security risk level"
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the review (0-1)"
},
"findings": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"severity": {
"type": "string",
"enum": ["critical", "high", "medium", "low"],
"description": "Vulnerability severity level"
},
"title": {
"type": "string",
"description": "Short title describing the vulnerability"
},
"file": {
"type": "string",
"description": "File path where the vulnerability was found"
},
"line_start": {
"type": "integer",
"description": "Starting line number"
},
"line_end": {
"type": "integer",
"description": "Ending line number"
},
"description": {
"type": "string",
"description": "Detailed explanation of the vulnerability"
},
"cwe_id": {
"type": "string",
"description": "CWE identifier if applicable (e.g., CWE-79)"
},
"owasp_category": {
"type": "string",
"description": "OWASP Top 10 category if applicable (e.g., A03:2021-Injection)"
},
"remediation": {
"type": "string",
"description": "Specific remediation steps to fix the vulnerability"
}
},
"required": ["severity", "title", "file", "line_start", "line_end", "description", "cwe_id", "owasp_category", "remediation"]
}
},
"stats": {
"type": "object",
"additionalProperties": false,
"properties": {
"files_reviewed": {
"type": "integer",
"description": "Number of files reviewed"
},
"critical": {
"type": "integer",
"description": "Count of critical findings"
},
"high": {
"type": "integer",
"description": "Count of high findings"
},
"medium": {
"type": "integer",
"description": "Count of medium findings"
},
"low": {
"type": "integer",
"description": "Count of low findings"
}
},
"required": ["files_reviewed", "critical", "high", "medium", "low"]
}
},
"required": ["summary", "risk_level", "confidence", "findings", "stats"]
}

View File

@@ -0,0 +1,90 @@
# Codex AI Review Pipeline for Woodpecker CI
# Drop this into your repo's .woodpecker/ directory to enable automated
# code and security reviews on every pull request.
#
# Required secrets:
# - codex_api_key: OpenAI API key or Codex-compatible key
#
# Optional secrets:
# - gitea_token: Gitea API token for posting PR comments (if not using tea CLI auth)
when:
event: pull_request
variables:
- &node_image "node:22-slim"
- &install_codex "npm i -g @openai/codex"
steps:
# --- Code Quality Review ---
code-review:
image: *node_image
environment:
CODEX_API_KEY:
from_secret: codex_api_key
commands:
- *install_codex
- apt-get update -qq && apt-get install -y -qq jq git > /dev/null 2>&1
# Generate the diff
- git fetch origin ${CI_COMMIT_TARGET_BRANCH:-main}
- DIFF=$(git diff origin/${CI_COMMIT_TARGET_BRANCH:-main}...HEAD)
# Run code review with structured output
- |
codex exec \
--sandbox read-only \
--output-schema .woodpecker/schemas/code-review-schema.json \
-o /tmp/code-review.json \
"You are an expert code reviewer. Review the following code changes for correctness, code quality, testing, performance, and documentation issues. Only flag actionable, important issues. Categorize as blocker/should-fix/suggestion. If code looks good, say so.
Changes:
$DIFF"
# Output summary
- echo "=== Code Review Results ==="
- jq '.' /tmp/code-review.json
- |
BLOCKERS=$(jq '.stats.blockers // 0' /tmp/code-review.json)
if [ "$BLOCKERS" -gt 0 ]; then
echo "FAIL: $BLOCKERS blocker(s) found"
exit 1
fi
echo "PASS: No blockers found"
# --- Security Review ---
security-review:
image: *node_image
environment:
CODEX_API_KEY:
from_secret: codex_api_key
commands:
- *install_codex
- apt-get update -qq && apt-get install -y -qq jq git > /dev/null 2>&1
# Generate the diff
- git fetch origin ${CI_COMMIT_TARGET_BRANCH:-main}
- DIFF=$(git diff origin/${CI_COMMIT_TARGET_BRANCH:-main}...HEAD)
# Run security review with structured output
- |
codex exec \
--sandbox read-only \
--output-schema .woodpecker/schemas/security-review-schema.json \
-o /tmp/security-review.json \
"You are an expert application security engineer. Review the following code changes for security vulnerabilities including OWASP Top 10, hardcoded secrets, injection flaws, auth/authz gaps, XSS, CSRF, SSRF, path traversal, and supply chain risks. Include CWE IDs and remediation steps. Only flag real security issues, not code quality.
Changes:
$DIFF"
# Output summary
- echo "=== Security Review Results ==="
- jq '.' /tmp/security-review.json
- |
CRITICAL=$(jq '.stats.critical // 0' /tmp/security-review.json)
HIGH=$(jq '.stats.high // 0' /tmp/security-review.json)
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
echo "FAIL: $CRITICAL critical, $HIGH high severity finding(s)"
exit 1
fi
echo "PASS: No critical or high severity findings"