Imported from KONFeature/wordforge (
AGENTS.md). Install upstream withnpx skills add KONFeature/wordforge. Copyright stays with the author.
AGENTS.md - WordForge Development Guide
Project Structure
wordforge/ # Bun monorepo
├── packages/
│ ├── php/ # WordPress plugin (PHP 8.0+, WPCS)
│ ├── ui/ # React admin UI (WordPress bundled React)
│ ├── mcp/ # MCP server for Claude Desktop (Node 18+)
│ └── desktop/ # Tauri desktop app (React + Rust)
│ ├── src/ # React frontend (TanStack Router/Query)
│ └── src-tauri/ # Rust backend (OpenCode sidecar manager)
├── biome.json # Shared TS/JS linting/formatting
└── package.json # Workspace root
Commands
Root
bun install # Install all dependencies
bun run build # Build all packages
bun run lint # Lint TypeScript/JavaScript
bun run lint:fix # Auto-fix lint issues
bun run typecheck # Type-check all packages
bun run test # Run all tests
bun run start # Start wp-env (localhost:8888)
Package-specific
# @wordforge/mcp
cd packages/mcp
bun run build # Build with tsdown
bun run test # Run all tests
bun run test -- --run integration.test.ts # Single file
bun run test -- --run "should list abilities" # Single test by name
bun run typecheck # Type-check
# @wordforge/ui
cd packages/ui
bun run build # Build React → packages/php/assets/js/
bun run start # Watch mode with HMR
# @wordforge/php
cd packages/php
composer install
composer run lint:php # WordPress coding standards
composer run lint:php:fix # Auto-fix
# @wordforge/desktop
cd packages/desktop
bun run tauri:dev # Start Tauri dev mode with HMR
bun run tauri:build # Build production app
bun run typecheck # Type-check TypeScript
Code Style
TypeScript (Biome)
- 2-space indentation, single quotes, semicolons required
import typefor type-only importscamelCasefunctions/variables,PascalCasetypes/classes,SCREAMING_SNAKE_CASEconstants- Strict mode enabled - avoid
any, useunknownand narrow types - Never use
@ts-ignoreor@ts-expect-error
// Import order: external → internal → types
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { AbilitiesApiClient } from './abilities-client.js';
import type { Config } from './types.js';
React (@wordforge/ui)
CRITICAL: Use WordPress bundled React, never import from 'react'
import { useState, useEffect } from '@wordpress/element';
import { Button, Card, Spinner } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
Components: Arrow functions, typed props, CSS Modules for styling.
import styles from './MyComponent.module.css';
interface MyComponentProps {
title: string;
onAction: () => void;
}
export const MyComponent = ({ title, onAction }: MyComponentProps) => {
const [state, setState] = useState<string>('');
return <div className={styles.container}>...</div>;
};
React (@wordforge/desktop)
Standard React - Desktop app uses regular React imports (NOT WordPress bundled).
import { useState, useEffect } from 'react';
import { useQuery, useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
Uses TanStack Router (file-based routing) and TanStack Query for state management. Hash history for Tauri compatibility (no web server).
PHP (WordPress Coding Standards)
- Tabs for indentation
- Spaces inside parentheses:
function_name( $arg ) - Yoda conditions:
if ( 'value' === $variable ) - PHP 8.0+ type declarations required
- Prefix hooks/filters with
wordforge_
<?php
declare(strict_types=1);
namespace WordForge\Abilities\Content;
use WordForge\Abilities\AbstractAbility;
class ListContent extends AbstractAbility {
public function execute( array $args ): array {
// Validate early
if ( ! post_type_exists( $args['post_type'] ) ) {
return $this->error( 'Invalid post type', 'invalid_post_type' );
}
return $this->success( $data, 'Optional message' );
}
}
Error Handling
TypeScript
try {
const result = await client.executeAbility(name, method, args);
return { content: [...], structuredContent: result };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
logger.error(`Tool ${name} failed`, err);
return { content: [{ type: 'text', text: JSON.stringify({ error: message }) }], isError: true };
}
PHP
return $this->success( $data ); // Success response
return $this->error( 'msg', 'code' ); // Error response
Testing (Vitest)
Tests in packages/mcp/test/. Globals enabled (describe, it, expect).
import { describe, it, expect, beforeAll } from 'vitest';
describe('MyFeature', () => {
let client: AbilitiesApiClient;
beforeAll(() => {
client = new AbilitiesApiClient(TEST_URL, TEST_USER, TEST_PASS);
});
it('should do something', async () => {
const result = await client.executeAbility('wordforge/list-content', 'GET', {});
expect(result.success).toBe(true);
});
});
Integration test env vars (.env in packages/mcp):
TEST_WORDPRESS_URL=https://example.com/wp-json/wp-abilities/v1
TEST_WORDPRESS_USER=admin
TEST_WORDPRESS_PASS=xxxx xxxx xxxx
Architecture
MCP Package
src/index.ts- MCP server bootstrap, registers tools/prompts/resourcesabilities-client.ts- HTTP client for WordPress Abilities APIability-loader.ts- Transforms WordPress abilities to MCP format
UI Package
- Entry points:
src/chat/index.tsx,src/settings/index.tsx - Builds to
packages/php/assets/js/via wp-scripts - Window config:
window.wordforgeChat,window.wordforgeSettings
PHP Package
- Entry:
wordforge.php- plugin bootstrap AbilityRegistry.php- registers abilities with WordPressAbilities/AbstractAbility.php- base class withsuccess(),error(),format_post()- Each ability implements:
get_title(),get_description(),get_input_schema(),execute()
Common Pitfalls
- React imports - Always
@wordpress/element, neverreactdirectly - PHP "undefined function" errors - WordPress functions work at runtime, not static analysis
- Type suppression - Never use
as any,@ts-ignore, or@ts-expect-error - Asset files -
.asset.phpfiles are auto-generated by wp-scripts, don't edit manually - Deploying - Only deploy
packages/php/contents to WordPress, not the monorepo