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
159 changes: 159 additions & 0 deletions .ai/ai-code-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<!-- .ai/ai-code-review.md v1.5 | Last updated: 2026-02-02 -->

# AI Code Review Guide

This document defines how **any AI tool** (Claude, Gemini, Codex, or others) should review code changes. The review process is tool-agnostic -- the diff is the universal input, the standards are shared.

> **Standards reference:** [shared-development-guide.md](shared-development-guide.md)

---

## 1. Review Modes

### Pre-Push Review (Developer-Initiated)

Review changes **before** pushing to remote. Works with any AI tool or interface (IDE, CLI, web chat).

**Step 1: Generate the diff**

Determine your base branch first (e.g., `master`, `main`, `develop`). Use the base branch your PR will target.

```bash
# All committed changes on current branch vs base branch
git diff <base-branch>...HEAD

# Only staged changes (not yet committed)
git diff --cached

# Only unstaged changes
git diff

# Specific files
git diff <base-branch>...HEAD -- path/to/file.php

# Example with master as base
git diff master...HEAD
```

> **Tip:** For a complete review, check both committed changes (`git diff <base-branch>...HEAD`) and any uncommitted work (`git diff`, `git diff --cached`).

**Step 2: Request the review**

Use this prompt template (adapt wording for your tool):

```
Review the following code changes against our project's coding standards
in .ai/shared-development-guide.md.

Check for:
- Security issues (SQL injection, XSS, hardcoded secrets)
- Performance problems (N+1 queries, inefficient loops)
- Code quality (SRP, naming, error handling, type safety)
- Test coverage (are new features/fixes tested?)
- PHPStan compliance (proper types, no mixed where avoidable)
- Linting compliance (CiviCRM Drupal standards)

For each issue, provide:
- Severity: BLOCKER / WARNING / SUGGESTION / QUESTION
- File and line reference
- What the issue is and why it matters
- Suggested fix

<paste diff here>
```

**Step 3: Act on feedback**

- Fix BLOCKERs before pushing
- Address WARNINGs where practical
- Consider SUGGESTIONs for follow-up
- Answer QUESTIONs with code comments or commit messages

### GitHub PR Review (Automated)

Review changes **after** pushing, on the Pull Request. The AI tool reads the PR diff automatically (base branch is known from the PR target).

Same checklist applies. The AI tool should post comments on specific lines.

---

## 2. Review Checklist

### Standards Compliance
- [ ] Commit messages follow `CIVIMM-###:` format
- [ ] PR uses `.github/PULL_REQUEST_TEMPLATE.md`
- [ ] All required sections filled (Overview, Before, After, Technical Details)
- [ ] No AI attribution in commits

### Security
- [ ] No hardcoded secrets, API keys, or credentials
- [ ] Parameterized queries (no SQL injection)
- [ ] User input sanitized before rendering (no XSS)
- [ ] Webhook signatures verified
- [ ] Authentication/authorization on API endpoints
- [ ] Sensitive files not committed (`civicrm.settings.php`, `.env`)

### Performance
- [ ] No N+1 query issues
- [ ] No inefficient loops over large datasets
- [ ] Unnecessary API calls avoided
- [ ] Database queries optimized

### Code Quality
- [ ] Single responsibility principle followed
- [ ] Meaningful names following project conventions
- [ ] Proper exception handling
- [ ] Return type declarations on service methods
- [ ] Dependency injection used
- [ ] Proper types in PHPDoc (no `mixed` where avoidable)
- [ ] No `assert()` in production code

### Testing
- [ ] New features and bug fixes include tests
- [ ] Tests cover positive, negative, and edge cases
- [ ] No tests removed or weakened
- [ ] Error message changes reflected in test assertions

### Static Analysis & Linting
- [ ] Code passes PHPStan level 9
- [ ] Coding standards followed
- [ ] Files end with newlines
- [ ] `@phpstan-param` / `@phpstan-var` used where linter and PHPStan conflict

---

## 3. Review Severity Levels

| Level | Meaning | Action Required |
|-------|---------|-----------------|
| **BLOCKER** | Security vulnerability, data loss risk, broken functionality | Must fix before merge |
| **WARNING** | Affects quality, performance, or maintainability | Should fix before merge |
| **SUGGESTION** | Optional improvement | Nice to have, can be follow-up |
| **QUESTION** | Needs clarification from the author | Author must respond |

---

## 4. Review Feedback Principles

- **Be specific** -- reference file paths and line numbers
- **Explain why** -- not just what to change, but why it matters
- **Think critically** -- don't suggest changes that contradict architectural decisions
- **Consider implications** -- type changes, database constraints, performance trade-offs
- **Distinguish severity** -- not everything is a blocker
- **Be constructive** -- suggest fixes, not just problems

---

## 5. Cross-Tool Review Workflow

Any combination works:

| Developer Tool | Reviewer Tool | How |
|---------------|---------------|-----|
| Claude | Gemini | Push branch, Gemini reviews PR or diff |
| Gemini | Claude | Feed diff to Claude in new session |
| Human | Any AI | Feed diff or create PR |
| Codex | Claude/Gemini | Push branch, other tool reviews |
| Any tool | Same tool (new session) | Feed diff to fresh session for unbiased review |

**Best practice:** Use a **different tool or session** for review than was used for development. Fresh context catches more issues.
225 changes: 225 additions & 0 deletions .ai/civicrm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<!-- .ai/civicrm.md v1.5 | Last updated: 2026-02-02 -->

# CiviCRM Core Reference

This file provides CiviCRM-specific patterns and conventions for AI tools and developers.

> **Extension structure & testing:** See [extension.md](extension.md) for extension directory layout, schema changes, and testing patterns.

---

## 1. Namespaces

| Namespace | Directory | Purpose |
|-----------|-----------|---------|
| `CRM_*` | `CRM/` | Traditional CiviCRM (DAO, BAO, Pages, Forms) |
| `Civi\Paymentprocessingcore\*` | `Civi/` | Modern services, hooks, factories |

---

## 2. CiviCRM API Usage

### Prefer API4 Over API3

API4 is the modern CiviCRM API with better type safety and cleaner syntax. Use it for all new code.

```php
// API4 with permission bypass for internal/IPN operations
$contribution = \Civi\Api4\Contribution::get(FALSE)
->addSelect('id', 'contribution_page_id', 'contribution_status_id:name')
->addWhere('id', '=', $contributionId)
->execute()
->first();

// API3 (legacy - avoid in new code)
$contribution = civicrm_api3('Contribution', 'getsingle', [
'id' => $contributionId,
'return' => ['id', 'contribution_page_id'],
]);
```

### Key API4 Differences
- `FALSE` as first parameter bypasses permission checks
- API3 requires `'check_permissions' => 0` (easy to forget)
- API4 uses `snake_case` field names consistently
- API4 status fields use pseudoconstant syntax: `contribution_status_id:name`

### When to Bypass Permissions (API4 `FALSE`)
- IPN/webhook handlers (anonymous context)
- Return URLs from payment processors
- Internal service operations
- Background processing jobs

### When API3 Is Acceptable
- Entity not yet available in API4 (rare)
- Maintaining consistency with existing code in same method
- `Payment.create` API (commonly used, works well)

---

## 3. API4 Result Handling & PHPStan

API4's `->first()` and `->single()` return `mixed`. Use these patterns for PHPStan compliance:

### In Services - `is_array()` Guard
```php
$result = \Civi\Api4\PaymentToken::create(FALSE)
->setValues($tokenParams)
->execute()
->first();

if (!is_array($result) || empty($result['id'])) {
throw new \CRM_Core_Exception('Failed to create payment token');
}
return $result;
```

### In Tests - `assertNotNull()` Before Accessing
```php
$updated = \Civi\Api4\ContributionRecur::get(FALSE)
->addSelect('payment_token_id')
->addWhere('id', '=', $recurId)
->execute()
->first();

$this->assertNotNull($updated);
$this->assertEquals($tokenId, $updated['payment_token_id']);
```

### Avoid These Patterns
```php
// BAD: Inline @var annotations - not the project pattern
/** @var array{id: int}|null $result */
$result = $api->execute()->first();

// BAD: ->single() throws if not exactly 1 result
// Use ->first() with is_array() check instead
$result = $api->execute()->single();
```

---

## 4. PHPStan Stub Files for Dynamic Classes

CiviCRM Api4 entity classes are **dynamically generated at runtime** and have no physical PHP files. Use stub files in `stubs/`:

```php
// stubs/CiviApi4.stub.php
namespace Civi\Api4;

use Civi\Api4\Generic\DAOGetAction;
use Civi\Api4\Generic\DAOCreateAction;
use Civi\Api4\Generic\DAOUpdateAction;
use Civi\Api4\Generic\DAODeleteAction;

/**
* @method static DAOGetAction get(bool $checkPermissions = TRUE)
* @method static DAOCreateAction create(bool $checkPermissions = TRUE)
* @method static DAOUpdateAction update(bool $checkPermissions = TRUE)
* @method static DAODeleteAction delete(bool $checkPermissions = TRUE)
*/
class Activity {

}
```

**Rules:**
- Do NOT use `extends AbstractEntity` in stubs (causes CI errors)
- Add new Api4 entities to the stub file when first used
- Stubs are loaded via `scanFiles` in both `phpstan.neon` and CI-generated `phpstan-ci.neon`
- Must use `scanFiles` (not `stubFiles`) because `stubFiles` are overridden when `scanDirectories` loads the `Civi\Api4` namespace from core

---

## 5. Hooks and Integration Points

Common CiviCRM hooks used in this extension:

| Hook | Purpose |
|------|---------|
| `civicrm_buildForm` | Inject scripts/templates into forms |
| `civicrm_validateForm` | Validate form submissions |
| `civicrm_permission` | Register custom permissions |
| `civicrm_permission_check` | Grant permissions dynamically |
| `civicrm_container` | Register services with DI container |
| `civicrm_postCommit` | Handle post-transaction tasks |
| `civicrm_check` | System status checks |

---

## 6. Service Registration

Services are registered via Symfony DI container in hook implementations:

```php
// In Civi/Paymentprocessingcore/Hook/Container/ServiceContainer.php
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

class ServiceContainer {
public function register(ContainerBuilder $container): void {
$definition = new Definition(MyService::class);
$definition->addArgument(new Reference('other.service'));
$container->setDefinition('my.service', $definition);
}
}
```

Usage:
```php
$service = \Civi::service('my.service');
```

---

## 7. Common CiviCRM Commands

```bash
# Enable extension
cv en paymentprocessingcore

# Disable extension
cv dis paymentprocessingcore

# Upgrade extension
cv api Extension.upgrade

# Clear cache
cv flush

# Run cv commands via Docker
./scripts/run.sh cv api Contact.get
./scripts/run.sh shell # Shell into container
```

---

## 8. PHPStan vs Linter Compatibility

The Drupal linter and PHPStan have conflicting requirements for array type annotations.

**Problem:** `@var array<string, string|int>` fails linter, but `@var array` fails PHPStan.

**Solution:** Use `@phpstan-var` / `@phpstan-param` which PHPStan reads but linter ignores:

```php
/**
* @param array $params
* @phpstan-param array<string, mixed> $params
*/
public function process(array $params): void {
// ...
}

/**
* {@inheritdoc}
*
* @phpstan-var array<string, string|int>
*/
protected static $defaultParams = [
'financial_type_id' => 'Donation',
'frequency_interval' => 1,
];
```

For inherited properties, use `{@inheritdoc}` plus `@phpstan-var` for the specific type.
Loading