|
| 1 | +# Hey Woo |
| 2 | + |
| 3 | +The intelligence layer on top of WooCommerce core's MCP server: analytics skills, knowledge resources, prompts, and an AI-readiness scoring engine, registered as WordPress Abilities and exposed through the single MCP endpoint at `/wp-json/woocommerce/mcp`. |
| 4 | + |
| 5 | +WordPress plugin, PHP 7.4+, GPL-3.0-or-later. Public repo — never commit secrets, internal-only URLs, or anything not intended for public distribution. |
| 6 | + |
| 7 | +## At the start of each session |
| 8 | + |
| 9 | +**Read [CONTRIBUTING.md](./CONTRIBUTING.md) first.** It is the authoritative reference for: |
| 10 | + |
| 11 | +- Architecture (`Plugin (PHP) → Abilities API → WC core MCP server`) |
| 12 | +- The **privacy rule** — analytics responses are aggregated only; no PII flows to AI by default |
| 13 | +- The **merchant-scope rule** — tools never suggest "build a new skill / endpoint"; the merchant can't action that |
| 14 | +- WooCommerce tables and known edge cases (refund sub-orders, returning-customer flag, refunds formula) |
| 15 | +- Caching (don't use WC core's DataStore cache; use transients with stable cache keys) |
| 16 | +- The full how-to for adding a new analytics Skill (questions-first, ability registration, mandatory PHPUnit test, coverage guard) |
| 17 | +- Design patterns worth knowing — silence-isn't-signal, guardrail bad/good phrasing pairs, narrative-layer pre-compute, small-N honesty, two-frame responses, mode-switching, tool-vs-resource affordances, custom-header vs OAuth auth, regulatory thresholds |
| 18 | + |
| 19 | +**Re-stating the two highest-stakes rules** because they're load-bearing for every ability description and a single violation ships to a public release: |
| 20 | + |
| 21 | +- **Privacy.** Analytics responses are aggregated counts/sums/averages only. No customer names, emails, or addresses unless the `hey_woo_allow_customer_pii` option is opt-in **and** the response shape clearly justifies it. Default off. |
| 22 | +- **Merchant scope.** The AI is talking to a merchant. They can't add a tool, register a REST endpoint, or edit plugin code. Tool descriptions and merchant-facing text MUST NOT suggest "a future X skill would answer this" — substitute with something the merchant can action (a setting, a manual workflow, a connector, or an honest "this isn't something we can answer"). The static guardrail-sweep test (`tests/integration/test-ability-description-guardrails.php`) catches the obvious violations on every `./bin/check`. |
| 23 | + |
| 24 | +## Layout |
| 25 | + |
| 26 | +``` |
| 27 | +hey-woo/ |
| 28 | +├── hey-woo.php # Plugin bootstrap (HPOS declaration, requirements, options migration) |
| 29 | +├── includes/ |
| 30 | +│ ├── class-plugin.php # Singleton — wires hooks, MCP filter, mcp_adapter_init injection |
| 31 | +│ ├── abilities/ # One file per skill. wc-analytics/* (analytics tools), |
| 32 | +│ │ # hey-woo/* (store/readiness/search tools), wc-knowledge/* |
| 33 | +│ │ # (resources), wc-prompts/* (prompts), hey-woo-integrations/* |
| 34 | +│ │ # (dev-only prototype scaffolds — gated by wp_get_environment_type) |
| 35 | +│ ├── api/ # REST controllers + AnalyticsController (shared SQL helper — |
| 36 | +│ │ # no REST routes; abilities call into it) |
| 37 | +│ ├── knowledge/ # Provider pattern (store profile / catalog / product / policy) |
| 38 | +│ ├── scoring/ # Engine + 4 factors (product, schema, content, policy) |
| 39 | +│ ├── settings/ # WC > Settings > Hey Woo tab |
| 40 | +│ └── telemetry/ # SkillTelemetry + handlers (log, Tracks-gated by opt-in toggle) |
| 41 | +├── tests/integration/ # PHPUnit; runs inside wp-env tests-cli container |
| 42 | +├── tools/ |
| 43 | +│ ├── seed-demo-store.php # 24-month, 5k-order seeded demo store (mt_srand(42)) |
| 44 | +│ └── mu-plugins/ # dev-only mu-plugins (allow-insecure-transport for HTTP wp-env) |
| 45 | +├── skills/ # Reference Claude Code / Codex skills (catalog-audit, |
| 46 | +│ # product-content-generator, store-health-monitor) |
| 47 | +├── bin/ |
| 48 | +│ ├── check # Local CI mirror — PHPCS + composer audit + PHPUnit + DCC |
| 49 | +│ └── check-dcc # Data Consistency Checker (gated; auto-skips if not installed) |
| 50 | +├── docs/performance-and-hosting.md |
| 51 | +├── .wp-env.json # wp-env (port 8888, mounts plugin + tools/mu-plugins) |
| 52 | +└── .github/workflows/ # ci.yml (PHPCS + PHPUnit) · release.yml (tag → plugin zip) |
| 53 | +``` |
| 54 | + |
| 55 | +## Local dev |
| 56 | + |
| 57 | +```bash |
| 58 | +npx @wordpress/env start # boots WP 6.9 + WC + this plugin on http://localhost:8888 |
| 59 | + # afterStart enables the MCP feature flag, activates WC + Hey Woo, |
| 60 | + # installs WC pages, sets a UK store address (London / GBP) |
| 61 | +./bin/check # full pre-push gate (PHPCS, composer audit, PHPUnit, DCC) |
| 62 | +``` |
| 63 | + |
| 64 | +To seed a realistic demo store (deterministic — `mt_srand(42)`): |
| 65 | + |
| 66 | +```bash |
| 67 | +npx @wordpress/env run cli -- bash -c "cat > /tmp/seed.php" < tools/seed-demo-store.php |
| 68 | +npx @wordpress/env run cli -- wp eval-file /tmp/seed.php |
| 69 | +``` |
| 70 | + |
| 71 | +## Architecture decisions baked in |
| 72 | + |
| 73 | +These are validated decisions. **MUST NOT** relitigate without strong new signal. |
| 74 | + |
| 75 | +- **No separate MCP server process.** Everything ships through WC core's MCP at `/wp-json/woocommerce/mcp`. This plugin registers WordPress Abilities (`wp_register_ability`) and opts them in via `woocommerce_mcp_include_ability` for tools and `mcp_adapter_init` (priority 20, after WC's 10) for resources/prompts. WC core's component registry exposes `register_resources()` / `register_prompts()` — that's the injection seam. |
| 76 | +- **Single Abilities API namespace.** Every analytics skill is at `wp-abilities/v1/abilities/wc-analytics/{skill}/run`. Earlier custom `hey-woo/v1/analytics/*` REST routes were folded into Abilities so MCP and direct callers hit one surface. |
| 77 | +- **Three plugin-owned ability prefixes**, declared in `Plugin::OWNED_ABILITY_NAMESPACES`: `wc-analytics/`, `hey-woo/`, `hey-woo-integrations/`. The WC auth scope filter trusts only routes under these prefixes — a Hey Woo consumer key cannot be replayed against abilities registered by other plugins. Adding a fourth prefix is a single-edit operation; the `include_wc_analytics_in_mcp` filter must be updated in lockstep. |
| 78 | +- **Aggregated-only privacy by default.** PII gate (`hey_woo_allow_customer_pii`) is off; merchants opt in only when chaining with email/CRM MCPs that need real addresses. `wc_string_to_bool` reads the option (not `(bool)` — `'no'` would otherwise be truthy). |
| 79 | +- **HPOS-compatible.** Declared via `FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true)`. SQL must read from HPOS tables (`wp_wc_orders_meta` for attribution) when available, falling back to `wp_postmeta` only when HPOS isn't enabled. The runtime branch lives in `AnalyticsController::get_order_meta_source()`. |
| 80 | +- **No background work.** No cron, no polling, no sync jobs. The plugin runs only when an MCP/REST request arrives. This is the contract that lets the perf doc say "lighter than loading WC Analytics a few times an hour." |
| 81 | +- **Transient cache with stable keys.** Never pass live `DateTime` objects into cache-key generation; normalise to `Y-m-d H:i:s` strings before hashing. WC core's DataStore cache has a known microseconds-in-key bug — don't reach for it. |
| 82 | + |
| 83 | +## Stack |
| 84 | + |
| 85 | +- **PHP 7.4+** (Composer platform pinned to 7.4 to match CI; PHPCompatibilityWP enforces the floor) |
| 86 | +- **WordPress 6.9+** (Abilities API requires it) |
| 87 | +- **WooCommerce 10.6+** (tested up to 10.7) |
| 88 | +- **PHPCS:** `WordPress-Extra` + `WordPress-Docs` + `WooCommerce` rulesets via `dealerdirect/phpcodesniffer-composer-installer` |
| 89 | +- **PHPUnit 9.6** + `yoast/phpunit-polyfills` — runs *inside* the wp-env `tests-cli` container, not on host PHP |
| 90 | +- **`@wordpress/scripts plugin-zip`** for release builds; CI tag (`v*`) triggers `.github/workflows/release.yml` |
| 91 | + |
| 92 | +## Common pitfalls |
| 93 | + |
| 94 | +### `./bin/check` requires the wp-env tests container |
| 95 | + |
| 96 | +Step 4 of the script (PHPUnit) shells into `npx @wordpress/env run tests-cli`. If wp-env isn't running, the script aborts with an explicit message before running PHPUnit. Start it once per session: |
| 97 | + |
| 98 | +```bash |
| 99 | +npx @wordpress/env start |
| 100 | +``` |
| 101 | + |
| 102 | +The DCC step (`bin/check-dcc`) is gated — it auto-skips when `vendor-plugins/wca-data-consistency/` isn't present, or when the dev store has no orders. Don't try to "fix" the skip; the upstream plugin is privately distributed and there's no public install path yet. |
| 103 | + |
| 104 | +### MCP requires HTTPS by default |
| 105 | + |
| 106 | +Local wp-env runs on plain HTTP. The mu-plugin at `tools/mu-plugins/dev-allow-insecure-transport.php` sets `woocommerce_mcp_allow_insecure_transport` for the dev environment only. Don't move that filter into the plugin proper — it's dev-only, deliberately. |
| 107 | + |
| 108 | +### Two-step skill addition |
| 109 | + |
| 110 | +A new analytics skill needs **code + PHPUnit test + two static-sweep constants** in the same PR. The coverage guard at `tests/integration/test-ability-registration.php` fails CI when: |
| 111 | + |
| 112 | +1. The new ability ID isn't in `Test_Ability_Registration::EXPECTED_ABILITY_IDS`, **or** |
| 113 | +2. There's no `tests/integration/test-<slug>.php` file with at least one `test_*` method. |
| 114 | + |
| 115 | +The full how-to is in CONTRIBUTING.md (`Adding a new analytics Skill`). Don't shortcut the test — the coverage guard is the substitute for "did anyone actually verify this against real data?" |
| 116 | + |
| 117 | +### The `hey-woo-tests` mapping is the integration-tests mount |
| 118 | + |
| 119 | +`.wp-env.json` mounts the repo into the dev environment as `hey-woo` and into the **tests** environment as `hey-woo-tests`. The PHPUnit container's working dir is `wp-content/plugins/hey-woo-tests` — that's why `bootstrap.php` does `glob($plugin_dir . '/hey-woo/...')` to find the production-side mount when loading WC. Don't rename either mount; the bootstrap and the CI workflow both rely on the slug. |
| 120 | + |
| 121 | +### British English |
| 122 | + |
| 123 | +The plugin is published as a UK-Automattic-shaped product (default seed store is London / GBP). British English everywhere — text strings, comments, docblocks, error messages, README/CONTRIBUTING. WP-Docs sniffs don't enforce this; rely on review. |
| 124 | + |
| 125 | +## Workflow |
| 126 | + |
| 127 | +- **Branch per change.** One feature / fix per branch. PR back to `trunk`. |
| 128 | +- **Conventional commits.** `feat:`, `fix:`, `chore:`, `docs:`. The release workflow expects this for auto-generated release notes (`generate_release_notes: true`). |
| 129 | +- **Definition of done for a PR:** |
| 130 | + 1. `./bin/check` exits green locally (PHPCS + composer audit + PHPUnit + DCC). |
| 131 | + 2. New analytics skill = code + PHPUnit test + the two static-sweep constants in the same PR. |
| 132 | + 3. Tool/ability descriptions don't violate the merchant-scope rule (the description-guardrail sweep enforces the obvious cases; review catches the rest). |
| 133 | + 4. CONTRIBUTING.md "Design patterns worth knowing" section updated when a new reusable pattern is established. |
| 134 | + 5. AGENTS.md (this file) updated when a new gotcha, command, or convention is introduced. |
| 135 | +- **Don't commit `hey-woo.zip`.** It's checked into the repo as a one-off artefact, but `*.zip` is in `.gitignore` and the release workflow rebuilds it from the tag. Don't update it in regular commits. |
0 commit comments