feat(ci): add Codex AI review pipeline for Woodpecker
Adds automated code quality and security review pipeline that runs on pull requests using OpenAI Codex with structured output schemas. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
120
.woodpecker/README.md
Normal file
120
.woodpecker/README.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# Woodpecker CI Configuration for Mosaic Stack
|
||||||
|
|
||||||
|
## Codex AI Review Pipeline
|
||||||
|
|
||||||
|
This directory contains the Codex AI review pipeline configuration for automated code and security reviews on pull requests.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. **Add Codex API key to Woodpecker:**
|
||||||
|
- Go to mosaic-stack repo at `https://ci.mosaicstack.dev`
|
||||||
|
- Settings → Secrets
|
||||||
|
- Add secret: `codex_api_key` with your OpenAI API key
|
||||||
|
|
||||||
|
2. **Enable the pipeline:**
|
||||||
|
- The `codex-review.yml` pipeline will automatically run on all PRs
|
||||||
|
- The main `.woodpecker.yml` handles primary CI tasks
|
||||||
|
- This codex pipeline is independent and focused solely on reviews
|
||||||
|
|
||||||
|
### What Gets Reviewed
|
||||||
|
|
||||||
|
**Code Review (`code-review` step):**
|
||||||
|
|
||||||
|
- Correctness — logic errors, edge cases, error handling
|
||||||
|
- Code Quality — complexity, duplication, naming
|
||||||
|
- Testing — coverage, test quality
|
||||||
|
- Performance — N+1 queries, blocking ops
|
||||||
|
- Dependencies — deprecated packages
|
||||||
|
- Documentation — comments, API docs
|
||||||
|
|
||||||
|
**Security Review (`security-review` step):**
|
||||||
|
|
||||||
|
- OWASP Top 10 vulnerabilities
|
||||||
|
- Hardcoded secrets/credentials
|
||||||
|
- Injection flaws (SQL, NoSQL, OS command)
|
||||||
|
- XSS, CSRF, SSRF
|
||||||
|
- Auth/authz gaps
|
||||||
|
- Data exposure in logs
|
||||||
|
|
||||||
|
### Pipeline Behavior
|
||||||
|
|
||||||
|
- **Triggers:** Every pull request
|
||||||
|
- **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
|
||||||
|
|
||||||
|
### Local Testing
|
||||||
|
|
||||||
|
Test the review scripts locally before pushing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Code review of uncommitted changes
|
||||||
|
~/.claude/scripts/codex/codex-code-review.sh --uncommitted
|
||||||
|
|
||||||
|
# Security review of uncommitted changes
|
||||||
|
~/.claude/scripts/codex/codex-security-review.sh --uncommitted
|
||||||
|
|
||||||
|
# Code review against main branch
|
||||||
|
~/.claude/scripts/codex/codex-code-review.sh -b main
|
||||||
|
|
||||||
|
# Security review and save JSON
|
||||||
|
~/.claude/scripts/codex/codex-security-review.sh -b main -o security.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schema Files
|
||||||
|
|
||||||
|
The `schemas/` directory contains JSON schemas that enforce structured output from Codex:
|
||||||
|
|
||||||
|
- `code-review-schema.json` — Defines output for code quality reviews
|
||||||
|
- `security-review-schema.json` — Defines output for security reviews
|
||||||
|
|
||||||
|
These schemas ensure consistent, machine-readable findings that the CI pipeline can parse and fail on.
|
||||||
|
|
||||||
|
### Integration with Main Pipeline
|
||||||
|
|
||||||
|
The main `.woodpecker.yml` in the repo root handles:
|
||||||
|
|
||||||
|
- Type checking (TypeScript)
|
||||||
|
- Linting (ESLint)
|
||||||
|
- Unit tests (Vitest)
|
||||||
|
- Integration tests (Playwright)
|
||||||
|
- Docker image builds
|
||||||
|
|
||||||
|
This `codex-review.yml` is independent and focuses solely on:
|
||||||
|
|
||||||
|
- AI-powered code quality review
|
||||||
|
- AI-powered security vulnerability scanning
|
||||||
|
|
||||||
|
Both pipelines run in parallel on PRs.
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
**Pipeline fails with "codex: command not found"**
|
||||||
|
|
||||||
|
- Check that the node image in `codex-review.yml` matches a version with npm
|
||||||
|
- Current: `node:22-slim`
|
||||||
|
|
||||||
|
**Pipeline fails with auth errors**
|
||||||
|
|
||||||
|
- Verify `codex_api_key` secret is set in Woodpecker
|
||||||
|
- Test the key locally: `CODEX_API_KEY=<key> codex exec "test"`
|
||||||
|
|
||||||
|
**Pipeline passes but should fail**
|
||||||
|
|
||||||
|
- Check the failure conditions in `codex-review.yml`
|
||||||
|
- Current thresholds: blockers, critical, or high findings
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| ------------------------------------- | -------------------------------------- |
|
||||||
|
| `codex-review.yml` | Codex AI review pipeline configuration |
|
||||||
|
| `schemas/code-review-schema.json` | Code review output schema |
|
||||||
|
| `schemas/security-review-schema.json` | Security review output schema |
|
||||||
|
| `README.md` | This file |
|
||||||
|
|
||||||
|
## Parent CI Pipeline
|
||||||
|
|
||||||
|
The main `.woodpecker.yml` is located at the repository root and handles all build/test tasks.
|
||||||
90
.woodpecker/codex-review.yml
Normal file
90
.woodpecker/codex-review.yml
Normal 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"
|
||||||
92
.woodpecker/schemas/code-review-schema.json
Normal file
92
.woodpecker/schemas/code-review-schema.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
106
.woodpecker/schemas/security-review-schema.json
Normal file
106
.woodpecker/schemas/security-review-schema.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user