Files
wursor/IMPLEMENTATION.md
T
SinachPat dfb3c9372b docs: PRD v2.0, IMPLEMENTATION v2.0 — non-technical-first pivot
Complete rewrite of both documents from engineer-first to non-technical-first:

Architecture changed:
- Desktop app (Tauri + Monaco) → Web app (React + Node.js)
- Local Docker runtime → Cloud-hosted sandboxes (Wursor-hosted, VPS)
- Code editor + terminal + diff panels → Chat interface + live preview + approve/reject
- Tools for engineers (fs, git, wp-cli) → Playbooks for everyone (content, design, plugins)
- BYO API key → Wursor-hosted model (Grok)

Product changed:
- Primary user: WordPress engineer → WordPress site owner (non-technical)
- Interface: code editor with panels → single chat input + live preview
- Safety model: permission tiers + file diffs → cloud sandbox + approve/reject
- Deploy: Git-based → plugin-based with one-click rollback
- Competitive frame: dev tools → WP management layer

Key new sections:
- Cloud sandbox orchestration with warm pool, mirroring, lazy media sync
- WordPress plugin connector (REST API, deploy receiver, rollback)
- Deploy mechanism (diff engine, pusher, verifier, rollback)
- New playbook system (content, design, plugin, site build)
- Error states for the web app + plugin paradigm
- Non-technical personas, principles, and UX
2026-08-13 18:57:12 +01:00

45 KiB
Raw Blame History

Implementation Guide — Wursor v2

Version: 2.0
Source: PRD.md v2.0
Method: Test-driven development (TDD) — every module is written against its tests before its implementation.


Table of Contents

  1. Architecture Overview
  2. Project Structure
  3. Build Phases
  4. Phase 1 — Foundation (Weeks 18)
  5. Phase 2 — Intelligence (Weeks 916)
  6. TDD Rules
  7. CI/CD Pipeline
  8. Glossary

1. Architecture Overview

┌──────────────────────────────────────────────────────────┐
│  User's Browser (Wursor Web App)                          │
│  ┌────────────────────┐  ┌─────────────────────────────┐ │
│  │  Chat Panel         │  │  Preview iframe              │ │
│  │  (React)            │  │  (sandbox URL, interactive)  │ │
│  │  ┌────────────────┐ │  │  ┌─────────────────────────┐ │ │
│  │  │ Message list    │ │  │  │ Live preview of the    │ │ │
│  │  │ Input field     │ │  │  │ sandbox site. User     │ │ │
│  │  │ Approve/Reject  │ │  │  │ can click around,     │ │ │
│  │  │ buttons         │ │  │  │ navigate pages.       │ │ │
│  │  └────────────────┘ │  │  └─────────────────────────┘ │ │
│  └────────────────────┘  └─────────────────────────────┘ │
│  ┌──────────────────────────────────────────────────────┐ │
│  │  Deploy History (timeline, one-click undo)            │ │
│  └──────────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────────┘
                       │ HTTPS / SSE
┌──────────────────────▼───────────────────────────────────┐
│  API Server (Node.js + TypeScript)                          │
│  ┌─────────────────────┐  ┌──────────────────────────────┐ │
│  │  Session Manager     │  │  Agent Orchestrator          │ │
│  │  ├─ Auth             │  │  ├─ Route request → playbook │ │
│  │  ├─ Site connection  │  │  ├─ Build system prompt     │ │
│  │  └─ Session state    │  │  ├─ Dispatch tool calls     │ │
│  │                     │  │  └─ Stream results (SSE)    │ │
│  ├─────────────────────┤  ├──────────────────────────────┤ │
│  │  Sandbox Manager     │  │  Playbook Runner             │ │
│  │  ├─ Spin up/down    │  │  ├─ Content playbook         │ │
│  │  ├─ Warm pool       │  │  ├─ Design playbook          │ │
│  │  ├─ Site mirroring  │  │  ├─ Plugin playbook          │ │
│  │  └─ GC              │  │  └─ Site build playbook      │ │
│  ├─────────────────────┤  ├──────────────────────────────┤ │
│  │  Deploy Manager      │  │  Plugin API Client           │ │
│  │  ├─ Compute diff    │  │  ├─ Site info (read)         │ │
│  │  ├─ Push changes    │  │  ├─ File write               │ │
│  │  ├─ Verify deploy   │  │  ├─ DB write                 │ │
│  │  └─ Rollback        │  │  └─ WP-CLI execute           │ │
│  └─────────────────────┘  └──────────────────────────────┘ │
├──────────────────────────────────────────────────────────┤
│  Infrastructure (Docker VPS)                               │
│  ┌─────────────────────┐  ┌──────────────────────────────┐ │
│  │  Warm Pool           │  │  Active Sandboxes            │ │
│  │  (pre-booted WP imgs)│  │  (ephemeral containers)      │ │
│  │  ┌─────────────────┐ │  │  ┌──────────────────────────┐ │ │
│  │  │ nginx + PHP 8.x │ │  │  │ WordPress + MySQL       │ │ │
│  │  │ + MySQL 8.x     │ │  │  │ + user's theme/plugins  │ │ │
│  │  │ + WP-CLI        │ │  │  │ + user's content/media  │ │ │
│  │  └─────────────────┘ │  │  └──────────────────────────┘ │ │
│  └─────────────────────┘  └──────────────────────────────┘ │
├──────────────────────────────────────────────────────────┤
│  Internet                                                   │
│  ┌──────────────────────────────────────────────────────┐ │
│  │  User's WordPress Site (their hosting)                  │ │
│  │  ┌──────────────────────────────────────────────────┐ │ │
│  │  │  Wursor Plugin (REST API, WP-CLI, deploy rx)     │ │ │
│  │  └──────────────────────────────────────────────────┘ │ │
│  └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘

Key stack decisions:

  • Backend: Node.js + TypeScript (fastest path to a working API server; the orchestration is I/O-bound, not CPU-bound)
  • Frontend: React + TypeScript (chat interface, preview iframe, deploy history)
  • Sandbox: Docker containers on VPS with pre-baked WordPress image
  • Plugin: PHP WordPress plugin (standard WordPress plugin architecture)
  • Database: PostgreSQL for Wursor's own data (users, sites, sessions, deploy history); MySQL inside sandboxes for WordPress
  • Queue: Redis for SSE streaming, task queues, and cache

2. Project Structure

wursor/
├── api/                           # API server (Node.js + TypeScript)
│   ├── src/
│   │   ├── index.ts               # Express/Fastify server entry
│   │   ├── routes/
│   │   │   ├── auth.ts            # Sign-up, sign-in, session
│   │   │   ├── sites.ts           # Site connection, plugin pairing
│   │   │   ├── sessions.ts        # Chat session create/resume
│   │   │   ├── chat.ts            # Chat message, SSE stream
│   │   │   ├── preview.ts         # Preview URL, sandbox status
│   │   │   ├── deploy.ts          # Approve, deploy, rollback
│   │   │   └── webhooks.ts        # Plugin webhook receiver
│   │   ├── services/
│   │   │   ├── session-manager.ts
│   │   │   ├── agent-orchestrator.ts
│   │   │   ├── playbook-runner.ts
│   │   │   ├── sandbox-manager.ts
│   │   │   ├── deploy-manager.ts
│   │   │   ├── plugin-client.ts
│   │   │   └── warm-pool.ts
│   │   ├── agents/
│   │   │   ├── grok-client.ts     # Grok API client
│   │   │   ├── prompt-builder.ts  # System prompt per session
│   │   │   ├── tool-schemas.ts    # Tool schemas → Grok format
│   │   │   └── fallback.ts       # Error handling, retry
│   │   ├── playbooks/
│   │   │   ├── registry.ts        # Playbook registry
│   │   │   ├── content.ts         # Content edit playbook
│   │   │   ├── design.ts          # Design change playbook
│   │   │   ├── plugin.ts          # Plugin install playbook
│   │   │   └── site-build.ts      # Site build playbook (P0 limited)
│   │   ├── sandbox/
│   │   │   ├── docker-client.ts   # Docker API client
│   │   │   ├── image-manager.ts   # Pre-baked image management
│   │   │   ├── mirror.ts          # Site mirroring (content, themes, plugins)
│   │   │   ├── media-sync.ts      # Lazy media sync
│   │   │   └── gc.ts              # Garbage collection (idle, hard timeout)
│   │   ├── deploy/
│   │   │   ├── diff-engine.ts     # Compare sandbox → live site
│   │   │   ├── pusher.ts          # Push changes via plugin API
│   │   │   ├── verifier.ts        # Verify live site after deploy
│   │   │   └── rollback.ts        # Snapshot-based rollback
│   │   ├── models/
│   │   │   ├── user.ts
│   │   │   ├── site.ts
│   │   │   ├── session.ts
│   │   │   ├── deploy-log.ts
│   │   │   └── sandbox.ts
│   │   └── lib/
│   │       ├── crypto.ts          # Token generation, encryption
│   │       ├── sse.ts             # Server-sent events
│   │       └── queue.ts           # Redis queue
│   ├── __tests__/
│   │   ├── services/
│   │   │   ├── agent-orchestrator.test.ts
│   │   │   ├── sandbox-manager.test.ts
│   │   │   ├── deploy-manager.test.ts
│   │   │   └── playbook-runner.test.ts
│   │   ├── agents/
│   │   │   ├── grok-client.test.ts
│   │   │   ├── prompt-builder.test.ts
│   │   │   └── tool-schemas.test.ts
│   │   ├── playbooks/
│   │   │   ├── content.test.ts
│   │   │   ├── design.test.ts
│   │   │   └── plugin.test.ts
│   │   ├── sandbox/
│   │   │   ├── mirror.test.ts
│   │   │   ├── media-sync.test.ts
│   │   │   └── gc.test.ts
│   │   └── deploy/
│   │       ├── diff-engine.test.ts
│   │       ├── pusher.test.ts
│   │       └── rollback.test.ts
│   ├── package.json
│   └── tsconfig.json
│
├── web/                           # Web frontend (React + TypeScript)
│   ├── src/
│   │   ├── main.tsx
│   │   ├── App.tsx
│   │   ├── pages/
│   │   │   ├── Chat.tsx           # Main chat + preview view
│   │   │   ├── SignIn.tsx
│   │   │   ├── SignUp.tsx
│   │   │   ├── ConnectSite.tsx    # Plugin pairing flow
│   │   │   └── History.tsx        # Deploy history timeline
│   │   ├── components/
│   │   │   ├── ChatPanel.tsx
│   │   │   ├── Preview.tsx
│   │   │   ├── ApproveBar.tsx
│   │   │   ├── DeployTimeline.tsx
│   │   │   ├── SiteConnector.tsx
│   │   │   └── WelcomeScreen.tsx
│   │   ├── hooks/
│   │   │   ├── useChat.ts
│   │   │   ├── usePreview.ts
│   │   │   ├── useSession.ts
│   │   │   └── useDeploy.ts
│   │   └── styles/
│   │       └── global.css
│   ├── __tests__/
│   │   ├── ChatPanel.test.tsx
│   │   ├── Preview.test.tsx
│   │   ├── ApproveBar.test.tsx
│   │   └── SiteConnector.test.tsx
│   ├── index.html
│   ├── vite.config.ts
│   ├── tsconfig.json
│   └── package.json
│
├── plugin/                        # WordPress plugin (PHP)
│   ├── wursor.php                 # Plugin header, bootstrap
│   ├── src/
│   │   ├── class-api.php          # REST API handlers
│   │   ├── class-auth.php         # Token auth, pairing
│   │   ├── class-deploy.php       # Deploy receiver (files, DB, WP-CLI)
│   │   ├── class-rollback.php     # Snapshot-based rollback
│   │   ├── class-site-info.php    # Site info provider
│   │   └── class-admin.php        # Admin settings page
│   ├── __tests__/
│   │   ├── test-api.php
│   │   ├── test-auth.php
│   │   ├── test-deploy.php
│   │   └── test-rollback.php
│   └── readme.txt
│
├── infrastructure/                # Infrastructure scripts
│   ├── docker/
│   │   ├── Dockerfile.wordpress   # Pre-baked WordPress image
│   │   └── docker-compose.yml     # For local dev
│   ├── scripts/
│   │   ├── warm-pool.ts           # Warm pool manager
│   │   ├── gc.ts                  # Garbage collection cron
│   │   └── deploy.ts              # Deploy hook
│   └── terraform/
│       └── main.tf                # VPS provisioning (v1: manual, v2: Terraform)
│
├── e2e/                           # End-to-end tests
│   ├── chat-flow.test.ts
│   ├── plugin-connection.test.ts
│   ├── deploy-flow.test.ts
│   └── rollback.test.ts
│
├── package.json
├── pnpm-workspace.yaml
├── tsconfig.base.json
└── .github/workflows/
    ├── ci.yml
    └── e2e.yml

3. Build Phases

Phase Weeks Output Exit criteria
Phase 1 18 Web app, chat, preview, sandbox, plugin, deploy, content+design playbooks New user connects site, makes a content change, previews, approves ≤5 min
Phase 2 916 Plugin playbooks, site build, agent disambiguation, closed alpha Non-technical user installs a plugin via chat
Phase 3 1728 Multi-step workflows, design picker, SEO, performance, paid beta
Phase 4 29+ Multi-site, team, e-commerce, scheduled changes, marketplace

4. Phase 1 — Foundation (Weeks 18)

8 sprints, one per week. Every sprint produces a passing integration test.


Sprint 1: Web app scaffold + sandbox orchestration

Goal: A user can sign up, see a chat interface, and Wursor spins up a sandbox WordPress instance.

TDD sequence

Step 1 — Write the integration test

// e2e/chat-flow.test.ts
import { test, expect } from '@playwright/test';

test('user signs up, starts a session, sandbox spins up', async ({ page }) => {
  await page.goto('http://localhost:3000');
  await page.locator('.wursor-signup-button').click();
  await page.locator('input[name=email]').fill('test@example.com');
  await page.locator('input[name=password]').fill('password123');
  await page.locator('.wursor-submit').click();

  // Should see the chat interface
  await expect(page.locator('.wursor-chat-input')).toBeVisible();
  await expect(page.locator('.wursor-welcome')).toContainText('Describe what you');

  // Type a request
  await page.locator('.wursor-chat-input').fill('Change the homepage heading to "Hello World"');
  await page.locator('.wursor-chat-send').click();

  // Agent should acknowledge and start working
  await expect(page.locator('.wursor-message-agent')).toContainText('working on it', { timeout: 30000 });
});

Step 2 — Write the unit tests

// api/__tests__/sandbox/mirror.test.ts
describe('Mirror', () => {
  it('connects to the live site and fetches site info', async () => {
    const mirror = new Mirror({ siteUrl: 'https://example.com', token: 'test-token' });
    const info = await mirror.fetchSiteInfo();
    expect(info.theme).toBeDefined();
    expect(info.plugins.length).toBeGreaterThan(0);
  });

  it('copies theme and active plugins to the sandbox', async () => {
    const mirror = new Mirror({ sandboxId: 'sb-123' });
    await mirror.copyTheme('twentytwentyfour');
    await mirror.copyPlugins(['woocommerce', 'contact-form-7']);
    // Verify files exist in the sandbox
    expect(await mirror.sandboxFileExists('/wp-content/themes/twentytwentyfour')).toBe(true);
  });

  it('lazy-syncs media only when accessed', async () => {
    // Media should not be synced during mirror, only on first access
  });
});

// api/__tests__/sandbox/docker-client.test.ts
describe('DockerClient', () => {
  it('spins up a sandbox container from the pre-baked image', async () => {
    const client = new DockerClient();
    const container = await client.createSandbox('wursor-base:latest');
    expect(container.id).toBeDefined();
    expect(container.status).toBe('running');
  });

  it('destroys a sandbox container', async () => {
    const client = new DockerClient();
    await client.destroySandbox('sb-123');
    const status = await client.getStatus('sb-123');
    expect(status).toBe('destroyed');
  });
});

// api/__tests__/sandbox/gc.test.ts
describe('GarbageCollection', () => {
  it('destroys sandboxes after 15 minutes of idle', async () => { /* ... */ });
  it('destroys sandboxes after 24 hours regardless', async () => { /* ... */ });
  it('does not destroy active sandboxes', async () => { /* ... */ });
});

Step 3 — Implement

  • web/ — React app with sign-up, sign-in, chat interface (placeholder)
  • api/src/index.ts — Express server with auth routes
  • api/src/routes/auth.ts — Sign-up, sign-in, session management
  • api/src/routes/sessions.ts — Create session, stream SSE
  • api/src/services/sandbox-manager.ts — Orchestrate sandbox lifecycle
  • api/src/sandbox/docker-client.ts — Docker API client (dockerode)
  • api/src/sandbox/image-manager.ts — Pre-baked image → Dockerfile
  • api/src/sandbox/mirror.ts — Site mirroring (stub plugin client)
  • api/src/sandbox/media-sync.ts — Lazy media sync (stub)
  • api/src/sandbox/gc.ts — Garbage collection (idle timeout, hard timeout)
  • infrastructure/docker/Dockerfile.wordpress — Pre-baked image
  • infrastructure/scripts/warm-pool.ts — Warm pool manager

Deliverables

  • Web app with sign-up and chat interface
  • Sandbox spin-up from pre-baked image
  • e2e/chat-flow.test.ts passing (sign-up → sees chat)
  • All unit tests passing

Sprint 2: WordPress plugin connector

Goal: User installs the Wursor plugin on their site, pairs it with Wursor, and Wursor can read site info.

TDD sequence

Step 1 — Write the tests

// plugin/__tests__/test-auth.php
class WursorAuthTest extends WP_UnitTestCase {
    public function test_generates_pairing_code() {
        $auth = new Wursor_Auth();
        $code = $auth->generate_pairing_code();
        $this->assertEquals(6, strlen($code));
        $this->assertMatchesRegularExpression('/^[A-Z0-9]{6}$/', $code);
    }

    public function test_verifies_valid_token() {
        $auth = new Wursor_Auth();
        $token = $auth->generate_token();
        $this->assertTrue($auth->verify_token($token));
    }

    public function test_rejects_invalid_token() {
        $auth = new Wursor_Auth();
        $this->assertFalse($auth->verify_token('invalid'));
    }
}

// plugin/__tests__/test-api.php
class WursorApiTest extends WP_UnitTestCase {
    public function test_returns_site_info() {
        $api = new Wursor_API();
        $response = $api->get_site_info();
        $this->assertArrayHasKey('theme', $response);
        $this->assertArrayHasKey('plugins', $response);
        $this->assertArrayHasKey('wordpress_version', $response);
        $this->assertArrayHasKey('php_version', $response);
    }

    public function test_requires_auth() {
        $api = new Wursor_API();
        $response = $api->handle_request('GET', '/site-info', []);
        $this->assertEquals(401, $response['status']);
    }
}

// plugin/__tests__/test-deploy.php (placeholder)
class WursorDeployTest extends WP_UnitTestCase {
    public function test_receives_file_change() {
        // Stub for Sprint 6
        $this->markTestSkipped('Deploy test in Sprint 6');
    }
}
// api/__tests__/services/plugin-client.test.ts
describe('PluginClient', () => {
  it('connects to the plugin and fetches site info', async () => {
    const client = new PluginClient({ siteUrl: 'https://example.com', token: 'valid-token' });
    const info = await client.getSiteInfo();
    expect(info.theme).toBeDefined();
    expect(info.plugins).toBeInstanceOf(Array);
  });

  it('throws on invalid token', async () => {
    const client = new PluginClient({ siteUrl: 'https://example.com', token: 'invalid' });
    await expect(client.getSiteInfo()).rejects.toThrow('Authentication failed');
  });

  it('handles unreachable site', async () => {
    const client = new PluginClient({ siteUrl: 'https://nonexistent.example.com', token: 'token' });
    await expect(client.getSiteInfo()).rejects.toThrow('Site unreachable');
  });
});
// web/__tests__/SiteConnector.test.tsx
describe('SiteConnector', () => {
  it('shows the pairing code', () => { /* ... */ });
  it('polls for connection status', () => { /* ... */ });
  it('shows success state when connected', () => { /* ... */ });
  it('shows error state when connection fails', () => { /* ... */ });
});

Step 2 — Implement

  • plugin/wursor.php — Plugin header, activation hook, bootstrap
  • plugin/src/class-auth.php — Token generation, verification, pairing code
  • plugin/src/class-api.php — REST API endpoints (site-info, files, DB, WP-CLI)
  • plugin/src/class-site-info.php — Site info provider (theme, plugins, WP version, PHP version)
  • plugin/src/class-admin.php — Admin settings page (pairing code display)
  • api/src/services/plugin-client.ts — HTTP client for the plugin API
  • api/src/routes/sites.ts — Site connection flow, pairing
  • web/src/components/SiteConnector.tsx — Pairing UI (show code, wait for connection)
  • web/src/pages/ConnectSite.tsx — Connection page

Deliverables

  • WordPress plugin with pairing and site-info API
  • Plugin client in the API server
  • Connection flow: user installs plugin → gets code → enters in Wursor → connected
  • plugin/__tests__/test-auth.php and test-api.php passing
  • api/__tests__/services/plugin-client.test.ts passing
  • web/__tests__/SiteConnector.test.tsx passing

Sprint 3: Agent orchestrator + playbook runner

Goal: User types a request, the agent orchestrator routes it to a playbook, and the playbook executes in the sandbox.

TDD sequence

Step 1 — Write the tests

// api/__tests__/agents/grok-client.test.ts
describe('GrokClient', () => {
  it('sends a message and returns a response', async () => {
    const client = new GrokClient({ apiKey: 'test-key' });
    const response = await client.send('Change the homepage heading to "Hello"');
    expect(response.type).toBe('tool_call');
  });

  it('handles API errors with a clear message', async () => {
    const client = new GrokClient({ apiKey: 'invalid-key' });
    await expect(client.send('hello')).rejects.toThrow('API error');
  });

  it('handles rate limiting with retry', async () => { /* ... */ });
});

// api/__tests__/agents/prompt-builder.test.ts
describe('PromptBuilder', () => {
  it('builds a system prompt with site context', async () => {
    const builder = new PromptBuilder();
    const prompt = await builder.build({
      siteInfo: { theme: 'twentytwentyfour', plugins: ['woocommerce'] },
      userGoal: 'Change the homepage',
    });
    expect(prompt).toContain('twentytwentyfour');
    expect(prompt).toContain('woocommerce');
    expect(prompt).toContain('never touch the live site');
  });

  it('includes safety rules', async () => {
    const builder = new PromptBuilder();
    const prompt = await builder.build({ siteInfo: {}, userGoal: '' });
    expect(prompt).toContain('sandbox');
    expect(prompt).toContain('approval');
  });
});

// api/__tests__/agents/tool-schemas.test.ts
describe('ToolSchemas', () => {
  it('generates tool schemas for the Grok API', () => {
    const schemas = generateToolSchemas();
    expect(schemas.length).toBeGreaterThan(0);
    expect(schemas[0].name).toBe('wp_cli');
    expect(schemas[0].parameters).toBeDefined();
  });
});

// api/__tests__/services/agent-orchestrator.test.ts
describe('AgentOrchestrator', () => {
  it('routes a content request to the content playbook', async () => {
    const orchestrator = new AgentOrchestrator();
    const playbook = await orchestrator.route('Change the homepage heading to "Hello"');
    expect(playbook.name).toBe('content');
  });

  it('routes a plugin request to the plugin playbook', async () => {
    const orchestrator = new AgentOrchestrator();
    const playbook = await orchestrator.route('Install a contact form plugin');
    expect(playbook.name).toBe('plugin');
  });

  it('streams updates to the frontend via SSE', async () => {
    // Mock SSE connection, verify events are sent
  });
});

// api/__tests__/services/playbook-runner.test.ts
describe('PlaybookRunner', () => {
  it('executes a content playbook and returns the result', async () => {
    const runner = new PlaybookRunner({ sandboxId: 'sb-123' });
    const result = await runner.run('content', {
      type: 'edit_text',
      target: 'homepage',
      changes: { heading: 'Hello World' },
    });
    expect(result.success).toBe(true);
    expect(result.previewUrl).toBe('http://sb-123.wursor.dev');
  });
});
// web/__tests__/ChatPanel.test.tsx
describe('ChatPanel', () => {
  it('sends a message and displays the agent response', () => { /* ... */ });
  it('shows typing indicator while agent works', () => { /* ... */ });
  it('shows the preview when ready', () => { /* ... */ });
  it('shows error state when agent fails', () => { /* ... */ });
});

Step 2 — Implement

  • api/src/agents/grok-client.ts — Grok API client (messages API, tool use, streaming)
  • api/src/agents/prompt-builder.ts — Build system prompt per session
  • api/src/agents/tool-schemas.ts — Tool schemas → Grok format
  • api/src/agents/fallback.ts — Error handling, retry
  • api/src/services/agent-orchestrator.ts — Route requests, dispatch tools, stream results
  • api/src/services/playbook-runner.ts — Execute playbook steps in sandbox
  • api/src/playbooks/registry.ts — Playbook registry
  • api/src/routes/chat.ts — Chat message endpoint, SSE stream
  • web/src/components/ChatPanel.tsx — Chat UI with message list, input, typing indicator
  • web/src/hooks/useChat.ts — SSE connection, message state

Deliverables

  • Agent orchestrator routing requests to playbooks
  • Grok API client with tool-calling
  • Chat panel streaming agent responses
  • All unit tests passing

Sprint 4: Content playbooks

Goal: User can change text, images, and page content on their site via chat.

TDD sequence

Step 1 — Write the tests

// api/__tests__/playbooks/content.test.ts
describe('ContentPlaybook', () => {
  it('finds and replaces text on a specific page', async () => {
    const playbook = new ContentPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.editText({
      page: 'homepage',
      target: 'Welcome to our site',
      replacement: 'Welcome to My Business',
    });
    expect(result.success).toBe(true);
    // Verify the text was changed in the sandbox DB
    const pageContent = await playbook.getPageContent('homepage');
    expect(pageContent).toContain('Welcome to My Business');
    expect(pageContent).not.toContain('Welcome to our site');
  });

  it('updates a heading tag', async () => {
    const playbook = new ContentPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.editHeading({
      page: 'homepage',
      headingIndex: 0,
      newText: 'New Heading',
    });
    expect(result.success).toBe(true);
  });

  it('replaces an image', async () => {
    const playbook = new ContentPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.replaceImage({
      page: 'about',
      imageSelector: '.hero-image',
      imageUrl: 'https://example.com/new-image.jpg',
    });
    expect(result.success).toBe(true);
  });

  it('adds a new section to a page', async () => {
    const playbook = new ContentPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.addSection({
      page: 'homepage',
      sectionType: 'cta',
      content: 'Call us today!',
      position: 'after-hero',
    });
    expect(result.success).toBe(true);
  });

  it('reverts changes on failure', async () => {
    // If the change fails, the sandbox should be reset to the mirror state
  });
});

Step 2 — Implement

  • api/src/playbooks/content.ts — Content playbook with tool calls
    • editText: search DB for content → wp-cli wp post update or direct DB update
    • editHeading: find heading in page HTML → update via WP-CLI or file edit
    • replaceImage: upload new image → replace in content → verify
    • addSection: create new content block → add to page → verify
  • Each method uses the plugin client to execute WP-CLI commands or file operations in the sandbox
  • After each change, the playbook triggers a preview refresh

Deliverables

  • Content playbook: edit text, edit headings, replace images, add sections
  • All content playbook tests passing

Sprint 5: Design playbooks

Goal: User can change the theme, layout, colors, and fonts of their site via chat.

TDD sequence

Step 1 — Write the tests

// api/__tests__/playbooks/design.test.ts
describe('DesignPlaybook', () => {
  it('changes the active theme', async () => {
    const playbook = new DesignPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.changeTheme('twentytwentyfour');
    expect(result.success).toBe(true);
    // Verify the theme was activated
    const activeTheme = await playbook.getActiveTheme();
    expect(activeTheme).toBe('twentytwentyfour');
  });

  it('changes the site layout (single column → two columns)', async () => {
    const playbook = new DesignPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.changeLayout('two-column');
    expect(result.success).toBe(true);
  });

  it('updates theme colors', async () => {
    const playbook = new DesignPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.updateColors({
      primary: '#ff0000',
      secondary: '#00ff00',
    });
    expect(result.success).toBe(true);
  });

  it('updates typography', async () => {
    const playbook = new DesignPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.updateTypography({
      headingFont: 'Inter',
      bodyFont: 'Open Sans',
    });
    expect(result.success).toBe(true);
  });

  it('fixes a mobile layout issue', async () => {
    const playbook = new DesignPlaybook({ sandboxId: 'sb-123' });
    const result = await playbook.fixMobileLayout({ page: 'homepage' });
    expect(result.success).toBe(true);
  });
});

Step 2 — Implement

  • api/src/playbooks/design.ts — Design playbook
    • changeTheme: install theme via WP-CLI → activate → verify
    • changeLayout: modify theme templates or page builder content → verify
    • updateColors: update theme.json → regenerate CSS → verify
    • updateTypography: update theme.json → verify
    • fixMobileLayout: identify responsive CSS issues → fix → verify

Deliverables

  • Design playbook: change theme, layout, colors, fonts, mobile fix
  • All design playbook tests passing

Sprint 6: Deploy + rollback

Goal: User approves the change, Wursor deploys to the live site, and can roll back.

TDD sequence

Step 1 — Write the tests

// api/__tests__/deploy/diff-engine.test.ts
describe('DiffEngine', () => {
  it('computes file changes between sandbox and mirror', async () => {
    const engine = new DiffEngine({ sandboxId: 'sb-123', mirrorId: 'mirror-123' });
    const diff = await engine.computeFileDiff();
    expect(diff.changedFiles).toContain('/wp-content/themes/twentytwentyfour/style.css');
    expect(diff.newFiles).toHaveLength(0);
  });

  it('computes database changes', async () => {
    const engine = new DiffEngine({ sandboxId: 'sb-123', mirrorId: 'mirror-123' });
    const diff = await engine.computeDbDiff();
    expect(diff.changedTables).toContain('wp_options');
    expect(diff.changedRows).toBeGreaterThan(0);
  });

  it('computes plugin changes', async () => {
    const engine = new DiffEngine({ sandboxId: 'sb-123', mirrorId: 'mirror-123' });
    const diff = await engine.computePluginDiff();
    expect(diff.installed).toContain('contact-form-7');
  });
});

// api/__tests__/deploy/pusher.test.ts
describe('Pusher', () => {
  it('pushes file changes to the live site', async () => {
    const pusher = new Pusher({ siteUrl: 'https://example.com', token: 'valid-token' });
    const result = await pusher.pushFiles([
      { path: '/wp-content/themes/twentytwentyfour/style.css', content: '...' },
    ]);
    expect(result.success).toBe(true);
  });

  it('pushes database changes', async () => {
    const pusher = new Pusher({ siteUrl: 'https://example.com', token: 'valid-token' });
    const result = await pusher.pushDb([
      { table: 'wp_options', operation: 'UPDATE', where: { option_name: 'blogname' }, data: { option_value: 'My Site' } },
    ]);
    expect(result.success).toBe(true);
  });

  it('pushes plugin installs', async () => {
    const pusher = new Pusher({ siteUrl: 'https://example.com', token: 'valid-token' });
    const result = await pusher.pushPluginInstall('contact-form-7');
    expect(result.success).toBe(true);
  });

  it('handles partial failures', async () => {
    // If some files fail but others succeed, what happens? Roll back the batch.
  });
});

// api/__tests__/deploy/verifier.test.ts
describe('Verifier', () => {
  it('checks the home page returns 200', async () => {
    const verifier = new Verifier({ siteUrl: 'https://example.com' });
    const result = await verifier.checkHomePage();
    expect(result.status).toBe(200);
  });

  it('checks for PHP errors', async () => {
    const verifier = new Verifier({ siteUrl: 'https://example.com' });
    const result = await verifier.checkPhpErrors();
    expect(result.hasErrors).toBe(false);
  });

  it('checks the admin dashboard loads', async () => {
    const verifier = new Verifier({ siteUrl: 'https://example.com' });
    const result = await verifier.checkAdmin();
    expect(result.status).toBe(200);
  });
});

// api/__tests__/deploy/rollback.test.ts
describe('Rollback', () => {
  it('restores files from the snapshot', async () => {
    const rollback = new Rollback({ siteUrl: 'https://example.com', token: 'valid-token' });
    const result = await rollback.restoreFiles('deploy-123');
    expect(result.success).toBe(true);
  });

  it('restores the database from the snapshot', async () => {
    const rollback = new Rollback({ siteUrl: 'https://example.com', token: 'valid-token' });
    const result = await rollback.restoreDb('deploy-123');
    expect(result.success).toBe(true);
  });
});

// plugin/__tests__/test-deploy.php
class WursorDeployTest extends WP_UnitTestCase {
    public function test_receives_file_change() {
        $deploy = new Wursor_Deploy();
        $result = $deploy->apply_file_change('/wp-content/themes/twentytwentyfour/style.css', 'body { color: red; }');
        $this->assertTrue($result);
        $this->assertEquals('body { color: red; }', file_get_contents(WP_CONTENT_DIR . '/themes/twentytwentyfour/style.css'));
    }

    public function test_receives_db_change() {
        $deploy = new Wursor_Deploy();
        $result = $deploy->apply_db_change('UPDATE wp_options SET option_value = "New Title" WHERE option_name = "blogname"');
        $this->assertTrue($result);
        $this->assertEquals('New Title', get_option('blogname'));
    }

    public function test_creates_snapshot_for_rollback() {
        $deploy = new Wursor_Deploy();
        $snapshot = $deploy->create_snapshot();
        $this->assertArrayHasKey('files', $snapshot);
        $this->assertArrayHasKey('db', $snapshot);
    }

    public function test_restores_from_snapshot() {
        $deploy = new Wursor_Deploy();
        $snapshot = $deploy->create_snapshot();
        // Make a change
        update_option('blogname', 'Changed Title');
        // Restore
        $deploy->restore_snapshot($snapshot);
        $this->assertEquals('Original Title', get_option('blogname'));
    }
}
// web/__tests__/ApproveBar.test.tsx
describe('ApproveBar', () => {
  it('shows "Looks good → Apply" and "Not right → Reject" buttons', () => { /* ... */ });
  it('shows a confirmation dialog before apply', () => { /* ... */ });
  it('shows success state after deploy', () => { /* ... */ });
  it('shows the deploy history timeline', () => { /* ... */ });
  it('allows one-click undo on a deploy', () => { /* ... */ });
});

Step 2 — Implement

  • plugin/src/class-deploy.php — Deploy receiver (file write, DB write, WP-CLI exec, snapshot)
  • plugin/src/class-rollback.php — Snapshot-based rollback (files + DB)
  • api/src/deploy/diff-engine.ts — Compare sandbox → live site
  • api/src/deploy/pusher.ts — Push changes via plugin API
  • api/src/deploy/verifier.ts — Verify live site after deploy
  • api/src/deploy/rollback.ts — Snapshot-based rollback
  • api/src/routes/deploy.ts — Approve, deploy, rollback endpoints
  • web/src/components/ApproveBar.tsx — Approve/reject buttons, confirmation dialog
  • web/src/components/DeployTimeline.tsx — Deploy history with one-click undo
  • web/src/hooks/useDeploy.ts — Deploy state, polling

Deliverables

  • Deploy + rollback for files, DB, and plugins
  • Approve/reject UI with confirmation dialog
  • Deploy history timeline with one-click undo
  • All unit tests passing

Sprint 7: Integration + exit criteria

Goal: All Phase 1 pieces work together. Exit criteria test passes end-to-end.

Integration test

// e2e/phase1-exit-criteria.test.ts
import { test, expect } from '@playwright/test';

test('new user connects site, makes a content change, previews, approves in ≤5 min', async ({ page }) => {
  const startTime = Date.now();

  // 1. Sign up
  await page.goto('http://localhost:3000');
  await page.locator('.wursor-signup-button').click();
  await page.locator('input[name=email]').fill('test@example.com');
  await page.locator('input[name=password]').fill('password123');
  await page.locator('.wursor-submit').click();

  // 2. Connect site (simulated plugin)
  await expect(page.locator('.wursor-connect-site')).toBeVisible();
  await page.locator('.wursor-pairing-code-input').fill('ABC123');
  await page.locator('.wursor-connect-button').click();
  await expect(page.locator('.wursor-connected')).toBeVisible({ timeout: 10000 });

  // 3. Make a change
  await page.locator('.wursor-chat-input').fill('Change the homepage heading to "Welcome to My Business"');
  await page.locator('.wursor-chat-send').click();

  // 4. See preview
  await expect(page.locator('.wursor-preview-frame')).toBeVisible({ timeout: 60000 });

  // 5. Approve
  await page.locator('.wursor-approve-button').click();
  await page.locator('.wursor-confirm-apply').click();
  await expect(page.locator('.wursor-deploy-success')).toBeVisible({ timeout: 30000 });

  const elapsed = Date.now() - startTime;
  expect(elapsed).toBeLessThan(5 * 60 * 1000);
});

Deliverables

  • Exit criteria test passing
  • All unit tests passing (pnpm test)
  • All integration tests passing (pnpm test:integration)

Sprint 8: Polish + alpha readiness

Goal: Error states handled, app ready for internal alpha.

Tasks

  • Error states — Wire each state from §8.5 into the UI
  • Mobile responsive — Chat collapses to full-screen on mobile, preview opens in new tab
  • Email auth — Magic link or password reset flow
  • Plugin auto-update — Plugin checks for updates from Wursor
  • Telemetry — Minimal events (sign-up, connect, task start, task approve, task reject, deploy) with consent dialog
  • DocumentationREADME.md with install instructions and quickstart
  • Bug bash — Internal team runs through the full flow

Deliverables

  • Web app deployed to staging
  • Plugin packaged for WordPress plugin repo
  • Error states all wired
  • Minimal telemetry with consent
  • README.md updated for alpha users

5. Phase 2 — Intelligence (Weeks 916)

Sprint Focus Files
9 Plugin playbook (install, configure, fix conflicts) api/src/playbooks/plugin.ts
10 Site build playbook (from scratch, limited) api/src/playbooks/site-build.ts
11 Mobile-responsive preview web/src/components/Preview.tsx
12 Agent clarifying questions api/src/services/agent-orchestrator.ts
13 Visual design picker (theme gallery) api/src/playbooks/design.ts, web/src/components/DesignPicker.tsx
14 Multi-step workflows (queue changes) api/src/services/playbook-runner.ts
15 Closed alpha with 1020 users Telemetry review, baselines
16 Alpha feedback → Phase 2 exit review All §11 baselines collected

6. TDD Rules

  1. Write the test first. No implementation code is written without a failing test.
  2. One assertion per test. Each test verifies exactly one behavior.
  3. Tests are deterministic. No network calls in unit tests (mock Grok API, mock plugin, mock Docker).
  4. Integration tests use real sandboxes in CI. Pre-baked WordPress image in Docker on GitHub Actions.
  5. Red → Green → Refactor. Write the failing test (red), make it pass (green), then clean up (refactor).
  6. Coverage floor. TypeScript: vitest enforces ≥ 90%. PHP: phpunit with coverage ≥ 80%.
  7. No skipped tests in main. test.skip and test.only only in feature branches.

7. CI/CD Pipeline

# .github/workflows/ci.yml — runs on every PR
name: CI
on: [pull_request]
jobs:
  api:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - run: pnpm install
      - run: pnpm test:api
      - run: pnpm test:api:coverage

  web:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - run: pnpm install
      - run: pnpm test:web
      - run: pnpm test:web:coverage

  plugin:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: composer install
      - run: ./vendor/bin/phpunit plugin/__tests__/
      - run: ./vendor/bin/phpunit --coverage-text

  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - run: pnpm install && pnpm lint

# .github/workflows/e2e.yml — runs on release branch
name: E2E
on:
  push:
    branches: [release/*]
jobs:
  e2e:
    runs-on: ubuntu-latest
    services:
      docker:
        image: docker:20.10
        options: --privileged
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - run: pnpm install
      - run: pnpm build
      - run: pnpm test:e2e

8. Glossary

Term Definition
Sandbox An ephemeral, isolated copy of the user's WordPress site running in Wursor's cloud
Playbook A structured, multi-step agent workflow for a specific task type
Plugin connector The WordPress plugin that connects the user's site to Wursor
Mirror The process of copying a site's theme, plugins, content, and settings into a sandbox
Deploy The process of applying sandbox changes to the live site
Warm pool Pre-booted WordPress containers ready to accept a mirror
SSE Server-Sent Events — the protocol used to stream agent responses to the frontend

End of Implementation Guide v2.0 — Wursor