From 935d804b5c6b1a60dd98f0c87c5fd48586e7252c Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Wed, 12 Nov 2025 18:46:41 -0600 Subject: [PATCH] Add contextual skills for ActivityPub development This implements a comprehensive skill system to provide context-aware documentation for development workflows: - activitypub-dev-cycle: Environment setup, testing, linting, and builds - activitypub-php-conventions: WordPress coding standards and patterns - activitypub-pr-workflow: PR creation and review processes - activitypub-federation: ActivityPub protocol specification - activitypub-testing: PHPUnit and Playwright test patterns - activitypub-release: Version management and releases - activitypub-integrations: Third-party plugin integration Skills use cross-references to reduce duplication while maintaining context-appropriate information. Federation skill focuses on protocol understanding rather than implementation details. Updates .gitignore to track shared skills while ignoring local overrides using the *.local.* pattern. --- .claude/settings.json | 14 + .claude/skills/activitypub-dev-cycle/SKILL.md | 239 ++++++ .../environment-setup.md | 397 +++++++++ .../skills/activitypub-dev-cycle/linting.md | 346 ++++++++ .../skills/activitypub-dev-cycle/testing.md | 240 ++++++ .../skills/activitypub-federation/SKILL.md | 312 +++++++ .../skills/activitypub-integrations/SKILL.md | 155 ++++ .../activitypub-php-conventions/SKILL.md | 394 +++++++++ .../class-structure.md | 587 +++++++++++++ .../coding-standards.md | 477 +++++++++++ .../activitypub-php-conventions/examples.md | 799 ++++++++++++++++++ .../skills/activitypub-pr-workflow/SKILL.md | 328 +++++++ .../activitypub-pr-workflow/checklist.md | 199 +++++ .claude/skills/activitypub-release/SKILL.md | 151 ++++ .claude/skills/activitypub-testing/SKILL.md | 123 +++ .gitignore | 4 +- CLAUDE.md | 27 + 17 files changed, 4790 insertions(+), 2 deletions(-) create mode 100644 .claude/settings.json create mode 100644 .claude/skills/activitypub-dev-cycle/SKILL.md create mode 100644 .claude/skills/activitypub-dev-cycle/environment-setup.md create mode 100644 .claude/skills/activitypub-dev-cycle/linting.md create mode 100644 .claude/skills/activitypub-dev-cycle/testing.md create mode 100644 .claude/skills/activitypub-federation/SKILL.md create mode 100644 .claude/skills/activitypub-integrations/SKILL.md create mode 100644 .claude/skills/activitypub-php-conventions/SKILL.md create mode 100644 .claude/skills/activitypub-php-conventions/class-structure.md create mode 100644 .claude/skills/activitypub-php-conventions/coding-standards.md create mode 100644 .claude/skills/activitypub-php-conventions/examples.md create mode 100644 .claude/skills/activitypub-pr-workflow/SKILL.md create mode 100644 .claude/skills/activitypub-pr-workflow/checklist.md create mode 100644 .claude/skills/activitypub-release/SKILL.md create mode 100644 .claude/skills/activitypub-testing/SKILL.md create mode 100644 CLAUDE.md diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..cef878a6e --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,14 @@ +{ + "description": "Claude Code settings for WordPress ActivityPub plugin development", + "skills": { + "allowedSkills": [ + "activitypub-dev-cycle", + "activitypub-php-conventions", + "activitypub-pr-workflow", + "activitypub-federation", + "activitypub-testing", + "activitypub-release", + "activitypub-integrations" + ] + } +} diff --git a/.claude/skills/activitypub-dev-cycle/SKILL.md b/.claude/skills/activitypub-dev-cycle/SKILL.md new file mode 100644 index 000000000..6330dd621 --- /dev/null +++ b/.claude/skills/activitypub-dev-cycle/SKILL.md @@ -0,0 +1,239 @@ +--- +name: activitypub-dev-cycle +description: Development workflows for WordPress ActivityPub plugin including wp-env setup, testing commands, linting, and build processes. Use when setting up development environment, running tests, checking code quality, building assets, or working with wp-env. +--- + +# ActivityPub Development Cycle + +This skill provides guidance for common development workflows in the WordPress ActivityPub plugin. + +**This is the authoritative source for:** +- Environment setup and wp-env management +- Testing commands and workflows +- Linting and code quality tools +- Build processes + +## Quick Reference + +### Environment Management +```bash +npm run env-start # Start WordPress at http://localhost +npm run env-stop # Stop WordPress environment +``` + +### Testing Commands +```bash +# Run all PHP tests +npm run env-test + +# Run specific test +npm run env-test -- --filter=test_name + +# Run tests from specific file +npm run env-test -- tests/phpunit/tests/path/to/test.php + +# Run tests by group +npm run env-test -- --group=migration +``` + +### Code Quality +```bash +# PHP linting +composer lint # Check PHP coding standards +composer lint:fix # Auto-fix PHP issues + +# JavaScript linting +npm run lint:js # Check JavaScript +npm run lint:css # Check CSS styles + +# Format all code +npm run format # Runs wp-scripts format +``` + +### Building Assets +```bash +npm run build # Format and build for production +npm run dev # Start development watch mode +``` + +## Initial Setup + +1. **Clone and install dependencies:** + ```bash + git clone git@github.com:Automattic/wordpress-activitypub.git + cd wordpress-activitypub + npm install + composer install + ``` + +2. **Configure git hooks:** + ```bash + npm run prepare # Sets up pre-commit hooks + ``` + +3. **Start development environment:** + ```bash + npm run env-start + ``` + WordPress will be available at http://localhost:8888 + +For detailed setup instructions, see [environment-setup.md](environment-setup.md). + +## Testing Workflows + +See [testing.md](testing.md) for comprehensive testing guidance. + +### Running Tests + +**Basic test execution:** +```bash +npm run env-test +``` + +**Common PHPUnit arguments:** +- `--filter=pattern` - Run tests matching pattern +- `--group=name` - Run tests with specific @group +- `--exclude-group=name` - Skip tests with @group +- `--verbose` - Show detailed output +- `--debug` - Display debugging information + +**Examples:** +```bash +# Test specific functionality +npm run env-test -- --filter=Notification + +# Test specific file +npm run env-test -- tests/phpunit/tests/includes/class-test-notification.php + +# Run migration tests only +npm run env-test -- --group=migration +``` + +### Code Coverage + +Generate coverage reports with Xdebug: + +```bash +# Start environment with coverage support +npm run env-start -- --xdebug=coverage + +# Generate text coverage report +npm run env-test -- --coverage-text + +# Generate HTML coverage report +npm run env-test -- --coverage-html ./coverage +open coverage/index.html # View report (macOS) +``` + +## Code Quality Standards + +See [linting.md](linting.md) for detailed linting configuration. + +### PHP Standards + +The project uses WordPress Coding Standards with custom rules: +- Run `composer lint` to check standards +- Run `composer lint:fix` to auto-fix issues +- Configuration in `.phpcs.xml.dist` + +### JavaScript Standards + +Uses `@wordpress/scripts` for linting: +- Run `npm run lint:js` for JavaScript +- Run `npm run lint:css` for stylesheets +- Configuration extends WordPress standards + +## Pre-commit Hooks + +The repository uses automated pre-commit hooks (`.githooks/pre-commit`) that: + +1. **Sort PHP imports** - Automatically organizes use statements +2. **Check unused imports** - Prevents unused use statements +3. **Validate test patterns** - Blocks `remove_all_filters('pre_http_request')` +4. **Run PHPCS auto-fix** - Applies coding standards +5. **Format JavaScript** - Runs wp-scripts formatter + +**Important:** Hooks modify files automatically. Always review changes before committing. + +## Build Process + +### Development Build +```bash +npm run dev # Starts watch mode with source maps +``` + +### Production Build +```bash +npm run build # Formats and minifies for production +``` + +The build process: +1. Runs `wp-scripts format` on JavaScript files +2. Builds with experimental modules support +3. Outputs to `build/` directory + +## Common Development Tasks + +### Start Fresh Environment +```bash +npm run env-stop +npm run env-start +``` + +### Run Tests After Code Changes +```bash +npm run env-test -- --filter=ChangedFeature +``` + +### Check Code Before Committing +```bash +composer lint +npm run lint:js +``` + +### Debug Failing Tests +```bash +# Run with verbose output +npm run env-test -- --verbose --filter=failing_test + +# Check test groups +npm run env-test -- --group=specific_group +``` + +### E2E Testing +```bash +# Run Playwright E2E tests +npm run test:e2e + +# Debug mode +npm run test:e2e:debug +``` + +## Environment Variables + +wp-env automatically sets up WordPress with: +- WordPress latest version +- Debug mode enabled +- ActivityPub plugin activated +- Test user accounts created + +## Troubleshooting + +### Port Conflicts +If port 80 is in use: +```bash +wp-env stop +# Then check what's using the port +lsof -i :80 # macOS/Linux +``` + +### Permission Issues +Ensure Docker is running and you have proper permissions. + +## Key Files + +- `package.json` - npm scripts and dependencies +- `composer.json` - PHP dependencies and scripts +- `.wp-env.json` - wp-env configuration +- `.phpcs.xml.dist` - PHP coding standards +- `.githooks/pre-commit` - Git hook configuration diff --git a/.claude/skills/activitypub-dev-cycle/environment-setup.md b/.claude/skills/activitypub-dev-cycle/environment-setup.md new file mode 100644 index 000000000..b83aeef10 --- /dev/null +++ b/.claude/skills/activitypub-dev-cycle/environment-setup.md @@ -0,0 +1,397 @@ +# Environment Setup Reference + +## Table of Contents +- [Prerequisites](#prerequisites) +- [Initial Setup](#initial-setup) +- [wp-env Configuration](#wp-env-configuration) +- [Docker Management](#docker-management) +- [Troubleshooting](#troubleshooting) + +## Prerequisites + +### Required Software + +1. **Node.js** (v18 or later) + ```bash + node --version # Check version + ``` + +2. **npm** (comes with Node.js) + ```bash + npm --version # Check version + ``` + +3. **Docker Desktop** + - [Download Docker Desktop](https://www.docker.com/products/docker-desktop) + - Ensure Docker is running before using npm run env commands + +4. **Composer** (for PHP dependencies) + ```bash + # Install via Homebrew (macOS) + brew install composer + + # Or download directly + curl -sS https://getcomposer.org/installer | php + ``` + +5. **Git** with SSH key setup + ```bash + # Check SSH key + ssh -T git@github.com + ``` + +## Initial Setup + +### Step-by-Step Installation + +1. **Clone the repository:** + ```bash + git clone git@github.com:Automattic/wordpress-activitypub.git + cd wordpress-activitypub + ``` + +2. **Install JavaScript dependencies:** + ```bash + npm install + ``` + +3. **Install PHP dependencies:** + ```bash + composer install + ``` + +4. **Set up Git hooks:** + ```bash + npm run prepare + ``` + +5. **Start WordPress environment:** + ```bash + npm run env-start + ``` + +6. **Access WordPress:** + - Frontend: http://localhost:8888 + - Admin: http://localhost:8888/wp-admin + - Username: `admin` + - Password: `password` + +## wp-env Configuration + +### Default Configuration + +wp-env uses `.wp-env.json` for configuration: + +```json +{ + "phpVersion": "8.0", + "plugins": [ "." ], + "config": { + "WP_DEBUG": true, + "WP_DEBUG_LOG": true, + "WP_DEBUG_DISPLAY": true, + "SCRIPT_DEBUG": true + } +} +``` + +### Common Environment Commands + +Available npm scripts: +- `npm run env-start` → Start environment +- `npm run env-stop` → Stop environment +- `npm run env-test` → Run PHPUnit tests +- `npm run env -- ` → Pass any wp-env command + +```bash +# Start environment +npm run env-start + +# Stop environment +npm run env-stop + +# Restart environment +npm run env-stop +npm run env-start + +# Run PHPUnit tests +npm run env-test + +# Run WP-CLI commands +npm run env -- run cli wp user list +npm run env -- run cli wp plugin list + +# Access MySQL +npm run env -- run cli wp db cli + +# View logs +npm run env -- logs +``` + +### Passing Additional Parameters to wp-env + +Use `npm run env --` to pass any wp-env command and its parameters: + +```bash +# Run WP-CLI with additional arguments +npm run env -- run cli wp user create testuser test@example.com --role=editor + +# View logs with follow flag +npm run env -- logs --follow + +# Any wp-env command can be passed through +npm run env -- [options] +``` + +### Multiple WordPress Versions + +Test with different WordPress versions: + +```bash +# Update .wp-env.json +{ + "core": "WordPress/WordPress#6.4" +} +``` + +### Multiple PHP Versions + +```bash +# In .wp-env.json +{ + "phpVersion": "7.4" +} +``` + +## Docker Management + +### Container Information + +```bash +# List running containers +docker ps + +# View container logs +docker logs $(docker ps -q --filter name=wordpress) + +# Access container shell +docker exec -it $(docker ps -q --filter name=wordpress) bash +``` + +### Resource Management + +```bash +# Check Docker resource usage +docker system df + +# Clean up unused resources +docker system prune -a +``` + +### Port Management + +Default ports: +- WordPress: 8888 +- MySQL: (mapped to random external port) + +Change ports in `.wp-env.json`: +```json +{ + "port": 8080, + "testsPort": 8081 +} +``` + +## Troubleshooting + +### Common Issues and Solutions + +#### Docker Not Running + +**Error:** "Docker is not running" + +**Solution:** +```bash +# Start Docker Desktop application +open -a Docker # macOS + +# Wait for Docker to start, then retry +npm run env-start +``` + +#### Port Already in Use + +**Error:** "Port 8888 is already in use" + +**Solution:** +```bash +# Find process using port +lsof -i :8888 # macOS/Linux + +# Kill the process +kill -9 + +# Or use different port +npm run env-start -- --port=8889 +``` + +#### Permission Denied + +**Error:** "Permission denied" errors + +**Solution:** +```bash +# Ensure Docker has permissions +sudo usermod -aG docker $USER # Linux + +# Restart terminal and retry +``` + +#### Slow Performance + +**Issue:** wp-env runs slowly + +**Solutions:** +1. Increase Docker resources: + - Docker Desktop → Preferences → Resources + - Increase CPUs and Memory + +2. Use mutagen for file sync (macOS): + ```bash + brew install mutagen-io/mutagen/mutagen + ``` + +3. Exclude node_modules from sync + +#### Database Connection Errors + +**Error:** "Error establishing database connection" + +**Solution:** +```bash +# Restart containers +npm run env-stop +npm run env-start +``` + +#### Plugin Not Activated + +**Issue:** ActivityPub plugin not active + +**Solution:** +```bash +# Manually activate +npm run env -- run cli wp plugin activate activitypub + +# Check activation +npm run env -- run cli wp plugin list --status=active +``` + +### Debugging wp-env + +#### Enable Verbose Output + +```bash +# Set debug environment variable +DEBUG=wp-env:* npm run env-start +``` + +### Advanced Configuration + +#### Custom WordPress Configuration + +Create `wp-config.php` additions: + +```php +// In .wp-env.json +{ + "config": { + "WP_DEBUG": true, + "WP_DEBUG_LOG": true, + "WP_DEBUG_DISPLAY": false, + "SCRIPT_DEBUG": true, + "WP_ENVIRONMENT_TYPE": "local", + "WP_MEMORY_LIMIT": "256M" + } +} +``` + +#### Mount Additional Directories + +```json +{ + "mappings": { + "wp-content/mu-plugins": "./mu-plugins", + "wp-content/themes/custom": "./custom-theme" + } +} +``` + +#### Multiple Test Sites + +Run multiple instances: + +```bash +# Start on different ports +WP_ENV_PORT=8888 npm run env-start # Instance 1 +WP_ENV_PORT=9999 npm run env-start # Instance 2 +``` + +### Performance Optimization + +#### File Sync Optimization + +For better performance on macOS: + +1. **Use Docker Desktop's VirtioFS:** + - Docker Desktop → Settings → General + - Enable "Use the new Virtualization framework" + - Enable "VirtioFS accelerated directory sharing" + +2. **Limit watched files:** + ```json + { + "excludePaths": [ + "node_modules", + "vendor", + ".git" + ] + } + ``` + +#### Database Optimization + +```bash +# Optimize database tables +npm run env -- run cli wp db optimize + +# Clear transients +npm run env -- run cli wp transient delete --all +``` + +## Environment Variables + +### Available Variables + +- `WP_ENV_HOME` - wp-env home directory +- `WP_ENV_PORT` - WordPress port +- `WP_ENV_TESTS_PORT` - Tests port +- `WP_ENV_LIFECYCLE_SCRIPT` - Lifecycle script path + +### Custom Environment Setup + +Create `.env.local` for custom variables: + +```bash +# .env.local +AP_TEST_TIMEOUT=10000 +AP_DEBUG_MODE=true +``` + +Load in tests: +```php +if ( file_exists( '.env.local' ) ) { + $dotenv = Dotenv\Dotenv::createImmutable( __DIR__ ); + $dotenv->load(); +} +``` diff --git a/.claude/skills/activitypub-dev-cycle/linting.md b/.claude/skills/activitypub-dev-cycle/linting.md new file mode 100644 index 000000000..2ece7d2d7 --- /dev/null +++ b/.claude/skills/activitypub-dev-cycle/linting.md @@ -0,0 +1,346 @@ +# Linting and Code Quality Reference + +## Table of Contents +- [PHP Code Standards](#php-code-standards) +- [JavaScript Standards](#javascript-standards) +- [CSS Standards](#css-standards) +- [Pre-commit Automation](#pre-commit-automation) +- [Fixing Common Issues](#fixing-common-issues) + +## PHP Code Standards + +### Quick Commands + +```bash +# Check PHP code standards +composer lint + +# Auto-fix PHP issues +composer lint:fix + +# Check specific file +vendor/bin/phpcs path/to/file.php + +# Fix specific file +vendor/bin/phpcbf path/to/file.php +``` + +### PHPCS Configuration + +The project uses WordPress Coding Standards with custom rules defined in `.phpcs.xml.dist`: + +**Key Rules:** +- WordPress-Core standards +- WordPress-Docs standards +- WordPress-Extra standards +- Custom variable analysis +- PHPCompatibility checks for PHP 7.2+ + +**Excluded Patterns:** +- `vendor/` directory +- `node_modules/` directory +- `build/` directory +- Third-party integration files + +### Common PHP Standards + +For complete PHP coding standards including: +- File headers and documentation +- Class and method documentation +- Naming conventions +- Spacing and indentation + +See [PHP Conventions - Coding Standards](../activitypub-php-conventions/coding-standards.md). + +**Important:** All DocBlock descriptions must end with proper punctuation (periods). + +## JavaScript Standards + +### Quick Commands + +```bash +# Check JavaScript +npm run lint:js + +# Check CSS +npm run lint:css + +# Format all JavaScript/CSS +npm run format +``` + +### JavaScript Configuration + +Uses `@wordpress/scripts` with WordPress coding standards: + +**Key Rules:** +- ESLint with WordPress configuration +- Prettier for formatting +- React/JSX support +- ES6+ syntax + +### Common JavaScript Standards + +Uses `@wordpress/scripts` with WordPress coding standards. Key points: +- Prefer `const`/`let` over `var` +- Use WordPress import organization (external, WordPress, internal) +- Follow WordPress ESLint configuration + +## CSS Standards + +### Stylelint Configuration + +```bash +# Check CSS files +npm run lint:css + +# Auto-fix CSS issues +npm run format +``` + +**Key Rules:** +- WordPress CSS coding standards +- Alphabetical property ordering +- Consistent spacing +- No vendor prefixes (handled by build) + +### CSS Best Practices + +```css +/* Component naming */ +.activitypub-component { + /* Alphabetical properties */ + background: #fff; + border: 1px solid #ddd; + margin: 10px; + padding: 15px; +} + +/* BEM-style modifiers */ +.activitypub-component--active { + background: #f0f0f0; +} + +/* Child elements */ +.activitypub-component__title { + font-size: 1.2em; + font-weight: bold; +} +``` + +## Pre-commit Automation + +### What Happens on Commit + +The `.githooks/pre-commit` hook automatically: + +1. **Sorts PHP imports:** + - Organizes `use` statements alphabetically + - Groups by type (classes, functions, constants) + +2. **Checks for unused imports:** + - Detects unused `use` statements + - Blocks commit if found + +3. **Validates test patterns:** + - Prevents `remove_all_filters( 'pre_http_request' )` + - Suggests using `remove_filter()` instead + +4. **Runs PHPCS auto-fix:** + - Applies coding standards automatically + - Fixes spacing, indentation, etc. + +5. **Formats JavaScript:** + - Runs Prettier on JS/JSX files + - Ensures consistent formatting + +**Better approach - fix issues:** +```bash +# Fix PHP issues +composer lint:fix + +# Fix JS issues +npm run format + +# Then commit normally +git add . +git commit -m "Fixed: Issue description" +``` + +## Fixing Common Issues + +### PHP Issues + +**Issue: "Missing file comment"** +```php +// Add at top of file: + 1, + 'longer_key' => 2, +); + +// Good. +$array = array( + 'short' => 1, + 'longer_key' => 2, +); +``` + +### JavaScript Issues + +**Issue: "Prefer const over let"** +```javascript +// Bad +let unchangedValue = 'constant'; + +// Good +const unchangedValue = 'constant'; +``` + +**Issue: "Missing JSDoc comment"** +```javascript +// Add documentation +/** + * Function description. + * + * @param {string} param Parameter description. + * @return {boolean} Return description. + */ +function functionName( param ) { +``` + +**Issue: "Import order"** +```javascript +// Correct order: +// 1. External dependencies +import external from 'package'; + +// 2. WordPress dependencies +import { Component } from '@wordpress/element'; + +// 3. Internal dependencies +import internal from './file'; +``` + +### CSS Issues + +**Issue: "Properties not alphabetically ordered"** +```css +/* Bad */ +.class { + padding: 10px; + margin: 5px; + background: #fff; +} + +/* Good */ +.class { + background: #fff; + margin: 5px; + padding: 10px; +} +``` + +**Issue: "Unexpected vendor prefix"** +```css +/* Bad */ +.class { + -webkit-border-radius: 5px; + border-radius: 5px; +} + +/* Good - let build tools handle prefixes */ +.class { + border-radius: 5px; +} +``` + +## Custom Project Rules + +### PHP Specific Rules + +1. **Text Domain:** Always use `'activitypub'` + ```php + \__( 'Text', 'activitypub' ); + ``` + +2. **Namespace:** Follow pattern + ```php + namespace Activitypub\Feature_Name; + ``` + +3. **File Naming:** Use prefix pattern + ``` + class-*.php for classes + trait-*.php for traits + interface-*.php for interfaces + ``` + +### Ignored Files + +The following are excluded from linting: +- `vendor/` - Composer dependencies +- `node_modules/` - npm dependencies +- `build/` - Compiled assets +- `.wordpress-org/` - WordPress.org assets + +## Running Targeted Checks + +### Check Changed Files Only + +```bash +# PHP files changed in current branch +git diff --name-only HEAD~1 | grep '\.php$' | xargs vendor/bin/phpcs + +# JavaScript files changed +git diff --name-only HEAD~1 | grep '\.js$' | xargs npm run lint:js -- +``` + +### Check Specific Directories + +```bash +# Check all transformer classes +vendor/bin/phpcs includes/transformer/ + +# Check all JavaScript in src +npm run lint:js -- src/**/*.js +``` + +### Generate Reports + +```bash +# Generate PHP report +vendor/bin/phpcs --report=summary + +# Generate detailed report +vendor/bin/phpcs --report=full > phpcs-report.txt + +# Generate JSON report for CI +vendor/bin/phpcs --report=json > phpcs-report.json +``` diff --git a/.claude/skills/activitypub-dev-cycle/testing.md b/.claude/skills/activitypub-dev-cycle/testing.md new file mode 100644 index 000000000..d1696233a --- /dev/null +++ b/.claude/skills/activitypub-dev-cycle/testing.md @@ -0,0 +1,240 @@ +# Testing Reference + +This file provides detailed testing patterns and examples. For basic test commands, see the main [SKILL.md](SKILL.md#testing-workflows). + +## Table of Contents +- [Test Organization](#test-organization) +- [Writing Effective Tests](#writing-effective-tests) +- [Common Test Patterns](#common-test-patterns) +- [Debugging Tests](#debugging-tests) + + +## Test Organization + +### PHP Test Structure + +``` +tests/phpunit/ +├── bootstrap.php # Test bootstrap file +├── tests/ +│ ├── includes/ # Core functionality tests +│ │ ├── class-test-*.php +│ │ ├── handler/ # Handler tests +│ │ ├── transformer/ # Transformer tests +│ │ └── collection/ # Collection tests +│ ├── integration/ # Integration tests +│ └── rest/ # REST API tests +└── fixtures/ # Test data files +``` + +### Test Groups + +Use `@group` annotations to organize tests. Run with: `npm run env-test -- --group=groupname` + +Common groups: `migration`, `rest`, `integration` + +### Test Naming Conventions + +```php +// Test class names. +class Test_Notification extends \WP_UnitTestCase {} + +// Test method names. +public function test_send_notification() {} +public function test_notification_with_invalid_data() {} +public function test_should_handle_empty_followers() {} +``` + +## Writing Effective Tests + +### PHP Test Template + +```php +assertEquals( 'expected', $result ); + } +} +``` + +### Common Assertions + +```php +// Equality assertions. +$this->assertEquals( $expected, $actual ); +$this->assertSame( $expected, $actual ); // Strict comparison. +$this->assertNotEquals( $expected, $actual ); + +// Type assertions. +$this->assertIsArray( $value ); +$this->assertIsString( $value ); +$this->assertIsBool( $value ); + +// WordPress assertions. +$this->assertWPError( $result ); +$this->assertNotWPError( $result ); +$this->assertQueryTrue( 'is_single', 'is_singular' ); + +// Count assertions. +$this->assertCount( 3, $array ); +$this->assertEmpty( $value ); +$this->assertNotEmpty( $value ); + +// Exception assertions. +$this->expectException( Exception::class ); +$this->expectExceptionMessage( 'Error message' ); +``` + +## Debugging Tests + +### Debugging Techniques + +1. **Print debugging:** + ```php + var_dump( $variable ); + print_r( $array ); + error_log( print_r( $data, true ) ); + ``` + +2. **Stop on failure:** + ```bash + npm run env-test -- --stop-on-failure + ``` + +3. **Verbose output:** + ```bash + npm run env-test -- --verbose --debug + ``` + +4. **Single test isolation:** + ```bash + npm run env-test -- --filter=test_specific_method + ``` + +5. **Check test database:** + ```bash + npm run env -- run tests-cli wp db query "SELECT * FROM wp_posts" + ``` + +### Common Issues and Solutions + +**Issue: Tests pass individually but fail together** +- Check for test interdependencies +- Ensure proper tearDown() cleanup +- Look for global state modifications + +**Issue: Different results locally vs CI** +- Check PHP/WordPress versions +- Verify timezone settings +- Check for environment-specific code + +**Issue: Timeout errors** +- Increase timeout in test +- Check for infinite loops +- Verify external API mocks + +**Issue: Database not reset between tests** +- Check transaction rollback +- Ensure proper cleanup in tearDown() +- Look for direct database writes + +### Test Utilities + +**Creating test data:** +```php +// Create test user. +$user_id = self::factory()->user->create( [ + 'role' => 'editor', +] ); + +// Create test post. +$post_id = self::factory()->post->create( [ + 'post_author' => $user_id, + 'post_status' => 'publish', +] ); + +// Create test term. +$term_id = self::factory()->term->create( [ + 'taxonomy' => 'category', +] ); +``` + +**Mocking HTTP requests:** + +**IMPORTANT:** Always save filter callbacks to a variable so they can be removed properly. NEVER use `remove_all_filters()` or `remove_all_actions()` as they can break other tests and plugins. + +```php +// CORRECT: Save callback to variable for proper removal. +$http_filter_callback = function( $preempt, $args, $url ) { + if ( strpos( $url, 'example.com' ) !== false ) { + return array( + 'response' => array( 'code' => 200 ), + 'body' => json_encode( array( 'success' => true ) ), + ); + } + + return $preempt; +}; + +// Add the filter. +\add_filter( 'pre_http_request', $http_filter_callback, 10, 3 ); + +// Do your test... + +// Remove the specific filter. +\remove_filter( 'pre_http_request', $http_filter_callback, 10 ); + +// NEVER DO THIS - it breaks other tests and plugins: +// remove_all_filters( 'pre_http_request' ); // ❌ WRONG +// remove_all_actions( 'some_action' ); // ❌ WRONG +``` + +**Time-based testing:** +```php +// Mock current time. +$now = '2024-01-01 12:00:00'; +add_filter( 'current_time', function() use ( $now ) { + return $now; +} ); +``` diff --git a/.claude/skills/activitypub-federation/SKILL.md b/.claude/skills/activitypub-federation/SKILL.md new file mode 100644 index 000000000..30b497d46 --- /dev/null +++ b/.claude/skills/activitypub-federation/SKILL.md @@ -0,0 +1,312 @@ +--- +name: activitypub-federation +description: ActivityPub protocol specification and federation concepts. Use when working with ActivityPub activities, understanding federation mechanics, implementing protocol features, or debugging federation issues. +--- + +# ActivityPub Federation Protocol + +This skill provides understanding of the ActivityPub protocol specification and how federation works. + +**For implementation details** (transformers, handlers, PHP code), see [PHP Conventions](../activitypub-php-conventions/SKILL.md). + +## Core Concepts + +### Three Building Blocks + +1. **Actors** - Users/accounts in the system + - Each actor has a unique URI + - Required: `inbox`, `outbox` + - Optional: `followers`, `following`, `liked` + +2. **Activities** - Actions taken by actors + - Create, Update, Delete, Follow, Like, Announce, Undo + - Wrap objects to describe how they're shared + +3. **Objects** - Content being acted upon + - Notes, Articles, Images, Videos, etc. + - Can be embedded or referenced by URI + +### Actor Structure + +```json +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Person", + "id": "https://example.com/@alice", + "inbox": "https://example.com/@alice/inbox", + "outbox": "https://example.com/@alice/outbox", + "followers": "https://example.com/@alice/followers", + "following": "https://example.com/@alice/following", + "preferredUsername": "alice", + "name": "Alice Example", + "summary": "Bio text here" +} +``` + +## Collections + +### Standard Collections + +**Inbox** - Receives incoming activities +- De-duplicate by activity ID +- Filter based on permissions +- Process activities for side effects + +**Outbox** - Publishes actor's activities +- Public record of what actor has posted +- Filtered based on viewer permissions +- Used for profile activity displays + +**Followers** - Actors following this actor +- Updated when Follow activities are Accepted +- Used for delivery targeting + +**Following** - Actors this actor follows +- Tracks subscriptions +- Used for timeline building + +### Public Addressing + +Special collection: `https://www.w3.org/ns/activitystreams#Public` + +- Makes content publicly accessible +- **Do not deliver to this URI** - it's a marker, not a real inbox +- Used in `to`, `cc`, `bto`, `bcc` fields for visibility + +## Activity Types + +### Create +Wraps newly published content: +```json +{ + "type": "Create", + "actor": "https://example.com/@alice", + "object": { + "type": "Note", + "content": "Hello, Fediverse!" + } +} +``` + +### Follow +Initiates subscription: +```json +{ + "type": "Follow", + "actor": "https://example.com/@alice", + "object": "https://other.example/@bob" +} +``` +- Recipient should respond with Accept or Reject +- Only add to followers upon Accept + +### Like +Indicates appreciation: +```json +{ + "type": "Like", + "actor": "https://example.com/@alice", + "object": "https://other.example/@bob/post/123" +} +``` + +### Announce +Reshares/boosts content: +```json +{ + "type": "Announce", + "actor": "https://example.com/@alice", + "object": "https://other.example/@bob/post/123" +} +``` + +### Update +Modifies existing content: +- Supplied properties replace existing +- `null` values remove fields +- Must include original object ID + +### Delete +Removes content: +- May replace with Tombstone for referential integrity +- Should cascade to related activities + +### Undo +Reverses previous activities: +```json +{ + "type": "Undo", + "actor": "https://example.com/@alice", + "object": { + "type": "Follow", + "id": "https://example.com/@alice/follow/123" + } +} +``` + +## Server-to-Server Federation + +### Activity Delivery Process + +1. **Resolve Recipients** + - Check `to`, `bto`, `cc`, `bcc`, `audience` fields + - Dereference collections to find individual actors + - De-duplicate recipient list + - Exclude activity's own actor + +2. **Discover Inboxes** + - Fetch actor profiles + - Extract `inbox` property + - Use `sharedInbox` if available for efficiency + +3. **Deliver via HTTP POST** + - Content-Type: `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` + - Include HTTP Signatures for authentication + - Handle delivery failures gracefully + +### Inbox Forwarding + +**Ghost Replies Problem:** When Alice replies to Bob's post that Carol follows, Carol might not see the reply if she doesn't follow Alice. + +**Solution:** Inbox forwarding +- When receiving activity addressing a local collection +- If activity references local objects +- Forward to collection members +- Ensures conversation participants see replies + +### Shared Inbox Optimization + +For public posts with many recipients on same server: +- Use `sharedInbox` endpoint instead of individual inboxes +- Reduces number of HTTP requests +- Server distributes internally + +## Addressing and Visibility + +### To/CC Fields + +- `to` - Primary recipients (public in UI) +- `cc` - Secondary recipients (copied/mentioned) +- `bto` - Blind primary (hidden in delivery) +- `bcc` - Blind secondary (hidden in delivery) + +**Important:** Remove `bto` and `bcc` before delivery to preserve privacy + +### Visibility Patterns + +**Public Post:** +```json +{ + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/@alice/followers"] +} +``` + +**Followers-Only:** +```json +{ + "to": ["https://example.com/@alice/followers"] +} +``` + +**Direct Message:** +```json +{ + "to": ["https://other.example/@bob"], + "cc": [] +} +``` + +## Content Verification + +### Security Considerations + +1. **Verify Origins** + - Don't trust claimed sources without verification + - Check HTTP Signatures + - Validate actor owns referenced objects + +2. **Prevent Spoofing** + - Mallory could claim Alice posted something + - Always verify before processing side effects + +3. **Rate Limiting** + - Limit recursive dereferencing + - Protect against denial-of-service + - Implement spam filtering + +4. **Content Sanitization** + - Clean HTML before browser rendering + - Validate media types + - Check for malicious payloads + +## Protocol Extensions + +### Supported Standards + +See `FEDERATION.md` for complete list, including: +- WebFinger - Actor discovery +- HTTP Signatures - Request authentication +- NodeInfo - Server metadata +- Various FEPs (Fediverse Enhancement Proposals) + +### FEPs Overview + +FEPs extend ActivityPub with additional features: +- Long-form text support +- Quote posts +- Activity intents +- Follower synchronization +- Actor metadata extensions + +## Implementation Notes + +### WordPress Plugin Specifics + +This plugin implements: +- **Actor Types**: User, Blog, Application +- **Transformers**: Convert WordPress content to ActivityPub objects +- **Handlers**: Process incoming activities + +For implementation details, see: +- [PHP Conventions](../activitypub-php-conventions/SKILL.md) for code structure +- [Integration Guide](../activitypub-integrations/SKILL.md) for extending + +### Testing Federation + +```bash +# Test actor endpoint +curl -H "Accept: application/activity+json" \ + https://site.com/@username + +# Test WebFinger +curl https://site.com/.well-known/webfinger?resource=acct:user@site.com + +# Test NodeInfo +curl https://site.com/.well-known/nodeinfo +``` + +## Common Issues + +### Activities Not Received +- Check inbox URL is accessible +- Verify HTTP signature validation +- Ensure content-type headers correct +- Check for firewall/security blocks + +### Replies Not Federated +- Verify inbox forwarding enabled +- Check addressing includes relevant actors +- Ensure `inReplyTo` properly set + +### Follower Sync Issues +- Check Accept activities sent for Follow +- Verify followers collection updates +- Ensure shared inbox used when available + +## Resources + +- [ActivityPub Spec](https://www.w3.org/TR/activitypub/) +- [ActivityStreams Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/) +- [Project FEDERATION.md](../../../FEDERATION.md) +- [FEPs Repository](https://codeberg.org/fediverse/fep) \ No newline at end of file diff --git a/.claude/skills/activitypub-integrations/SKILL.md b/.claude/skills/activitypub-integrations/SKILL.md new file mode 100644 index 000000000..c95145f68 --- /dev/null +++ b/.claude/skills/activitypub-integrations/SKILL.md @@ -0,0 +1,155 @@ +--- +name: activitypub-integrations +description: Third-party WordPress plugin integration patterns. Use when adding new integrations, debugging compatibility with other plugins, or working with existing integrations. +--- + +# ActivityPub Integrations + +This skill provides guidance on integrating the ActivityPub plugin with other WordPress plugins. + +## Quick Reference + +### Integration Location +All integrations live in the `integration/` directory. + +**File naming:** `class-{plugin-name}.php` (following [PHP conventions](../activitypub-php-conventions/SKILL.md)) + +### Available Integrations +- BuddyPress +- bbPress +- WooCommerce +- Jetpack +- The Events Calendar +- WP User Avatars +- And 13+ more + +For complete directory structure and naming conventions, see [PHP Conventions - Class Structure](../activitypub-php-conventions/class-structure.md). + +## Creating New Integration + +### Basic Integration Class + +```php +ID, 'price', true ); + } + return $object; +}, 10, 2 ); +``` + +## Testing Integrations + +### Verify Integration Loading + +```php +// Check if integration is active. +if ( class_exists( '\Activitypub\Integration\Plugin_Name' ) ) { + // Integration loaded. +} +``` + +### Test Compatibility + +1. Install target plugin +2. Activate ActivityPub +3. Check for conflicts +4. Verify custom post types work +5. Test federation of plugin content + +## Common Integration Issues + +### Plugin Detection +```php +// Multiple detection methods. +if ( defined( 'PLUGIN_VERSION' ) ) { } +if ( function_exists( 'plugin_function' ) ) { } +if ( class_exists( 'Plugin_Class' ) ) { } +``` + +### Hook Priority +```php +// Use appropriate priority. +add_filter( 'hook', 'callback', 20 ); // After plugin's filter. +``` + +### Namespace Conflicts +```php +// Use fully qualified names. +$object = new \Plugin\Namespace\Class(); +``` + +## Existing Integrations + +### BuddyPress +- Adds BuddyPress activity support +- Custom member transformers +- Group activity federation + +### WooCommerce +- Product post type support +- Order activity notifications +- Customer review federation + +### bbPress +- Forum topic federation +- Reply activities +- User forum profiles + +## Best Practices + +1. **Always check if plugin is active** before adding hooks +2. **Use late priority** for filters to override plugin defaults +3. **Test with multiple plugin versions** +4. **Document compatibility requirements** +5. **Handle plugin deactivation gracefully** diff --git a/.claude/skills/activitypub-php-conventions/SKILL.md b/.claude/skills/activitypub-php-conventions/SKILL.md new file mode 100644 index 000000000..97173c308 --- /dev/null +++ b/.claude/skills/activitypub-php-conventions/SKILL.md @@ -0,0 +1,394 @@ +--- +name: activitypub-php-conventions +description: PHP coding standards and WordPress patterns for ActivityPub plugin. Use when writing PHP code, creating classes, implementing WordPress hooks, structuring plugin files, or following WordPress coding conventions. +--- + +# ActivityPub PHP Conventions + +This skill provides guidance on PHP coding standards, WordPress patterns, and architectural conventions used in the ActivityPub plugin. + +**This is the authoritative source for:** +- File naming conventions +- Directory structure and organization +- WordPress coding standards +- PHP patterns and best practices + +## Quick Reference + +### File Naming +``` +class-{name}.php # Regular classes +trait-{name}.php # Traits +interface-{name}.php # Interfaces +``` + +### Namespace Pattern +```php +namespace Activitypub; +namespace Activitypub\Transformer; +namespace Activitypub\Collection; +namespace Activitypub\Handler; +``` + +### Text Domain +Always use `'activitypub'` for translations: +```php +\__( 'Text', 'activitypub' ); +\_e( 'Text', 'activitypub' ); +``` + +## File Structure + +### Standard PHP File Header +```php +init(); + } +} +``` + +### Static Initialization +```php +class Feature { + /** + * Initialize the class. + */ + public static function init() { + \add_action( 'init', array( self::class, 'register' ) ); + \add_filter( 'the_content', array( self::class, 'filter_content' ) ); + } +} +``` + +### Transformer Pattern +```php +namespace Activitypub\Transformer; + +class Custom extends Base { + /** + * Transform object to ActivityPub format. + * + * @return array The ActivityPub representation. + */ + public function transform() { + $object = parent::transform(); + // Custom transformation logic. + return $object; + } +} +``` + +## Error Handling + +### Using WP_Error +```php +// Creating errors. +if ( empty( $data ) ) { + return new \WP_Error( + 'activitypub_no_data', + \__( 'No data provided', 'activitypub' ), + array( 'status' => 400 ) + ); +} + +// Checking for errors. +if ( \is_wp_error( $result ) ) { + return $result; +} + +// Multiple error codes. +$error = new \WP_Error(); +$error->add( 'code_1', 'Message 1' ); +$error->add( 'code_2', 'Message 2' ); +``` + +## Database Patterns + +### Custom Tables (if needed) +```php +global $wpdb; +$table_name = $wpdb->prefix . 'activitypub_followers'; + +// Prepared statements. +$results = $wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM {$table_name} WHERE actor = %s", + $actor_url + ) +); +``` + +### Options API +```php +// Get option with default. +$value = \get_option( 'activitypub_setting', 'default' ); + +// Update option. +\update_option( 'activitypub_setting', $value ); + +// Delete option. +\delete_option( 'activitypub_setting' ); +``` + +### Transients +```php +// Set transient. +\set_transient( 'activitypub_cache_' . $key, $data, HOUR_IN_SECONDS ); + +// Get transient. +$cached = \get_transient( 'activitypub_cache_' . $key ); + +// Delete transient. +\delete_transient( 'activitypub_cache_' . $key ); +``` + +## REST API Patterns + +### Registering Endpoints +```php +\register_rest_route( + ACTIVITYPUB_REST_NAMESPACE, + '/users/(?P\d+)/followers', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( self::class, 'get_followers' ), + 'permission_callback' => array( self::class, 'permission_check' ), + 'args' => self::get_collection_params(), + ), + ) +); +``` + +### Permission Callbacks +```php +public static function permission_check( $request ) { + return \current_user_can( 'read' ); +} +``` + +## Security Best Practices + +### Nonce Verification +```php +if ( ! isset( $_POST['_wpnonce'] ) || + ! \wp_verify_nonce( $_POST['_wpnonce'], 'activitypub_action' ) ) { + \wp_die( 'Security check failed' ); +} +``` + +### Capability Checks +```php +if ( ! \current_user_can( 'manage_options' ) ) { + return; +} +``` + +### Data Validation +```php +// Validate URLs. +if ( ! \wp_http_validate_url( $url ) ) { + return new \WP_Error( 'invalid_url' ); +} + +// Validate email. +if ( ! \is_email( $email ) ) { + return new \WP_Error( 'invalid_email' ); +} +``` + +## Common Functions + +### ActivityPub Helper Functions +```php +// Get remote metadata. +$metadata = get_remote_metadata_by_actor( $actor_url ); + +// Convert object to URI. +$uri = object_to_uri( $object ); + +// Enrich content data. +$content = enrich_content_data( $content, $pattern, $callback ); + +// Get Webfinger resource. +$resource = Webfinger::resolve( $handle ); +``` + +### WordPress Global Functions +When in a namespace, always escape WordPress functions with backslash: `\get_option()`, `\add_action()`, etc. + +## Testing Considerations + +When writing code, consider testability: + +1. **Dependency Injection:** Pass dependencies as parameters +2. **Hooks for Testing:** Add filters/actions for test manipulation +3. **Pure Functions:** Separate logic from WordPress functions where possible +4. **Mock-friendly:** Structure code to allow mocking external calls + +## Performance Guidelines + +1. **Cache expensive operations:** Use transients +2. **Lazy loading:** Load resources only when needed +3. **Batch operations:** Process multiple items together +4. **Avoid N+1 queries:** Fetch related data in single queries +5. **Use WordPress APIs:** Leverage built-in caching diff --git a/.claude/skills/activitypub-php-conventions/class-structure.md b/.claude/skills/activitypub-php-conventions/class-structure.md new file mode 100644 index 000000000..8a7b7dd56 --- /dev/null +++ b/.claude/skills/activitypub-php-conventions/class-structure.md @@ -0,0 +1,587 @@ +# Class Structure and Organization + +## Table of Contents +- [Directory Layout](#directory-layout) +- [Class Types](#class-types) +- [Namespace Organization](#namespace-organization) +- [File Placement Guidelines](#file-placement-guidelines) +- [Integration Patterns](#integration-patterns) + +## Directory Layout + +### Core Structure +``` +wordpress-activitypub/ +├── includes/ # Core plugin functionality +│ ├── class-*.php # Main classes +│ ├── trait-*.php # Shared traits +│ ├── interface-*.php # Interfaces +│ ├── functions.php # Global functions +│ │ +│ ├── activity/ # Activity type implementations +│ │ ├── class-accept.php +│ │ ├── class-create.php +│ │ ├── class-delete.php +│ │ ├── class-follow.php +│ │ ├── class-undo.php +│ │ └── class-update.php +│ │ +│ ├── collection/ # Collection implementations +│ │ ├── class-actors.php +│ │ ├── class-extra-fields.php +│ │ ├── class-followers.php +│ │ ├── class-following.php +│ │ ├── class-posts.php +│ │ └── class-replies.php +│ │ +│ ├── handler/ # Incoming activity handlers +│ │ ├── class-create.php +│ │ ├── class-delete.php +│ │ ├── class-follow.php +│ │ ├── class-move.php +│ │ ├── class-undo.php +│ │ └── class-update.php +│ │ +│ ├── rest/ # REST API endpoints +│ │ ├── class-actors.php +│ │ ├── class-collections.php +│ │ ├── class-followers.php +│ │ ├── class-nodeinfo.php +│ │ └── class-webfinger.php +│ │ +│ ├── transformer/ # Content transformers +│ │ ├── class-activity-object.php +│ │ ├── class-attachment.php +│ │ ├── class-base.php +│ │ ├── class-comment.php +│ │ ├── class-factory.php +│ │ ├── class-json.php +│ │ ├── class-post.php +│ │ └── class-user.php +│ │ +│ └── wp-admin/ # Admin functionality +│ ├── table/ # List tables +│ │ ├── class-blocked-actors.php +│ │ └── class-list.php +│ └── views/ # Admin views +│ +├── integration/ # Third-party plugin integrations +│ ├── load.php # Integration loader +│ └── class-{plugin}.php # Individual integrations +│ +├── templates/ # Template files +│ ├── activitypub-json.php +│ ├── blog.php +│ └── user.php +│ +└── tests/ # Test files + ├── phpunit/ + └── e2e/ +``` + +## Class Types + +### Core Classes + +Located in `includes/`: + +```php +// Main plugin class. +class Activitypub { + public static function init() { + // Initialize plugin. + } +} + +// Specific feature classes. +class Scheduler { + // Handle scheduled tasks. +} + +class Signature { + // Handle HTTP signatures. +} + +class Webfinger { + // Handle Webfinger protocol. +} +``` + +### Activity Classes + +Located in `includes/activity/`: + +```php +namespace Activitypub\Activity; + +/** + * Base activity class. + */ +abstract class Base { + protected $type; + protected $actor; + protected $object; + + abstract public function to_array(); +} + +/** + * Specific activity implementation. + */ +class Follow extends Base { + protected $type = 'Follow'; + + public function to_array() { + // Implementation. + } +} +``` + +### Handler Classes + +Located in `includes/handler/`: + +```php +namespace Activitypub\Handler; + +/** + * Handle incoming Follow activities. + */ +class Follow { + /** + * Process the activity. + * + * @param array $activity The activity data. + * @param int $user_id The target user. + */ + public static function handle( $activity, $user_id ) { + // Process follow request. + } +} +``` + +### Transformer Classes + +Located in `includes/transformer/`: + +```php +namespace Activitypub\Transformer; + +/** + * Base transformer class. + */ +abstract class Base { + protected $object; + + /** + * Transform to ActivityPub format. + * + * @return array + */ + abstract public function transform(); +} + +/** + * Post transformer. + */ +class Post extends Base { + /** + * @var WP_Post + */ + protected $post; + + public function transform() { + return array( + 'type' => 'Note', + 'content' => $this->get_content(), + // ... + ); + } +} +``` + +### Collection Classes + +Located in `includes/collection/`: + +```php +namespace Activitypub\Collection; + +/** + * Followers collection + */ +class Followers { + /** + * Get followers for a user. + * + * @param int $user_id User ID. + * @return array + */ + public static function get( $user_id ) { + // Return followers. + } + + /** + * Add a follower. + * + * @param int $user_id User ID. + * @param string $actor Actor URL. + */ + public static function add( $user_id, $actor ) { + // Add follower. + } +} +``` + +### REST API Classes + +Located in `includes/rest/`: + +**IMPORTANT:** All REST API classes must extend WordPress Core's `WP_REST_Controller` class to ensure proper integration with the WordPress REST API infrastructure. + +```php +namespace Activitypub\Rest; + +/** + * REST API endpoint class - MUST extend WP_REST_Controller. + */ +class Webfinger extends \WP_REST_Controller { + /** + * The namespace of this controller's route. + * + * @var string + */ + protected $namespace = ACTIVITYPUB_REST_NAMESPACE; + + /** + * The base of this controller's route. + * + * @var string + */ + protected $rest_base = 'webfinger'; + + /** + * Register routes. + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/.well-known/webfinger', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + ) + ); + } + + /** + * Permission check. + * + * @param \WP_REST_Request $request Request object. + * @return true|WP_Error + */ + public function get_item_permissions_check( $request ) { + return true; // Public endpoint. + } + + /** + * Handle GET request. + * + * @param \WP_REST_Request $request Request object. + * @return \WP_REST_Response|\WP_Error + */ + public function get_item( $request ) { + // Implementation. + return new \WP_REST_Response( $data, 200 ); + } +} +``` + +**Benefits of extending WP_REST_Controller:** +- Provides standard methods for CRUD operations +- Built-in permission callback support +- Consistent parameter handling and validation +- Schema definition support +- Proper integration with WordPress REST API discovery +- Standard response formatting + +## Namespace Organization + +### Namespace Hierarchy + +```php +// Root namespace. +namespace Activitypub; + +// Feature namespaces. +namespace Activitypub\Activity; +namespace Activitypub\Collection; +namespace Activitypub\Handler; +namespace Activitypub\Rest; +namespace Activitypub\Transformer; +namespace Activitypub\Integration; + +// Admin namespaces. +namespace Activitypub\Wp_Admin; +namespace Activitypub\Wp_Admin\Table; +``` + +### Using Namespaces + +```php +ID, 'audio_file', true ) + ) { + return new Seriously_Simple_Podcasting( $data ); + } + + return $transformer; + }, + 10, + 3 + ); + } +} +\add_action( 'plugins_loaded', __NAMESPACE__ . '\plugin_init' ); + +// Some integrations hook into plugin-specific actions. +\add_action( 'bp_include', array( __NAMESPACE__ . '\Buddypress', 'init' ), 0 ); + +// Activation/deactivation hooks for cache plugins. +\register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'add_cache_config' ) ); +\register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'remove_cache_config' ) ); +``` + +**Key patterns:** +- No centralized list - each integration is explicitly checked +- Detection methods: constants (`AKISMET_VERSION`), class existence, options +- Most use static `::init()` method pattern +- Some integrations use inline filters for simple transformations +- Special hooks for plugin lifecycle (BuddyPress uses `bp_include`) +- Cache plugins need activation/deactivation hooks + +## Class Design Patterns + +### Singleton Pattern + +```php +class Manager { + private static $instance = null; + + private function __construct() { + // Private constructor. + } + + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + return self::$instance; + } +} +``` + +### Factory Pattern + +```php +namespace Activitypub\Transformer; + +class Factory { + /** + * Get transformer for object. + * + * @param mixed $object Object to transform. + * @return Base Transformer instance. + */ + public static function get( $object ) { + if ( $object instanceof \WP_Post ) { + return new Post( $object ); + } + + if ( $object instanceof \WP_Comment ) { + return new Comment( $object ); + } + + return new Json( $object ); + } +} +``` + +### Static Initialization + +```php +class Feature { + /** + * Initialize the feature. + */ + public static function init() { + // Add hooks + \add_action( 'init', array( self::class, 'register' ) ); + \add_filter( 'the_content', array( self::class, 'filter' ) ); + } + + /** + * Register functionality. + */ + public static function register() { + // Registration logic. + } +} +``` + +### Dependency Injection + +```php +class Processor { + private $transformer; + private $validator; + + public function __construct( Transformer $transformer, Validator $validator ) { + $this->transformer = $transformer; + $this->validator = $validator; + } + + public function process( $data ) { + if ( $this->validator->validate( $data ) ) { + return $this->transformer->transform( $data ); + } + return false; + } +} +``` diff --git a/.claude/skills/activitypub-php-conventions/coding-standards.md b/.claude/skills/activitypub-php-conventions/coding-standards.md new file mode 100644 index 000000000..600228693 --- /dev/null +++ b/.claude/skills/activitypub-php-conventions/coding-standards.md @@ -0,0 +1,477 @@ +# PHP Coding Standards Reference + +## Table of Contents +- [WordPress Coding Standards](#wordpress-coding-standards) +- [File Organization](#file-organization) +- [Naming Conventions](#naming-conventions) +- [Documentation Standards](#documentation-standards) +- [Security Practices](#security-practices) +- [Performance Considerations](#performance-considerations) + +## WordPress Coding Standards + +The ActivityPub plugin follows WordPress Coding Standards with PHPCS configuration: + +### PHPCS Rules Applied +- `WordPress` - Full WordPress coding standards +- `PHPCompatibility` - PHP 7.2+ compatibility +- `PHPCompatibilityWP` - WordPress 6.5+ compatibility +- `VariableAnalysis` - Detect undefined/unused variables + +### Indentation and Spacing + +```php +// Use tabs for indentation. +function example_function() { +→ $variable = 'value'; +→ if ( $condition ) { +→ → do_something(); +→ } +} + +// Space inside parentheses. +if ( $condition ) { // Correct. +if ($condition) { // Incorrect. + +// Space around operators. +$sum = $a + $b; // Correct. +$sum=$a+$b; // Incorrect. + +// Array formatting - use array() syntax. +$array = array( +→ 'key_one' => 'value', +→ 'key_two' => 'value', +→ 'key_three' => 'value', +); + +// DO NOT use short array syntax [] - WordPress standards require array(). +$array = [ // Incorrect - do not use. +→ 'key_one' => 'value', +→ 'key_two' => 'value', +→ 'key_three' => 'value', +]; +``` + +### Control Structures + +```php +// If statements. +if ( $condition ) { +→ // Code. +} elseif ( $other_condition ) { +→ // Code. +} else { +→ // Code. +} + +// Switch statements. +switch ( $variable ) { +→ case 'value1': +→ → do_something(); +→ → break; + +→ case 'value2': +→ → do_something_else(); +→ → break; + +→ default: +→ → do_default(); +} + +// Loops. +foreach ( $items as $key => $item ) { +→ \process_item( $item ); +} + +while ( $condition ) { +→ // Code. +} + +for ( $i = 0; $i < 10; $i++ ) { +→ // Code. +} +``` + +### Yoda Conditions + +WordPress recommends Yoda conditions to prevent assignment errors: + +```php +// Yoda conditions (recommended). +if ( 'value' === $variable ) { +if ( true === $condition ) { +if ( null !== $result ) { + +// But readable conditions are also acceptable. +if ( $user->has_cap( 'edit_posts' ) ) { +if ( \is_array( $data ) ) { +``` + +## File Organization + +### File Naming Patterns + +``` +includes/ +├── class-{feature}.php # Regular classes +├── trait-{behavior}.php # Shared traits +├── interface-{contract}.php # Interfaces +├── functions.php # Global functions +├── deprecated.php # Deprecated functions +``` + +### File Header Template + +```php +'; + +// Escape URLs. +echo 'Link'; + +// Escape JavaScript. +echo ''; + +// Escape SQL. +$sql = $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ); + +// Allow specific HTML tags. +echo \wp_kses( $html, array( + 'a' => array( + 'href' => array(), + 'title' => array(), + ), + 'br' => array(), + 'em' => array(), + 'strong' => array(), +) ); +``` + +### Nonce Verification + +```php +// Create nonce. +\wp_nonce_field( 'activitypub_action', 'activitypub_nonce' ); + +// Verify nonce. +if ( ! \isset( $_POST['activitypub_nonce'] ) || + ! \wp_verify_nonce( $_POST['activitypub_nonce'], 'activitypub_action' ) ) { + \wp_die( \__( 'Security check failed', 'activitypub' ) ); +} + +// AJAX nonce. +\check_ajax_referer( 'activitypub_ajax', 'nonce' ); +``` + +### Capability Checks + +```php +// Check user capabilities. +if ( ! \current_user_can( 'edit_posts' ) ) { + \wp_die( \__( 'Insufficient permissions', 'activitypub' ) ); +} + +// Custom capability. +if ( ! \current_user_can( 'activitypub_manage_followers' ) ) { + return new \WP_Error( 'forbidden', \__( 'Access denied', 'activitypub' ) ); +} + +// Check specific object capability. +if ( ! \current_user_can( 'edit_post', $post_id ) ) { + return false; +} +``` + +## Performance Considerations + +### Caching Strategies + +```php +// Use transients for temporary data. +$cache_key = 'activitypub_data_' . \md5( \serialize( $args ) ); +$cached = \get_transient( $cache_key ); + +if ( false === $cached ) { + $cached = \expensive_operation(); + \set_transient( $cache_key, $cached, HOUR_IN_SECONDS ); +} + +// Object caching. +\wp_cache_set( 'key', $data, 'activitypub', 3600 ); +$data = \wp_cache_get( 'key', 'activitypub' ); + +// Static caching in class. +class Example { + private static $cache = array(); + + public static function get_data( $id ) { + if ( ! \isset( self::$cache[ $id ] ) ) { + self::$cache[ $id ] = \fetch_data( $id ); + } + return self::$cache[ $id ]; + } +} +``` + +### Database Optimization + +```php +// Use get_posts() instead of WP_Query when possible. +$posts = \get_posts( array( + 'post_type' => 'post', + 'posts_per_page' => 10, + 'meta_key' => 'activitypub_id', + 'fields' => 'ids', // Only get IDs if that's all you need. +) ); + +// Batch database operations. +$values = array(); +foreach ( $items as $item ) { + $values[] = $wpdb->prepare( '(%s, %s)', $item['key'], $item['value'] ); +} + +if ( $values ) { + $wpdb->query( + "INSERT INTO {$table} (key, value) VALUES " . \implode( ',', $values ) + ); +} + +// Use LIMIT in custom queries. +$results = $wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM {$table} WHERE status = %s LIMIT %d", + 'active', + 100 + ) +); +``` + +## Error Handling + +### WP_Error Usage + +```php +// Create single error. +return new \WP_Error( + 'activitypub_invalid_actor', + \__( 'Invalid actor URL provided', 'activitypub' ), + array( 'status' => 400, 'actor' => $actor ) +); + +// Check for errors. +$result = \remote_request( $url ); +if ( \is_wp_error( $result ) ) { + \error_log( 'ActivityPub Error: ' . $result->get_error_message() ); + return $result; +} + +// Add multiple errors. +$errors = new \WP_Error(); + +if ( \empty( $data['id'] ) ) { + $errors->add( 'missing_id', \__( 'ID is required', 'activitypub' ) ); +} + +if ( \empty( $data['inbox'] ) ) { + $errors->add( 'missing_inbox', \__( 'Inbox URL is required', 'activitypub' ) ); +} + +if ( $errors->has_errors() ) { + return $errors; +} +``` + +### Exception Handling + +```php +try { + $result = \risky_operation(); +} catch ( \Exception $e ) { + \error_log( 'ActivityPub Exception: ' . $e->getMessage() ); + return new \WP_Error( + 'activitypub_exception', + $e->getMessage(), + array( 'code' => $e->getCode() ) + ); +} +``` diff --git a/.claude/skills/activitypub-php-conventions/examples.md b/.claude/skills/activitypub-php-conventions/examples.md new file mode 100644 index 000000000..e21cc6fce --- /dev/null +++ b/.claude/skills/activitypub-php-conventions/examples.md @@ -0,0 +1,799 @@ +# PHP Code Examples + +## Table of Contents +- [Complete Class Example](#complete-class-example) +- [Transformer Example](#transformer-example) +- [Handler Example](#handler-example) +- [REST Endpoint Example](#rest-endpoint-example) +- [Integration Example](#integration-example) + +## Complete Class Example + +### Full-featured ActivityPub Class + +```php +init(); + } + + /** + * Initialize the notification manager. + */ + private function init() { + \add_action( 'transition_post_status', array( $this, 'handle_post_transition' ), 10, 3 ); + \add_action( 'activitypub_send_notifications', array( $this, 'process_queue' ) ); + \add_filter( 'activitypub_notification_recipients', array( $this, 'filter_recipients' ), 10, 2 ); + } + + /** + * Handle post status transitions. + * + * @param string $new_status New post status. + * @param string $old_status Old post status. + * @param WP_Post $post Post object. + * + * @return void + */ + public function handle_post_transition( $new_status, $old_status, $post ) { + // Only notify for newly published posts. + if ( 'publish' !== $new_status || 'publish' === $old_status ) { + return; + } + + // Check if post type supports ActivityPub. + if ( ! \post_type_supports( $post->post_type, 'activitypub' ) ) { + return; + } + + // Add to queue. + $this->queue_notification( $post ); + } + + /** + * Queue a notification for sending. + * + * @param WP_Post $post The post to notify about. + * + * @return bool True on success, false on failure. + */ + public function queue_notification( $post ) { + if ( ! $post instanceof \WP_Post ) { + return false; + } + + $notification = array( + 'post_id' => $post->ID, + 'author_id' => $post->post_author, + 'timestamp' => time(), + ); + + $this->queue[] = $notification; + + // Schedule processing if not already scheduled. + if ( ! \wp_next_scheduled( 'activitypub_send_notifications' ) ) { + \wp_schedule_single_event( time() + 10, 'activitypub_send_notifications' ); + } + + return true; + } + + /** + * Process the notification queue. + */ + public function process_queue() { + if ( empty( $this->queue ) ) { + return; + } + + foreach ( $this->queue as $key => $notification ) { + $result = $this->send_notification( $notification ); + + if ( ! \is_wp_error( $result ) ) { + unset( $this->queue[ $key ] ); + } + } + + // Re-index array. + $this->queue = array_values( $this->queue ); + } + + /** + * Send a notification. + * + * @param array $notification The notification data. + * + * @return true|\WP_Error True on success, WP_Error on failure. + */ + private function send_notification( $notification ) { + $post = \get_post( $notification['post_id'] ); + + if ( ! $post ) { + return new \WP_Error( + 'activitypub_post_not_found', + \__( 'Post not found', 'activitypub' ), + array( 'post_id' => $notification['post_id'] ) + ); + } + + // Get recipients. + $recipients = $this->get_recipients( $notification['author_id'] ); + + if ( empty( $recipients ) ) { + return true; // No recipients, but not an error. + } + + /** + * Filter the recipients before sending. + * + * @param array $recipients List of recipient URLs. + * @param array $notification Notification data. + */ + $recipients = \apply_filters( 'activitypub_notification_recipients', $recipients, $notification ); + + // Create activity. + $activity = new Create(); + $activity->set_object( $post ); + + // Send to each recipient. + $errors = array(); + foreach ( $recipients as $recipient ) { + $result = $this->send_to_inbox( $activity, $recipient ); + + if ( \is_wp_error( $result ) ) { + $errors[] = $result; + } + } + + if ( ! empty( $errors ) ) { + return new \WP_Error( 'activitypub_notification_errors', \__( 'Some notifications failed to send', 'activitypub' ), $errors ); + } + + return true; + } + + /** + * Get recipients for notifications. + * + * @param int $user_id The user ID. + * + * @return array List of inbox URLs. + */ + private function get_recipients( $user_id ) { + $followers = Followers::get( $user_id ); + + if ( empty( $followers ) ) { + return array(); + } + + $inboxes = array(); + foreach ( $followers as $follower ) { + if ( ! empty( $follower['inbox'] ) ) { + $inboxes[] = $follower['inbox']; + } + } + + return array_unique( $inboxes ); + } + + /** + * Send activity to an inbox. + * + * @param Activity $activity The activity to send. + * @param string $inbox The inbox URL. + * + * @return true|\WP_Error True on success, WP_Error on failure. + */ + private function send_to_inbox( $activity, $inbox ) { + $response = \wp_remote_post( + $inbox, + array( + 'body' => \wp_json_encode( $activity->to_array() ), + 'headers' => array( + 'Content-Type' => 'application/activity+json', + ), + 'timeout' => 30, + ) + ); + + if ( \is_wp_error( $response ) ) { + return $response; + } + + $code = \wp_remote_retrieve_response_code( $response ); + + if ( $code >= 400 ) { + return new \WP_Error( + 'activitypub_inbox_error', + \sprintf( + /* translators: 1: HTTP status code, 2: Inbox URL */ + \__( 'Inbox returned %1$d for %2$s', 'activitypub' ), + $code, + $inbox + ) + ); + } + + return true; + } + + /** + * Filter notification recipients. + * + * @param array $recipients Current recipients. + * @param array $notification Notification data. + * + * @return array Filtered recipients. + */ + public function filter_recipients( $recipients, $notification ) { + // Remove blocked actors. + $blocked = \get_option( 'activitypub_blocked_actors', array() ); + + if ( ! empty( $blocked ) ) { + $recipients = array_diff( $recipients, $blocked ); + } + + // Limit number of recipients. + $max_recipients = \apply_filters( 'activitypub_max_recipients', 100 ); + + if ( count( $recipients ) > $max_recipients ) { + $recipients = array_slice( $recipients, 0, $max_recipients ); + } + + return $recipients; + } +} +``` + +## Transformer Example + +### Custom Post Type Transformer + +```php +get_start_time(); + $object['endTime'] = $this->get_end_time(); + $object['location'] = $this->get_location(); + + return $object; + } + + /** + * Get event start time. + * + * @return string ISO 8601 formatted datetime. + */ + protected function get_start_time() { + $start = \get_post_meta( $this->post->ID, 'event_start', true ); + + if ( ! $start ) { + return ''; + } + + return \gmdate( 'c', strtotime( $start ) ); + } + + /** + * Get event end time. + * + * @return string ISO 8601 formatted datetime. + */ + protected function get_end_time() { + $end = \get_post_meta( $this->post->ID, 'event_end', true ); + + if ( ! $end ) { + return ''; + } + + return \gmdate( 'c', strtotime( $end ) ); + } + + /** + * Get event location. + * + * @return array Location object. + */ + protected function get_location() { + $venue = \get_post_meta( $this->post->ID, 'event_venue', true ); + + if ( ! $venue ) { + return null; + } + + return array( + 'type' => 'Place', + 'name' => $venue, + 'address' => $this->get_address(), + ); + } + + /** + * Get event address. + * + * @return array|null Address object. + */ + private function get_address() { + $address = \get_post_meta( $this->post->ID, 'event_address', true ); + + if ( ! $address ) { + return null; + } + + return array( + 'type' => 'PostalAddress', + 'streetAddress' => $address['street'] ?? '', + 'addressLocality' => $address['city'] ?? '', + 'postalCode' => $address['zip'] ?? '', + 'addressCountry' => $address['country'] ?? '', + ); + } +} +``` + +## Handler Example + +### Incoming Activity Handler + +```php + 'activitypub_id', + 'meta_value' => $url, + 'posts_per_page' => 1, + 'post_type' => 'any', + 'fields' => 'ids', + ) + ); + + if ( ! empty( $posts ) ) { + return $posts[0]; + } + + // Try to find by permalink. + $post_id = \url_to_postid( $url ); + + if ( $post_id ) { + return $post_id; + } + + return false; + } +} +``` + +## REST Endpoint Example + +### Custom REST API Endpoint + +```php + \WP_REST_Server::READABLE, + 'callback' => array( self::class, 'get_statistics' ), + 'permission_callback' => array( self::class, 'get_permissions_check' ), + 'args' => self::get_collection_params(), + ), + ) + ); + } + + /** + * Check permissions for the request. + * + * @param \WP_REST_Request $request The request object. + * + * @return true|\WP_Error + */ + public static function get_permissions_check( $request ) { + if ( ! \current_user_can( 'manage_options' ) ) { + return new \WP_Error( 'rest_forbidden', \__( 'You do not have permission to view statistics.', 'activitypub' ), array( 'status' => 403 ) ); + } + + return true; + } + + /** + * Get statistics. + * + * @param \WP_REST_Request $request The request object. + * + * @return \WP_REST_Response + */ + public static function get_statistics( $request ) { + $user_id = $request->get_param( 'user_id' ); + $period = $request->get_param( 'period' ) ?? 'week'; + + $stats = array( + 'followers' => self::get_follower_count( $user_id ), + 'following' => self::get_following_count( $user_id ), + 'posts' => self::get_post_count( $user_id, $period ), + 'interactions' => self::get_interaction_count( $user_id, $period ), + ); + + return new \WP_REST_Response( $stats, 200 ); + } + + /** + * Get collection parameters. + * + * @return array + */ + public static function get_collection_params() { + return array( + 'user_id' => array( + 'description' => \__( 'User ID to get statistics for.', 'activitypub' ), + 'type' => 'integer', + 'sanitize_callback' => 'absint', + ), + 'period' => array( + 'description' => \__( 'Time period for statistics.', 'activitypub' ), + 'type' => 'string', + 'enum' => array( 'day', 'week', 'month', 'year' ), + 'default' => 'week', + 'sanitize_callback' => 'sanitize_text_field', + ), + ); + } + + /** + * Get follower count. + * + * @param int|null $user_id User ID. + * + * @return int + */ + private static function get_follower_count( $user_id = null ) { + // Implementation. + return 0; + } + + /** + * Get following count. + * + * @param int|null $user_id User ID. + * + * @return int + */ + private static function get_following_count( $user_id = null ) { + // Implementation. + return 0; + } + + /** + * Get post count. + * + * @param int|null $user_id User ID. + * @param string $period Time period. + * + * @return int + */ + private static function get_post_count( $user_id = null, $period = 'week' ) { + // Implementation. + return 0; + } + + /** + * Get interaction count. + * + * @param int|null $user_id User ID. + * @param string $period Time period. + * + * @return int + */ + private static function get_interaction_count( $user_id = null, $period = 'week' ) { + // Implementation. + return 0; + } +} +``` + +## Integration Example + +### Third-Party Plugin Integration + +```php +post_type ) { + require_once __DIR__ . '/transformer/class-product.php'; + return new \Transformer\Product( $post ); + } + + return $transformer; + } + + /** + * Add WooCommerce post types. + * + * @param array $post_types Current post types. + * + * @return array + */ + public static function add_post_types( $post_types ) { + $post_types[] = 'product'; + return $post_types; + } + + /** + * Handle completed orders. + * + * @param int $order_id Order ID. + */ + public static function handle_order_complete( $order_id ) { + $order = \wc_get_order( $order_id ); + + if ( ! $order ) { + return; + } + + /** + * Fires when a WooCommerce order is completed. + * + * @param \WC_Order $order The order object. + */ + \do_action( 'activitypub_woocommerce_order_complete', $order ); + } +} +``` diff --git a/.claude/skills/activitypub-pr-workflow/SKILL.md b/.claude/skills/activitypub-pr-workflow/SKILL.md new file mode 100644 index 000000000..22e485d74 --- /dev/null +++ b/.claude/skills/activitypub-pr-workflow/SKILL.md @@ -0,0 +1,328 @@ +--- +name: activitypub-pr-workflow +description: Pull request creation and review workflow for WordPress ActivityPub plugin. Use when creating branches, managing PRs, adding changelogs, assigning reviewers, or following branch naming conventions. +--- + +# ActivityPub PR Workflow + +This skill provides guidance for creating and managing pull requests in the WordPress ActivityPub plugin repository. + +## Quick Reference + +### Branch Naming +```bash +add/{feature} # New features +update/{feature} # Iterating on existing features +fix/{bug} # Bug fixes +try/{idea} # Experimental ideas +``` + +### PR Requirements +- ✅ Changelog entry (or "Skip Changelog" label) +- ✅ Passing CI checks +- ✅ Clean merge with trunk +- ✅ Assign @me +- ✅ Add Fediverse team as reviewer +- ✅ Proper branch naming + +## Creating a Branch + +### Branch Naming Convention + +Choose the appropriate prefix: + +| Prefix | Use Case | Example | +|--------|----------|---------| +| `add/` | New feature or functionality | `add/mastodon-api` | +| `update/` | Enhance existing feature | `update/follower-list` | +| `fix/` | Fix a bug | `fix/signature-verification` | +| `try/` | Experimental idea for feedback | `try/new-transformer` | + +**Reserved branches:** +- `release/{X.Y.Z}` - Used for release process only +- `trunk` - Main development branch + +### Creating Your Branch + +```bash +# Always branch from trunk +git checkout trunk +git pull origin trunk + +# Create new branch with appropriate prefix +git checkout -b fix/notification-issue + +# Or for a new feature +git checkout -b add/custom-emoji-support +``` + +## Development Workflow + +See [checklist.md](checklist.md) for complete PR checklist. + +### 1. Break Down Features + +**Important:** Break your feature into small pieces. Each piece should be its own PR. + +Good approach: +- PR 1: Add database schema +- PR 2: Add REST endpoint +- PR 3: Add UI component +- PR 4: Add tests + +Bad approach: +- Single PR with all changes + +### 2. Commit Best Practices + +```bash +# Make frequent commits +git add . +git commit -m "Add follower validation" + +# Squash minor fixes +git rebase -i HEAD~3 + +# Force push to your branch (safe if you're the only one) +git push --force-with-lease origin fix/something +``` + +**CRITICAL:** Never add Claude Code as a co-author or mention AI tools in commit messages. + +### 3. Prepare Changelog Information + +For the PR template changelog section: +- Select type: Added/Fixed/Changed/Deprecated/Removed/Security +- Write clear description +- **Always end with proper punctuation!** + +See [Release Process - Changelog Management](../activitypub-release/SKILL.md#changelog-management) for complete details. + +## Creating the Pull Request + +### PR Template + +The repository uses `.github/PULL_REQUEST_TEMPLATE.md` which includes: + +1. **Changelog Entry Section:** + - Check "Automatically create a changelog entry" checkbox + - Select **Significance** (Patch/Minor/Major) + - Select **Type** (Added/Changed/Deprecated/Removed/Fixed/Security) + - Write **Message** (must end with punctuation!) + - OR add "Skip Changelog" label if no changelog needed + +2. **PR Description:** + - Summary of changes + - Testing instructions + - Screenshots for visual changes + +See the full template at `.github/PULL_REQUEST_TEMPLATE.md` + +### Assignment and Review + +**Required for every PR:** +- **Always assign yourself** as assignee +- **Always add Fediverse** as reviewer +- Add other relevant reviewers if needed + +**CRITICAL:** Never mention Claude Code in commits, PR descriptions, comments, or anywhere in the codebase. + +### PR Checklist + +Before marking ready for review: + +- [ ] Changelog details filled in PR template (or "Skip Changelog" label added) +- [ ] All CI checks passing +- [ ] Branch merges cleanly with trunk +- [ ] Code follows WordPress standards (`composer lint`) +- [ ] Tests added/updated if applicable +- [ ] Documentation updated if needed +- [ ] Self-assigned and reviewer added + +## Code Quality + +### Pre-Push Checks + +```bash +# Check PHP standards +composer lint +composer lint:fix + +# Check JavaScript/CSS if needed +npm run lint:js +npm run lint:css + +# Run tests +npm run env-test + +# Check for merge conflicts +git fetch origin +git merge origin/trunk --no-commit --no-ff +git merge --abort # If just checking +``` + +### Handling CI Failures + +If CI fails: +1. Check the specific failing job +2. Fix locally and test +3. Push fix to your branch +4. CI will re-run automatically + +## Review Process + +### Responding to Feedback + +```bash +# Make requested changes +git add . +git commit -m "Address review feedback" +git push + +# Or amend if minor +git commit --amend +git push --force-with-lease +``` + +### Re-requesting Review + +After addressing feedback: +1. Click "Re-request review" button +2. Leave comment summarizing changes +3. Ensure CI still passes + +## Keeping Branch Updated + +### Rebasing Against Trunk + +```bash +# Fetch latest +git fetch origin + +# Rebase your branch +git rebase origin/trunk + +# Resolve conflicts if any +git add . +git rebase --continue + +# Force push +git push --force-with-lease +``` + +### When to Rebase vs Merge + +- **Rebase:** For clean history before merging +- **Merge:** If branch is shared with others +- **Always rebase** before final merge to trunk + +## Special Cases + +### Large Features + +For features requiring multiple PRs: +1. Create tracking issue +2. Link all related PRs to issue +3. Use consistent branch naming: `add/feature-part-1`, `add/feature-part-2` +4. Merge in order + +### Hotfixes + +For urgent fixes: +1. Branch from trunk: `fix/critical-issue` +2. Minimal changeset +3. Add "Hotfix" label +4. Request expedited review + +### Experimental Changes + +For trying ideas: +1. Use `try/` prefix +2. Mark as draft PR +3. Request feedback early +4. Convert to proper branch type once approach confirmed + +## Common Issues + +### Merge Conflicts + +```bash +# Update your branch +git fetch origin +git rebase origin/trunk + +# Resolve conflicts +# Edit conflicted files +git add . +git rebase --continue + +# Or abort if needed +git rebase --abort +``` + +### Changelog Conflicts + +If changelog conflicts: +1. Keep both changes +2. Ensure proper formatting +3. Verify entries are in correct section + +### Forgotten Changelog + +If you forgot changelog before PR: +```bash +# On your branch +composer changelog:add + +# Commit and push +git add . +git commit -m "Add changelog entry" +git push +``` + +## Best Practices + +### Do's +- ✅ Small, focused PRs +- ✅ Descriptive branch names +- ✅ Clear commit messages +- ✅ Test locally first +- ✅ Add screenshots for UI changes +- ✅ Update documentation + +### Don'ts +- ❌ Large, multi-purpose PRs +- ❌ Force push to trunk +- ❌ Merge without review +- ❌ Skip changelog without label +- ❌ Ignore CI failures +- ❌ Leave PRs in draft unnecessarily + +## PR Labels + +Common labels to use: +- `[Type] Bug` - Bug fixes +- `[Type] Enhancement` - New features +- `[Type] Documentation` - Doc updates +- `Skip Changelog` - No changelog needed +- `Needs Review` - Ready for review +- `In Progress` - Still working + +## Quick Commands + +```bash +# Create branch +git checkout -b add/new-feature + +# Check standards +composer lint + +# Create PR (using GitHub CLI) +gh pr create --assignee @me --reviewer Fediverse + +# Check PR status +gh pr status + +# View PR checks +gh pr checks +``` diff --git a/.claude/skills/activitypub-pr-workflow/checklist.md b/.claude/skills/activitypub-pr-workflow/checklist.md new file mode 100644 index 000000000..f58178506 --- /dev/null +++ b/.claude/skills/activitypub-pr-workflow/checklist.md @@ -0,0 +1,199 @@ +# Pull Request Checklist + +## Before Creating PR + +### Code Preparation +- [ ] Branch created from latest `trunk` +- [ ] Branch follows naming convention (`add/`, `update/`, `fix/`, `try/`) +- [ ] Changes are focused and single-purpose +- [ ] Code follows [WordPress coding standards](../activitypub-php-conventions/coding-standards.md) +- [ ] No debug code or console.logs left + +### Testing +- [ ] PHP tests pass: `npm run env-test` +- [ ] Linting passes: `composer lint` +- [ ] JavaScript linting passes: `npm run lint:js` +- [ ] No regressions in existing functionality + +### Documentation +- [ ] Changelog entry created: `composer changelog:add` +- [ ] Changelog entry ends with proper punctuation +- [ ] Code comments added where needed +- [ ] README updated if adding new feature +- [ ] Inline documentation follows WordPress standards (trailing periods, etc.) + +## Creating the PR + +### PR Description +- [ ] Clear, descriptive title +- [ ] Summary section explains the change +- [ ] Testing instructions provided +- [ ] Screenshots added for visual changes +- [ ] Related issue linked (if applicable) + +### GitHub Settings +- [ ] Self-assigned as assignee +- [ ] pfefferle added as reviewer +- [ ] Appropriate labels added +- [ ] Milestone set (if applicable) + +## After Creating PR + +### CI/CD +- [ ] All CI checks passing +- [ ] No merge conflicts with trunk +- [ ] Code coverage maintained or improved + +### Review Process +- [ ] Responded to all review comments +- [ ] Requested re-review after changes +- [ ] Resolved conversations that are addressed +- [ ] Thanked reviewers for their time + +## Before Merge + +### Final Checks +- [ ] Branch is up to date with trunk +- [ ] All review feedback addressed +- [ ] CI still passing after final changes +- [ ] Changelog entry still accurate +- [ ] No accidental files included + +### Clean History +- [ ] Commits are logical and well-organized +- [ ] Fixup commits squashed +- [ ] Commit messages are clear +- [ ] No merge commits (use rebase) + +## PR Description Template + +```markdown +## Summary + + +## Why? + + +## Changes + +- +- +- + +## Testing Instructions + +1. +2. +3. + +## Screenshots + +### Before +![Before]() + +### After +![After]() + +## Checklist +- [ ] Changelog entry added +- [ ] Tests added/updated +- [ ] Documentation updated +- [ ] Follows coding standards + +## Related Issues + +Fixes # +Related to # +``` + +## Special Situations + +### Hotfix PR +- [ ] Marked with "Hotfix" label +- [ ] Minimal changeset +- [ ] Tested thoroughly despite urgency +- [ ] Changelog marks as patch release + +### Breaking Changes +- [ ] Marked with "Breaking Change" label +- [ ] Migration guide provided +- [ ] Major version bump indicated +- [ ] Deprecation notices added where needed + +### New Feature +- [ ] Feature flag added (if applicable) +- [ ] Documentation added +- [ ] Examples provided +- [ ] Performance impact assessed + +### Bug Fix +- [ ] Root cause identified +- [ ] Test added to prevent regression +- [ ] Related issues linked +- [ ] Verified fix doesn't break other features + +## Common Review Feedback + +### Code Quality +- "Please add error handling here" +- "This could use a comment explaining why" +- "Consider extracting this to a method" +- "Please add type hints" + +### Testing +- "Please add a test for this edge case" +- "Can you verify this works with [scenario]" +- "What happens when [condition]" + +### Documentation +- "Please update the docblock" +- "The changelog needs more detail" +- "Can you add an example" + +### Performance +- "This could cause N+1 queries" +- "Consider caching this result" +- "This might be expensive for large datasets" + +## Commit Message Guidelines + +### Format +``` +Type: Brief description + +Longer explanation if needed. +Multiple paragraphs are fine. + +Fixes #123 +``` + +### Types +- `Add:` New feature +- `Fix:` Bug fix +- `Update:` Enhancement to existing feature +- `Remove:` Removed functionality +- `Refactor:` Code restructuring +- `Test:` Test additions/changes +- `Docs:` Documentation only + +### Examples +``` +Fix: Correct signature verification for Delete activities + +The signature verification was failing for Delete activities +because the actor URL was not being properly extracted. + +This commit extracts the actor from the activity object +and uses it for verification. + +Fixes #456 +``` + +**Important:** Never mention AI tools, coding assistants, or automation tools in commit messages, PR descriptions, code comments, or anywhere in the repository. + +## Resources + +- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/) +- [GitHub PR Documentation](https://docs.github.com/en/pull-requests) +- [Project Release Process](../release-process.md) +- [Project Contributing Guide](../../../CONTRIBUTING.md) diff --git a/.claude/skills/activitypub-release/SKILL.md b/.claude/skills/activitypub-release/SKILL.md new file mode 100644 index 000000000..a75b00e7a --- /dev/null +++ b/.claude/skills/activitypub-release/SKILL.md @@ -0,0 +1,151 @@ +--- +name: activitypub-release +description: Version management and release processes using Jetpack Changelogger. Use when creating releases, managing changelogs, bumping versions, or preparing patch releases. +--- + +# ActivityPub Release Process + +This skill provides guidance on managing releases and changelogs for the WordPress ActivityPub plugin. + +## Quick Reference + +### Changelog Commands +```bash +# Create release PR +npm run release +``` + +### Version Locations +- `activitypub.php` - Plugin header +- `readme.txt` - WordPress.org readme +- `package.json` - npm version +- `CHANGELOG.md` - Changelog file + +## Major/Minor Releases + +### Process Overview +1. Generate version bump PR with `npm run release` +2. Review and merge PR +3. Create GitHub release from trunk + +### Using Release Script + +```bash +# Run from plugin root +npm run release + +# Script will: +# - Determine version from changelogs +# - Update version numbers +# - Update CHANGELOG.md +# - Create PR +``` + +## Patch Releases + +### Process Overview +1. Create branch from the release tag to patch +2. Cherry-pick fixes +3. Update changelog manually +4. Create release from branch + +### Cherry-picking Fixes + +```bash +# Create branch from the tag of the release to patch +git fetch --tags +git checkout -b release/5.3.1 5.3.0 # Create 5.3.1 branch from 5.3.0 tag + +# Cherry-pick merge commits from trunk +git cherry-pick -m 1 + +# Update changelog +composer changelog:write + +# Manually update versions in: +# - activitypub.php +# - readme.txt + +# Push the branch +git push -u origin release/5.3.1 +``` + +## Changelog Management + +### How Changelog Works + +Changelogs are managed automatically through the PR process: + +1. **PR Template** (`.github/PULL_REQUEST_TEMPLATE.md`): + - Check "Automatically create a changelog entry" checkbox + - Select significance level (Patch/Minor/Major) + - Select change type (Added/Fixed/Changed/Deprecated/Removed/Security) + - Write clear message ending with punctuation + +2. **GitHub Action** (`.github/workflows/changelog.yml`): + - Automatically creates changelog file from PR description + - Validates message has proper punctuation + - Creates file in `.github/changelog/` directory + +3. **Release Process**: + - `npm run release` aggregates all changelog entries + - Updates `CHANGELOG.md` and `readme.txt` automatically + +**Requirements:** +- **Always end messages with punctuation!** +- Never mention AI tools or coding assistants +- Focus on user impact +- Be clear and concise + +### Changelog Format + +```markdown +## [1.0.0] - 2024-01-15 +### Added +- New feature description. + +### Fixed +- Bug fix description. + +### Changed +- Updated feature description. +``` + +## Version Numbering + +### Semantic Versioning +- **Major (X.0.0)** - Breaking changes +- **Minor (0.X.0)** - New features +- **Patch (0.0.X)** - Bug fixes only + +### Version Update Locations + +1. **activitypub.php:** +```php +/** + * Plugin Name: ActivityPub + * Version: 1.0.0 + */ +``` + +2. **readme.txt:** +``` +Stable tag: 1.0.0 +``` + +3. **package.json:** +```json +{ + "version": "1.0.0" +} +``` + +## Creating GitHub Release + +1. Go to repository releases page +2. Click "Draft a new release" +3. Create new tag with version number +4. Select target branch (trunk or release branch) +5. Generate release notes +6. Publish release + diff --git a/.claude/skills/activitypub-testing/SKILL.md b/.claude/skills/activitypub-testing/SKILL.md new file mode 100644 index 000000000..c07f106df --- /dev/null +++ b/.claude/skills/activitypub-testing/SKILL.md @@ -0,0 +1,123 @@ +--- +name: activitypub-testing +description: Testing patterns for PHPUnit and Playwright E2E tests. Use when writing tests, debugging test failures, setting up test coverage, or implementing test patterns for ActivityPub features. +--- + +# ActivityPub Testing + +This skill provides guidance on writing and running tests for the WordPress ActivityPub plugin. + +## Quick Reference + +For complete testing commands and environment setup, see [Development Cycle - Testing](../activitypub-dev-cycle/testing.md). + +### Key Commands +- **PHP:** `npm run env-test` +- **E2E:** `npm run test:e2e` +- **JavaScript:** `npm run test:unit` + +## PHPUnit Testing + +### Test Structure + +```php + { + await page.goto('/wp-admin/options-general.php?page=activitypub'); + await expect(page.locator('h1')).toContainText('ActivityPub'); +}); +``` + +### Testing Federation + +```javascript +test('WebFinger discovery works', async ({ page }) => { + const response = await page.request.get('/.well-known/webfinger', { + params: { + resource: 'acct:admin@localhost:8888' + } + }); + + expect(response.ok()).toBeTruthy(); + const json = await response.json(); + expect(json.subject).toBe('acct:admin@localhost:8888'); +}); +``` + +## Test Data Factories + +For creating test data (users, posts, comments), see [Test Utilities](../activitypub-dev-cycle/testing.md#test-utilities). + +## Coverage Reports + +See [Development Cycle - Testing](../activitypub-dev-cycle/testing.md#coverage-reports) for detailed coverage generation instructions. + +## Debugging Tests + +### Debug Output + +```php +// In tests +var_dump( $data ); +error_log( print_r( $result, true ) ); + +// Run with verbose +npm run env-test -- --verbose --debug +``` + +### Isolating Tests + +```bash +# Run single test method +npm run env-test -- --filter=test_specific_method + +# Stop on first failure +npm run env-test -- --stop-on-failure +``` \ No newline at end of file diff --git a/.gitignore b/.gitignore index a984b643e..f816aa17f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ /vendor/ _site .idea/ -.claude +# Claude Code - track skills but ignore local overrides +.claude/**/*.local* .cursor .DS_Store .php_cs.cache @@ -16,5 +17,4 @@ _site .windsurf .windsurfrules .wp-env.override.json -CLAUDE* composer.lock diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..d619502c5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,27 @@ +## WordPress ActivityPub Development + +This project uses AI coding assistant skills to provide contextual guidance for development workflows. + +### Available Skills + +The following skills are available in `.claude/skills/`: + +- **activitypub-dev-cycle** — Development workflows, testing, linting, and environment setup +- **activitypub-php-conventions** — PHP coding standards and WordPress patterns +- **activitypub-pr-workflow** — Pull request creation and review processes +- **activitypub-federation** — ActivityPub protocol implementation and federation +- **activitypub-testing** — PHPUnit and E2E testing patterns +- **activitypub-release** — Version management and release processes +- **activitypub-integrations** — Third-party plugin integration patterns + +**CRITICAL:** After reading a skill, check if a local skill override file exists at `~/.claude/skills/{skill-name}-local/SKILL.md` and apply it too. +For example, after reading `.claude/skills/activitypub-dev-cycle/SKILL.md`, check for `~/.claude/skills/activitypub-dev-cycle-local/SKILL.md`. + +**Local override skills take precedence over project-level skills in case of conflict.** + +## Notes for Claude + +- This doc provides context; skills provide procedures +- When in doubt about HOW to do something, check the skills +- When in doubt about WHAT something is or WHERE it fits, check this doc +- Skills are invoked automatically when relevant to the task