chore: consolidate new foundation and archive v1 (#1495)

This commit is contained in:
2026-09-07 12:32:57 -05:00
3511 changed files with 727899 additions and 10 deletions
@@ -0,0 +1,281 @@
# 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
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
# Security review
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
```
### Review a Pull Request
```bash
# Review and post findings as a PR comment
~/.config/mosaic/tools/codex/codex-code-review.sh -n 42
# Security review and post to PR
~/.config/mosaic/tools/codex/codex-security-review.sh -n 42
```
PR mode resolves the provider's PR diff rather than relying on the caller's checked-out branch. On Gitea, it fetches the base and `refs/pull/<number>/head` refs and diffs those explicit refs. If the refs cannot be fetched or the resulting diff is empty, the command exits nonzero before Codex runs or a review is posted.
### Review Against Base Branch
```bash
# Code review changes vs main
~/.config/mosaic/tools/codex/codex-code-review.sh -b main
# Security review changes vs develop
~/.config/mosaic/tools/codex/codex-security-review.sh -b develop
```
### Review a Specific Commit
```bash
~/.config/mosaic/tools/codex/codex-code-review.sh -c abc123f
~/.config/mosaic/tools/codex/codex-security-review.sh -c abc123f
```
### Save Results to File
```bash
# Save JSON output
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted -o review-results.json
~/.config/mosaic/tools/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 ~/.config/mosaic/tools/codex/woodpecker/codex-review.yml your-repo/.woodpecker/
```
2. **Copy the schemas directory:**
```bash
cp -r ~/.config/mosaic/tools/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.9,
"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 `~/.config/mosaic/tools/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 non-PR mode (`--uncommitted`, `--base`, etc.) found no changes to review. PR mode instead fails closed with an actionable error when it cannot construct a non-empty provider diff; verify the PR number, remote, provider login, and ref access before retrying.
### "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
- `~/.config/mosaic/guides/CODE-REVIEW.md` — Manual code review checklist
- `~/.config/mosaic/tools/git/` — Git helper scripts (issue/PR management)
- OpenAI Codex CLI docs: https://developers.openai.com/codex/cli/
@@ -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"
@@ -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"
+200
View File
@@ -0,0 +1,200 @@
#!/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)
# Provider detection writes its result to stdout; suppress it so it cannot
# be mistaken for diff content when this function is used in a substitution.
detect_platform >/dev/null
if [[ "$PLATFORM" == "github" ]]; then
diff_text=$(gh pr diff "$value" 2>/dev/null) || {
echo "Error: Failed to fetch the diff for PR #${value}." >&2
return 1
}
elif [[ "$PLATFORM" == "gitea" ]]; then
local pr_base base_ref pr_head_ref
pr_base=$(tea pr list --fields index,base --output simple 2>/dev/null | awk -v pr="$value" '$1 == pr { print $2; exit }')
if [[ -z "$pr_base" ]]; then
echo "Error: Could not resolve the base branch for Gitea PR #${value}." >&2
return 1
fi
base_ref="refs/remotes/origin/${pr_base}"
pr_head_ref="refs/remotes/origin/pr/${value}/head"
if ! git fetch --quiet origin \
"+refs/heads/${pr_base}:${base_ref}" \
"+refs/pull/${value}/head:${pr_head_ref}"; then
echo "Error: Failed to fetch the base and head refs for Gitea PR #${value}." >&2
return 1
fi
diff_text=$(git diff "${base_ref}...${pr_head_ref}") || {
echo "Error: Failed to diff the fetched refs for Gitea PR #${value}." >&2
return 1
}
else
echo "Error: Unsupported git platform while resolving PR #${value}." >&2
return 1
fi
;;
esac
if [[ "$mode" == "pr" && -z "${diff_text//[[:space:]]/}" ]]; then
echo "Error: Unable to construct a non-empty diff for PR #${value}; verify the PR refs and provider access." >&2
return 1
fi
printf '%s\n' "$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"
}
@@ -0,0 +1,92 @@
{
"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"]
}
@@ -0,0 +1,106 @@
{
"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"]
}
@@ -0,0 +1,158 @@
#!/bin/bash
# Hermetic regression coverage for Gitea PR diff construction and fail-closed reviews.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "not ok - $*" >&2
exit 1
}
assert_contains() {
local haystack="$1" needle="$2"
if [[ "$haystack" != *"$needle"* ]]; then
printf 'actual output:\n%s\n' "$haystack" >&2
fail "expected output to contain: $needle"
fi
}
# Prevent CI-provided repository context from leaking into the fixture repositories.
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_OBJECT_DIRECTORY \
GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_COMMON_DIR
export GIT_AUTHOR_NAME="Codex Fixture"
export GIT_AUTHOR_EMAIL="[email protected]"
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GITEA_LOGIN="fixture"
export GITEA_TOKEN="fixture-token"
export GITEA_URL="file://$TMP_DIR"
create_pr_fixture() {
local fixture_root="$1" head_mode="$2"
local origin="$fixture_root/origin.git"
local seed="$fixture_root/seed"
local work="$fixture_root/work"
local base_sha head_sha
mkdir -p "$fixture_root"
git init --quiet --bare "$origin"
git init --quiet --initial-branch=release/next "$seed"
printf 'base\n' > "$seed/pr-change.ts"
git -C "$seed" add pr-change.ts
git -C "$seed" commit --quiet -m "fixture base"
base_sha=$(git -C "$seed" rev-parse HEAD)
git -C "$seed" remote add origin "$origin"
git -C "$seed" push --quiet origin release/next
git --git-dir="$origin" symbolic-ref HEAD refs/heads/release/next
if [[ "$head_mode" == "changed" ]]; then
git -C "$seed" switch --quiet -c feature/pr-795
printf 'actual-pr-change\n' > "$seed/pr-change.ts"
git -C "$seed" commit --quiet -am "fixture PR head"
head_sha=$(git -C "$seed" rev-parse HEAD)
git -C "$seed" push --quiet origin HEAD:refs/pull/795/head
else
head_sha="$base_sha"
git --git-dir="$origin" update-ref refs/pull/795/head "$head_sha"
fi
# Gitea's provider-owned PR head ref now exists in the local bare origin.
git clone --quiet "$origin" "$work"
printf '%s\n' "$work"
}
FAKE_BIN="$TMP_DIR/bin"
mkdir -p "$FAKE_BIN"
cat > "$FAKE_BIN/tea" <<'STUB'
#!/bin/bash
if [[ "$*" == "pr list --fields index,base --output simple" ]]; then
printf '795 release/next\n'
exit 0
fi
exit 1
STUB
cat > "$FAKE_BIN/codex" <<'STUB'
#!/bin/bash
printf 'CODEX %s\n' "$*" >> "$CODEX_LOG"
exit 99
STUB
chmod +x "$FAKE_BIN/tea" "$FAKE_BIN/codex"
export PATH="$FAKE_BIN:$PATH"
# The valid fixture is a fresh clone on the non-main base. The PR head exists only
# at refs/pull/795/head, so local HEAD cannot accidentally satisfy the assertion.
if [[ "${1:-all}" != "fail-closed" ]]; then
VALID_WORK=$(create_pr_fixture "$TMP_DIR/valid" changed)
(
cd "$VALID_WORK"
# shellcheck source=common.sh
source "$SCRIPT_DIR/common.sh"
diff_context=$(build_diff_context pr 795)
assert_contains "$diff_context" "actual-pr-change"
base_sha=$(git rev-parse refs/remotes/origin/release/next)
head_sha=$(git rev-parse refs/remotes/origin/pr/795/head)
local_sha=$(git rev-parse HEAD)
[[ "$local_sha" == "$base_sha" ]] || fail "fixture clone is not on the PR base"
[[ "$head_sha" != "$base_sha" ]] || fail "fixture PR head does not differ from its base"
git show-ref --verify --quiet refs/remotes/origin/pr/795/head || \
fail "fetched PR head ref is missing"
[[ "$(git diff --name-only "${base_sha}...${head_sha}")" == "pr-change.ts" ]] || \
fail "explicit PR refs do not contain the fixture change"
if git show-ref --verify --quiet refs/heads/main || \
git show-ref --verify --quiet refs/remotes/origin/main; then
fail "fixture unexpectedly contains a main ref"
fi
)
echo "ok - Gitea PR mode fetches and diffs explicit non-main base and PR head refs"
fi
# Build an empty PR entirely inside another local repository. Both review wrappers
# must emit the PR-numbered error before Codex or the stubbed post path can execute.
if [[ "${1:-all}" != "pr-head" ]]; then
EMPTY_WORK=$(create_pr_fixture "$TMP_DIR/empty" empty)
SANDBOX="$TMP_DIR/sandbox"
mkdir -p "$SANDBOX/tools/codex/schemas" "$SANDBOX/tools/git"
cp "$SCRIPT_DIR/common.sh" \
"$SCRIPT_DIR/codex-code-review.sh" \
"$SCRIPT_DIR/codex-security-review.sh" \
"$SANDBOX/tools/codex/"
cp "$SCRIPT_DIR/schemas/code-review-schema.json" \
"$SCRIPT_DIR/schemas/security-review-schema.json" \
"$SANDBOX/tools/codex/schemas/"
cp "$SCRIPT_DIR/../git/detect-platform.sh" "$SANDBOX/tools/git/"
cat > "$SANDBOX/tools/git/pr-review.sh" <<'STUB'
#!/bin/bash
printf 'POST %s\n' "$*" >> "$POST_LOG"
STUB
chmod +x "$SANDBOX/tools/git/pr-review.sh"
POST_LOG="$TMP_DIR/post.log"
CODEX_LOG="$TMP_DIR/codex.log"
export POST_LOG CODEX_LOG
for review_kind in code security; do
: > "$POST_LOG"
: > "$CODEX_LOG"
review_script="$SANDBOX/tools/codex/codex-${review_kind}-review.sh"
set +e
(
cd "$EMPTY_WORK"
"$review_script" -n 795
) >"$TMP_DIR/${review_kind}.stdout" 2>"$TMP_DIR/${review_kind}.stderr"
review_status=$?
set -e
stderr_text=$(cat "$TMP_DIR/${review_kind}.stderr")
[[ "$review_status" -ne 0 ]] || fail "${review_kind} review returned success for an empty PR diff"
[[ ! -s "$CODEX_LOG" ]] || fail "Codex ran for an empty ${review_kind} PR diff"
[[ ! -s "$POST_LOG" ]] || fail "${review_kind} review auto-post ran for an empty PR diff"
assert_contains "$stderr_text" "Error:"
assert_contains "$stderr_text" "PR #795"
echo "ok - empty ${review_kind} PR diff fails closed before Codex and auto-post"
done
fi
@@ -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"