Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/ai-repo-guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# JetFormBuilder AI Repo Guidelines

This repository contains the Crocoblock **JetFormBuilder** WordPress plugin.

## Domain & architecture

- JetFormBuilder is a **visual form builder for WordPress**, tightly integrated with:
- Gutenberg editor (form blocks)
- Dynamic data from JetEngine (CCT, meta fields, relations)
- Actions system (insert post, register user, send email/webhook, etc.)
- Optional e-commerce / payments (WooCommerce checkout, payment gateways)

When reviewing or generating changes, the AI should:

- Prefer using existing **JetFormBuilder core APIs and abstractions** instead of ad-hoc logic.
- Keep backward compatibility for:
- Public form block attributes and structure.
- Public PHP classes/methods used by add-ons and snippets.
- Action/notification configuration formats.
- REST API endpoints and form submission endpoints.
- Be especially careful with:
- How form data is mapped to posts/users/CCT records.
- Validation and sanitization pipeline.
- Actions order and transactional behaviour (e.g. payment + insert post).
- Multi-step forms and conditional logic.

## Form processing & security

- All form submission endpoints **must be secure**:
- Always verify nonces to prevent CSRF.
- Always check capabilities for admin-only or sensitive actions (e.g. editing existing posts, user management).
- Treat all incoming form data as **untrusted**:
- Validate input types and ranges explicitly.
- Sanitize before saving to DB or using in queries/emails.
- Carefully handle:
- File uploads (allowed MIME types, file size limits, safe paths).
- Redirect URLs (do not allow open redirects without whitelisting).
- Webhooks and remote requests (timeouts, SSL verification, error handling).

## Coding standards

- Follow **WordPress Coding Standards (WPCS)**.
- Always:
- Escape output (`esc_html`, `esc_attr`, `wp_kses_post`, etc.) where content is rendered in templates, admin pages or Gutenberg controls.
- Sanitize input coming from `$_POST`, `$_GET`, JSON payloads before using or storing.
- Use nonces (`wp_nonce_field`, `wp_verify_nonce`) and capability checks (`current_user_can`) for any write or configuration-changing operations.
- Use `$wpdb->prepare()` for all manual SQL and respect JetFormBuilder table names / schemas.
- Maintain compatibility with the minimum supported versions of:
- PHP (see `readme.txt`/`composer.json`)
- WordPress core
- Gutenberg / block editor APIs used by JetFormBuilder.

## Performance & scalability

- Keep form rendering and submission **fast**:
- Avoid heavy DB queries in `render()` methods or block registration callbacks.
- Cache reusable configuration (form definitions, options) where possible.
- Prefer `WP_Query`/`get_posts` with `'fields' => 'ids'` when only IDs are needed.
- When integrating with JetEngine or external services:
- Avoid N+1 queries (e.g. querying CCT or meta per field repeatedly).
- Use batching and existing Jet APIs when possible.
- Be mindful of:
- Large forms with many fields and conditions.
- Sites with high submission volume (thousands of entries, heavy automations).

## Integrations & compatibility

- Do not break existing integrations:
- JetEngine dynamic data sources.
- WooCommerce / payment gateways.
- Common third-party actions (webhooks, CRMs, email services).
- When changing form schema, field types, or action settings:
- Ensure migrations are safe and idempotent.
- Preserve existing configuration where possible and provide graceful fallbacks.
- Respect multisite behaviour and multilingual setups (e.g. WPML/Polylang where relevant).

## Changelog conventions

- Changelog entries are short, user-focused descriptions of what changed in JetFormBuilder.
- Use a single bullet line with prefix:
- `- FIX: ...` for bug fixes
- `- ADD: ...` for new features
- `- IMPROVE: ...` for enhancements / refactors
- `- CHANGE: ...` for breaking or notable behaviour changes
- Mention:
- The affected area (e.g. "multi-step forms", "user registration action", "CCT form mapping", "Gutenberg block UI").
- Any important conditions (e.g. specific integrations, gateways, or edge cases).
249 changes: 249 additions & 0 deletions .github/scripts/ai-pr-review.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#!/usr/bin/env node
import OpenAI from "openai";
import fs from "node:fs/promises";

const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

const MODEL = process.env.AI_REVIEW_MODEL || "gpt-4.1-mini";

async function main() {
const eventPath = process.env.GITHUB_EVENT_PATH;

if (!eventPath) {
console.error("GITHUB_EVENT_PATH is not set.");
process.exit(1);
}

const event = JSON.parse(await fs.readFile(eventPath, "utf8"));

if (!event.pull_request) {
console.error("No pull_request in event payload.");
process.exit(0);
}

const pr = event.pull_request;
const repoFull = process.env.GITHUB_REPOSITORY;

if (!repoFull) {
console.error("GITHUB_REPOSITORY is not set.");
process.exit(1);
}

const [owner, repo] = repoFull.split("/");
const githubToken = process.env.GITHUB_TOKEN;

if (!githubToken) {
console.error("GITHUB_TOKEN is not set.");
process.exit(1);
}

// 1) Fetch changed files for this PR
const filesResp = await fetch(
`https://api.github.com/repos/${owner}/${repo}/pulls/${pr.number}/files?per_page=200`,
{
headers: {
Authorization: `Bearer ${githubToken}`,
"X-GitHub-Api-Version": "2022-11-28",
Accept: "application/vnd.github+json",
},
}
);

if (!filesResp.ok) {
console.error("Failed to fetch PR files:", filesResp.status, await filesResp.text());
process.exit(1);
}

const files = await filesResp.json();

// 2) Build a truncated diff summary
let diffSummary = "";
const maxFiles = 30;
const maxLinesPerFile = 200;

for (const f of files.slice(0, maxFiles)) {
if (!f.patch) continue;

const isRelevant =
f.filename.endsWith(".php") ||
f.filename.endsWith(".js") ||
f.filename.endsWith(".ts") ||
f.filename.endsWith(".tsx") ||
f.filename.endsWith(".jsx") ||
f.filename.endsWith(".css") ||
f.filename.endsWith(".scss");

if (!isRelevant) continue;

const patchLines = f.patch.split(/\r?\n/).slice(0, maxLinesPerFile).join("\n");

diffSummary += `
File: ${f.filename}
Status: ${f.status}

Patch (truncated):
${patchLines}

----------------------------------------`;
}

if (!diffSummary.trim()) {
diffSummary = "No relevant diff (PHP/JS/TS/CSS) or patch is empty.";
}

// 3) Read repo guidelines (plugin-specific instructions)
let repoGuidelines = "";
try {
repoGuidelines = await fs.readFile(".github/ai-repo-guidelines.md", "utf8");
} catch {
// optional
}

// 4) Read top of CHANGELOG.md to learn style
let changelogSnippet = "";
try {
const changelog = await fs.readFile("CHANGELOG.md", "utf8");
changelogSnippet = changelog.split("\n").slice(0, 200).join("\n");
} catch {
// optional
}

// 5) Build instructions for the Responses API
const instructions = `
You are a senior WordPress plugin engineer and release manager reviewing a Pull Request
for the WordPress plugin.

General rules:
- Follow WordPress Coding Standards (WPCS).
- Focus on security (nonces, capabilities, escaping/sanitizing), performance, and backward compatibility.
- Be careful with potential backward compatibility issues.
- Consider multisite, large datasets, and integration with Elementor/Bricks/Gutenberg where relevant.

Repository-specific guidelines (from .github/ai-repo-guidelines.md):
${repoGuidelines || "(no additional repo guidelines provided)"}

Changelog style example (from top of CHANGELOG.md, when available):
${changelogSnippet || "(CHANGELOG.md not available; use '- FIX: ...', '- ADD: ...', '- IMPROVE: ...', '- CHANGE: ...')"}

You MUST:
1) Review the PR changes and produce a concise but useful review in Markdown.
- Focus on security issues, performance hotspots, backward compatibility, and obvious bugs.
- Mention specific files / functions where possible.
- Call out missing tests for high-risk changes.
2) Propose a single changelog entry for the plugin, matching the changelog style as closely as possible.
- A single bullet line, prefixed with one of: '- FIX:', '- ADD:', '- IMPROVE:', '- CHANGE:'
- Short but specific: what changed and which area of the plugin it affects.

Return ONLY JSON that matches the provided JSON schema.
`;

const userContent = `
Repository: ${owner}/${repo}
PR #${pr.number}
Title: ${pr.title}
Author: ${pr.user.login}

Body:
${pr.body || "(no description)"}

Diff summary (truncated):
${diffSummary}
`;

// 6) Call OpenAI Responses API with JSON schema response_format
const response = await client.responses.create({
model: MODEL,
instructions,
input: [
{
role: "user",
content: userContent,
},
],
text: {
format: {
type: "json_schema",
name: "PRReview",
strict: true,
schema: {
type: "object",
properties: {
review_comment: { type: "string" },
changelog_entry: { type: "string" },
risk_level: {
type: "string",
enum: ["low", "medium", "high"],
},
},
required: ["review_comment", "changelog_entry", "risk_level"],
additionalProperties: false,
},
},
},
store: true, // optional but useful if you ever chain with previous_response_id
});



const raw =
response.output_text ??
response.output?.[0]?.content?.[0]?.text ??
"{}";

let parsed;

try {
parsed = JSON.parse(raw);
} catch (e) {
console.error("Failed to parse JSON from model:\n", raw);
process.exit(1);
}

const review = parsed.review_comment || "_No review generated._";
const changelog =
parsed.changelog_entry || "- CHANGE: Internal refactoring.";
const risk = parsed.risk_level || "unknown";


const commentBody = `
### 🤖 AI PR Review

**Risk level:** \`${risk}\`

#### Review

${review}

#### Suggested changelog entry

\`${changelog}\`
`;

// 7) Post comment back to the PR
const commentResp = await fetch(
`https://api.github.com/repos/${owner}/${repo}/issues/${pr.number}/comments`,
{
method: "POST",
headers: {
Authorization: `Bearer ${githubToken}`,
"X-GitHub-Api-Version": "2022-11-28",
Accept: "application/vnd.github+json",
},
body: JSON.stringify({ body: commentBody }),
}
);

if (!commentResp.ok) {
console.error("Failed to post PR comment:", commentResp.status, await commentResp.text());
process.exit(1);
}

console.log("AI PR review posted successfully.");
}

main().catch((err) => {
console.error("Unexpected error:", err);
process.exit(1);
});
39 changes: 39 additions & 0 deletions .github/workflows/ai-pr-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: AI PR Review

on:
pull_request:
types: [opened, synchronize, reopened, edited]

jobs:
ai_pr_review:
runs-on: ubuntu-latest

# Job-level environment
env:
AI_REVIEW_MODEL: gpt-4.1-mini

permissions:
contents: read # to read the repo
pull-requests: write # to post comments on PRs

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install OpenAI SDK (CI-only)
run: |
npm install openai@latest

- name: Run AI PR review
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# AI_REVIEW_MODEL comes from the job-level env above
run: node .github/scripts/ai-pr-review.mjs
Loading