claude-plugin-wordpress-manager 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +26 -0
  3. package/agents/wp-content-strategist.md +25 -0
  4. package/agents/wp-ecommerce-manager.md +23 -0
  5. package/agents/wp-site-manager.md +26 -0
  6. package/docs/GUIDE.md +116 -28
  7. package/package.json +8 -3
  8. package/skills/wordpress-router/references/decision-tree.md +8 -2
  9. package/skills/wp-content/SKILL.md +1 -0
  10. package/skills/wp-content-attribution/SKILL.md +97 -0
  11. package/skills/wp-content-attribution/references/attribution-models.md +189 -0
  12. package/skills/wp-content-attribution/references/conversion-funnels.md +137 -0
  13. package/skills/wp-content-attribution/references/reporting-dashboards.md +199 -0
  14. package/skills/wp-content-attribution/references/roi-calculation.md +202 -0
  15. package/skills/wp-content-attribution/references/utm-tracking-setup.md +161 -0
  16. package/skills/wp-content-attribution/scripts/attribution_inspect.mjs +277 -0
  17. package/skills/wp-headless/SKILL.md +1 -0
  18. package/skills/wp-i18n/SKILL.md +1 -0
  19. package/skills/wp-multilang-network/SKILL.md +107 -0
  20. package/skills/wp-multilang-network/references/content-sync.md +182 -0
  21. package/skills/wp-multilang-network/references/hreflang-config.md +198 -0
  22. package/skills/wp-multilang-network/references/language-routing.md +234 -0
  23. package/skills/wp-multilang-network/references/network-architecture.md +119 -0
  24. package/skills/wp-multilang-network/references/seo-international.md +213 -0
  25. package/skills/wp-multilang-network/scripts/multilang_inspect.mjs +308 -0
  26. package/skills/wp-multisite/SKILL.md +1 -0
  27. package/skills/wp-programmatic-seo/SKILL.md +97 -0
  28. package/skills/wp-programmatic-seo/references/data-sources.md +200 -0
  29. package/skills/wp-programmatic-seo/references/location-seo.md +134 -0
  30. package/skills/wp-programmatic-seo/references/product-seo.md +147 -0
  31. package/skills/wp-programmatic-seo/references/technical-seo.md +197 -0
  32. package/skills/wp-programmatic-seo/references/template-architecture.md +125 -0
  33. package/skills/wp-programmatic-seo/scripts/programmatic_seo_inspect.mjs +264 -0
  34. package/skills/wp-woocommerce/SKILL.md +1 -0
@@ -0,0 +1,202 @@
1
+ # ROI Calculation
2
+
3
+ Use this file when calculating content return on investment — revenue per post, content ROI formula, customer acquisition cost, and lifetime value by content source.
4
+
5
+ ## Revenue Per Post
6
+
7
+ The most straightforward content attribution metric.
8
+
9
+ **Formula:**
10
+ ```
11
+ Revenue Per Post = Total attributed revenue / Number of posts
12
+ ```
13
+
14
+ **Per-post attribution (last-touch):**
15
+ ```
16
+ Revenue(Post X) = SUM(order_total) WHERE _last_utm_campaign = "post-x-slug"
17
+ ```
18
+
19
+ **Per-post attribution (first-touch):**
20
+ ```
21
+ Revenue(Post X) = SUM(order_total) WHERE _first_utm_campaign = "post-x-slug"
22
+ ```
23
+
24
+ ### Calculating with WooCommerce MCP Tools
25
+
26
+ ```bash
27
+ # Step 1: Get completed orders for the period
28
+ wc_list_orders(status="completed", after="2025-01-01", before="2025-01-31", per_page=100)
29
+
30
+ # Step 2: For each order, read utm meta
31
+ # Group orders by _last_utm_campaign value
32
+ # Sum order_total per campaign (campaign = post slug)
33
+
34
+ # Step 3: Get content list for the same period
35
+ list_content(type="post", status="publish", after="2025-01-01", before="2025-01-31")
36
+
37
+ # Step 4: Match campaign slugs to post titles
38
+ # Result: { post_title: "Cactus Water Benefits", slug: "cactus-water-benefits", revenue: 2500, orders: 15 }
39
+ ```
40
+
41
+ ### Revenue Per Post Benchmarks
42
+
43
+ | Content Type | Typical Revenue/Post | Notes |
44
+ |-------------|---------------------|-------|
45
+ | Product review | $500–$5,000 | High intent, bottom-of-funnel |
46
+ | How-to guide | $100–$1,000 | Indirect conversion, builds trust |
47
+ | Comparison post | $1,000–$10,000 | Very high intent, decision-stage |
48
+ | Category guide | $200–$2,000 | Mid-funnel, product discovery |
49
+ | News/update | $50–$500 | Low intent, brand awareness |
50
+
51
+ ## Content ROI Formula
52
+
53
+ **Formula:**
54
+ ```
55
+ Content ROI = (Revenue - Content Cost) / Content Cost × 100%
56
+ ```
57
+
58
+ **Example:**
59
+ ```
60
+ Blog post cost: $200 (writer) + $50 (images) + $30 (editing) = $280
61
+ Revenue attributed: $1,400 (last-touch, 3 months)
62
+ Content ROI: ($1,400 - $280) / $280 × 100% = 400% ROI
63
+ ```
64
+
65
+ ### Content Cost Components
66
+
67
+ | Component | Typical Cost | How to Track |
68
+ |-----------|-------------|--------------|
69
+ | Writing | $0.10–$0.50/word | Per-post invoice or hourly rate |
70
+ | Editing | $25–$100/post | Editor time tracking |
71
+ | Images/media | $10–$100/post | Stock photo licenses, design time |
72
+ | SEO optimization | $25–$75/post | SEO tool costs + specialist time |
73
+ | Publishing/formatting | $15–$30/post | CMS time |
74
+ | **Total typical cost** | **$100–$500/post** | Sum all components |
75
+
76
+ ### ROI by Content Category
77
+
78
+ Track ROI by category to identify the most profitable content pillars:
79
+
80
+ ```
81
+ Category Posts Total Cost Total Revenue ROI
82
+ ──────────────────────────────────────────────────────
83
+ Product reviews 10 $3,000 $25,000 733%
84
+ How-to guides 20 $4,000 $8,000 100%
85
+ Company news 15 $1,500 $750 -50%
86
+ Case studies 5 $2,500 $12,000 380%
87
+ ```
88
+
89
+ **Action:** Double down on product reviews and case studies; reduce company news investment.
90
+
91
+ ## Customer Acquisition Cost (CAC) by Content Type
92
+
93
+ **Formula:**
94
+ ```
95
+ CAC = Total content investment / New customers acquired from content
96
+ ```
97
+
98
+ **By content type:**
99
+ ```
100
+ CAC(blog) = Blog content costs / New customers with _first_utm_source = "blog"
101
+ CAC(email) = Email costs / New customers with _first_utm_source = "newsletter"
102
+ CAC(social) = Social costs / New customers with _first_utm_source = "facebook|instagram"
103
+ ```
104
+
105
+ ### CAC Benchmarks (E-commerce)
106
+
107
+ | Channel | Typical CAC | Notes |
108
+ |---------|------------|-------|
109
+ | Organic blog content | $20–$80 | Lower CAC, longer to build |
110
+ | Email newsletter | $10–$40 | Lowest CAC, requires list |
111
+ | Paid search (Google) | $30–$100 | Immediate but expensive |
112
+ | Social media (organic) | $25–$75 | Variable, platform-dependent |
113
+ | Social media (paid) | $15–$60 | Scalable with budget |
114
+
115
+ ## Lifetime Value (LTV) by Acquisition Source
116
+
117
+ **Formula:**
118
+ ```
119
+ LTV = Average Order Value × Purchase Frequency × Customer Lifespan
120
+ ```
121
+
122
+ **By source:**
123
+ ```
124
+ LTV(blog) = AOV(blog customers) × Frequency(blog customers) × Lifespan(blog customers)
125
+ ```
126
+
127
+ ### Calculating with WooCommerce Data
128
+
129
+ ```bash
130
+ # Get top sellers to understand AOV patterns
131
+ wc_get_top_sellers(period="year")
132
+
133
+ # Get sales report for overall metrics
134
+ wc_get_sales_report(period="year")
135
+ # Returns: total_sales, total_orders → AOV = total_sales / total_orders
136
+
137
+ # For source-specific LTV:
138
+ # 1. Filter orders by _first_utm_source
139
+ # 2. Group by customer_id
140
+ # 3. Calculate per-customer: total_spent, order_count, first_order_date, last_order_date
141
+ # 4. Average across customers from that source
142
+ ```
143
+
144
+ ### LTV:CAC Ratio
145
+
146
+ ```
147
+ Healthy ratio: LTV:CAC > 3:1
148
+
149
+ Example:
150
+ Blog customers: LTV = $240, CAC = $50 → Ratio = 4.8:1 ✓ Excellent
151
+ Paid ad customers: LTV = $180, CAC = $80 → Ratio = 2.25:1 ⚠ Below target
152
+ ```
153
+
154
+ **Action:** If LTV:CAC < 3:1, either reduce acquisition cost or increase customer retention/upsell.
155
+
156
+ ## Content Efficiency Metrics
157
+
158
+ ### Revenue Per Word
159
+
160
+ ```
161
+ Revenue Per Word = Total attributed revenue / Total words published
162
+ ```
163
+
164
+ Useful for comparing content formats: long-form guides vs short product reviews.
165
+
166
+ ### Revenue Per Topic
167
+
168
+ ```
169
+ Revenue Per Topic = SUM(revenue of posts in topic cluster) / Number of posts in cluster
170
+ ```
171
+
172
+ Identifies which topic clusters are most commercially valuable.
173
+
174
+ ### Time to ROI
175
+
176
+ ```
177
+ Time to ROI = Days from publish date to break-even (revenue ≥ content cost)
178
+ ```
179
+
180
+ Short time to ROI = content with immediate commercial intent (product reviews).
181
+ Long time to ROI = evergreen SEO content (compounds over months).
182
+
183
+ ## Using WC Reports for Date Correlation
184
+
185
+ ```bash
186
+ # Monthly sales report
187
+ wc_get_sales_report(date_min="2025-03-01", date_max="2025-03-31")
188
+
189
+ # Top-selling products in the period
190
+ wc_get_top_sellers(date_min="2025-03-01", date_max="2025-03-31")
191
+
192
+ # Cross-reference with content published before the period:
193
+ list_content(type="post", status="publish", before="2025-03-31", orderby="date", order="desc", per_page=20)
194
+ ```
195
+
196
+ ## Decision Checklist
197
+
198
+ 1. Is content cost tracked per post? → Set up cost tracking (spreadsheet or custom field)
199
+ 2. Is revenue attributed to individual posts via UTM? → Verify mu-plugin capturing data
200
+ 3. Is CAC calculated by acquisition source? → Group orders by first-touch source
201
+ 4. Is LTV:CAC ratio above 3:1? → If not, optimize acquisition or retention
202
+ 5. Are content investments being shifted to highest-ROI categories? → Review quarterly
@@ -0,0 +1,161 @@
1
+ # UTM Tracking Setup
2
+
3
+ Use this file when setting up UTM parameter capture for WooCommerce orders — parameter architecture, mu-plugin installation, naming conventions, and verification.
4
+
5
+ ## UTM Parameter Architecture
6
+
7
+ | Parameter | Purpose | Example |
8
+ |-----------|---------|---------|
9
+ | `utm_source` | Where traffic originates | `blog`, `newsletter`, `google`, `facebook` |
10
+ | `utm_medium` | Marketing channel type | `organic`, `email`, `cpc`, `social`, `referral` |
11
+ | `utm_campaign` | Specific campaign name | `spring-sale-2025`, `product-launch-x` |
12
+ | `utm_content` | Differentiates ad/link variants | `cta-button`, `sidebar-banner`, `post-footer` |
13
+ | `utm_term` | Paid search keywords | `cactus-water-buy`, `zero-calorie-drink` |
14
+
15
+ ## mu-plugin Pattern: Capture UTM on Checkout
16
+
17
+ Create a mu-plugin to automatically capture UTM parameters from the visitor session and store them as WooCommerce order meta:
18
+
19
+ ```php
20
+ <?php
21
+ /**
22
+ * Plugin Name: UTM Order Attribution
23
+ * Description: Captures UTM parameters from visitor session and stores as order meta.
24
+ * Version: 1.0.0
25
+ */
26
+
27
+ // Store UTM params in session cookie on first visit
28
+ add_action('init', function () {
29
+ if (!is_admin() && !wp_doing_cron()) {
30
+ $utm_params = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
31
+ foreach ($utm_params as $param) {
32
+ if (isset($_GET[$param]) && !empty($_GET[$param])) {
33
+ // Store in cookie for 30 days (first-touch attribution)
34
+ $cookie_name = '_wc_' . $param;
35
+ if (!isset($_COOKIE[$cookie_name])) {
36
+ setcookie($cookie_name, sanitize_text_field($_GET[$param]), time() + (30 * DAY_IN_SECONDS), '/');
37
+ }
38
+ // Always update last-touch cookie
39
+ setcookie('_wc_last_' . $param, sanitize_text_field($_GET[$param]), time() + (30 * DAY_IN_SECONDS), '/');
40
+ }
41
+ }
42
+ }
43
+ });
44
+
45
+ // Save UTM data to order meta on checkout
46
+ add_action('woocommerce_checkout_order_processed', function ($order_id) {
47
+ $order = wc_get_order($order_id);
48
+ if (!$order) return;
49
+
50
+ $utm_params = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
51
+ foreach ($utm_params as $param) {
52
+ // Save first-touch
53
+ $first_cookie = '_wc_' . $param;
54
+ if (isset($_COOKIE[$first_cookie]) && !empty($_COOKIE[$first_cookie])) {
55
+ $order->update_meta_data('_first_' . $param, sanitize_text_field($_COOKIE[$first_cookie]));
56
+ }
57
+ // Save last-touch
58
+ $last_cookie = '_wc_last_' . $param;
59
+ if (isset($_COOKIE[$last_cookie]) && !empty($_COOKIE[$last_cookie])) {
60
+ $order->update_meta_data('_last_' . $param, sanitize_text_field($_COOKIE[$last_cookie]));
61
+ }
62
+ }
63
+
64
+ // Save landing page URL (first page visited)
65
+ if (isset($_COOKIE['_wc_landing_page'])) {
66
+ $order->update_meta_data('_landing_page', sanitize_url($_COOKIE['_wc_landing_page']));
67
+ }
68
+
69
+ $order->save();
70
+ });
71
+
72
+ // Track landing page
73
+ add_action('init', function () {
74
+ if (!is_admin() && !wp_doing_cron() && !isset($_COOKIE['_wc_landing_page'])) {
75
+ $landing = esc_url_raw((isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
76
+ setcookie('_wc_landing_page', $landing, time() + (30 * DAY_IN_SECONDS), '/');
77
+ }
78
+ });
79
+ ```
80
+
81
+ **Installation:**
82
+ 1. Save as `wp-content/mu-plugins/utm-order-attribution.php`
83
+ 2. mu-plugins load automatically — no activation needed
84
+ 3. Verify by placing a test order with UTM params in the URL
85
+
86
+ ## UTM Naming Conventions
87
+
88
+ Consistent naming is critical for accurate attribution. Adopt these rules:
89
+
90
+ | Rule | Good | Bad |
91
+ |------|------|-----|
92
+ | Lowercase always | `utm_source=blog` | `utm_source=Blog` |
93
+ | Hyphens for spaces | `spring-sale-2025` | `spring_sale_2025` or `spring sale` |
94
+ | No special chars | `product-launch` | `product_launch!` |
95
+ | Consistent source names | `newsletter` (always) | `email`, `newsletter`, `mail` (mixed) |
96
+ | Date in campaigns | `black-friday-2025` | `black-friday` (ambiguous year) |
97
+
98
+ **Recommended source taxonomy:**
99
+
100
+ | Source | Medium | When to Use |
101
+ |--------|--------|-------------|
102
+ | `blog` | `organic` | Internal blog post links to products |
103
+ | `blog` | `cta` | Blog post CTA buttons to products |
104
+ | `newsletter` | `email` | Email newsletter links |
105
+ | `google` | `cpc` | Google Ads campaigns |
106
+ | `facebook` | `social` | Facebook organic or paid posts |
107
+ | `instagram` | `social` | Instagram bio/stories links |
108
+ | `partner-name` | `referral` | Partner/affiliate links |
109
+
110
+ ## Internal Link UTM Tagging
111
+
112
+ Tag all blog-to-product links with UTMs:
113
+
114
+ ```html
115
+ <!-- Blog post CTA linking to product -->
116
+ <a href="/product/cactus-water/?utm_source=blog&utm_medium=cta&utm_campaign=cactus-water-benefits&utm_content=post-footer-button">
117
+ Buy Cactus Water
118
+ </a>
119
+
120
+ <!-- Sidebar widget linking to product -->
121
+ <a href="/product/cactus-water/?utm_source=blog&utm_medium=sidebar&utm_campaign=always-on&utm_content=product-widget">
122
+ Try Cactus Water
123
+ </a>
124
+ ```
125
+
126
+ **Automated tagging approach:** Use a WordPress filter to append UTMs to all internal product links in post content:
127
+
128
+ ```php
129
+ add_filter('the_content', function ($content) {
130
+ if (!is_singular('post')) return $content;
131
+ $post_slug = get_post_field('post_name', get_the_ID());
132
+ // Append UTM to internal /product/ links that don't already have UTM
133
+ $content = preg_replace_callback(
134
+ '#href="(/product/[^"]*?)(?<!\?[^"]*utm_source[^"]*)"#',
135
+ function ($matches) use ($post_slug) {
136
+ $sep = strpos($matches[1], '?') !== false ? '&' : '?';
137
+ return 'href="' . $matches[1] . $sep . 'utm_source=blog&utm_medium=organic&utm_campaign=' . $post_slug . '"';
138
+ },
139
+ $content
140
+ );
141
+ return $content;
142
+ });
143
+ ```
144
+
145
+ ## Verification
146
+
147
+ After installing the mu-plugin:
148
+
149
+ 1. **Test visit:** Navigate to `yoursite.com/product/example/?utm_source=test&utm_medium=test&utm_campaign=test`
150
+ 2. **Place test order** through WooCommerce checkout
151
+ 3. **Check order meta:** Use `wc_list_orders` MCP tool or WooCommerce admin → Orders → order details
152
+ 4. **Verify fields:** `_first_utm_source`, `_last_utm_source`, `_first_utm_campaign`, etc. should be populated
153
+ 5. **Check cookies:** Browser dev tools → Application → Cookies → look for `_wc_utm_*` cookies
154
+
155
+ ## Decision Checklist
156
+
157
+ 1. Is the mu-plugin installed in `wp-content/mu-plugins/`? → Verify file exists
158
+ 2. Are UTM naming conventions documented for the team? → Share taxonomy table
159
+ 3. Are internal blog→product links tagged with UTMs? → Audit sample posts
160
+ 4. Has a test order been placed with UTM params to verify capture? → Must pass before going live
161
+ 5. Is cookie duration appropriate (30 days default)? → Adjust for sales cycle length
@@ -0,0 +1,277 @@
1
+ /**
2
+ * attribution_inspect.mjs — Detect content-commerce attribution readiness.
3
+ *
4
+ * Scans for WooCommerce presence, analytics plugins, UTM tracking setup,
5
+ * content/product volume, and existing order meta with source fields.
6
+ * Outputs a JSON report to stdout.
7
+ *
8
+ * Usage:
9
+ * node attribution_inspect.mjs [--cwd=/path/to/check]
10
+ *
11
+ * Exit codes:
12
+ * 0 — attribution indicators detected
13
+ * 1 — no attribution indicators detected
14
+ */
15
+
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import process from "node:process";
19
+ import { execSync } from "node:child_process";
20
+
21
+ const TOOL_VERSION = "1.0.0";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ function statSafe(p) {
28
+ try {
29
+ return fs.statSync(p);
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ function readFileSafe(p) {
36
+ try {
37
+ return fs.readFileSync(p, "utf8");
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function readJsonSafe(p) {
44
+ const raw = readFileSafe(p);
45
+ if (!raw) return null;
46
+ try {
47
+ return JSON.parse(raw);
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function execSafe(cmd, cwd, timeoutMs = 5000) {
54
+ try {
55
+ return execSync(cmd, { encoding: "utf8", timeout: timeoutMs, cwd, stdio: ["pipe", "pipe", "pipe"] }).trim();
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ function readdirSafe(dir) {
62
+ try {
63
+ return fs.readdirSync(dir);
64
+ } catch {
65
+ return [];
66
+ }
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Parse --cwd argument
71
+ // ---------------------------------------------------------------------------
72
+
73
+ function parseCwd() {
74
+ const cwdArg = process.argv.find((a) => a.startsWith("--cwd="));
75
+ return cwdArg ? cwdArg.slice(6) : process.cwd();
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Detect WooCommerce
80
+ // ---------------------------------------------------------------------------
81
+
82
+ function detectWooCommerce(cwd) {
83
+ const pluginsDir = path.join(cwd, "wp-content", "plugins");
84
+ if (statSafe(path.join(pluginsDir, "woocommerce"))?.isDirectory()) return true;
85
+
86
+ const composer = readJsonSafe(path.join(cwd, "composer.json"));
87
+ if (composer) {
88
+ const allDeps = { ...composer.require, ...composer["require-dev"] };
89
+ if (allDeps["woocommerce/woocommerce"] || allDeps["wpackagist-plugin/woocommerce"]) return true;
90
+ }
91
+
92
+ return false;
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Detect analytics plugins
97
+ // ---------------------------------------------------------------------------
98
+
99
+ function detectAnalyticsPlugin(cwd) {
100
+ const result = { detected: false, plugin: null };
101
+
102
+ const pluginsDir = path.join(cwd, "wp-content", "plugins");
103
+ const analyticsPlugins = [
104
+ { dir: "google-analytics-for-wordpress", name: "MonsterInsights" },
105
+ { dir: "google-site-kit", name: "Google Site Kit" },
106
+ { dir: "woocommerce-google-analytics-integration", name: "WooCommerce Google Analytics" },
107
+ { dir: "ga-google-analytics", name: "GA Google Analytics" },
108
+ { dir: "analytify", name: "Analytify" },
109
+ { dir: "matomo", name: "Matomo Analytics" },
110
+ ];
111
+
112
+ for (const plugin of analyticsPlugins) {
113
+ if (statSafe(path.join(pluginsDir, plugin.dir))?.isDirectory()) {
114
+ result.detected = true;
115
+ result.plugin = plugin.name;
116
+ return result;
117
+ }
118
+ }
119
+
120
+ return result;
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Detect UTM tracking
125
+ // ---------------------------------------------------------------------------
126
+
127
+ function detectUtmTracking(cwd) {
128
+ const result = { detected: false, sources: [] };
129
+
130
+ // Check mu-plugins for UTM capture
131
+ const muPluginsDir = path.join(cwd, "wp-content", "mu-plugins");
132
+ const muFiles = readdirSafe(muPluginsDir);
133
+ for (const file of muFiles) {
134
+ if (!file.endsWith(".php")) continue;
135
+ const content = readFileSafe(path.join(muPluginsDir, file));
136
+ if (content && /utm_source|utm_campaign|utm_medium/i.test(content)) {
137
+ result.detected = true;
138
+ result.sources.push(`mu-plugin: ${file}`);
139
+ }
140
+ }
141
+
142
+ // Check plugins for UTM tracking
143
+ const pluginsDir = path.join(cwd, "wp-content", "plugins");
144
+ const utmPlugins = [
145
+ { dir: "utm-dot-io", name: "UTM.io" },
146
+ { dir: "campaign-url-builder", name: "Campaign URL Builder" },
147
+ { dir: "leadin", name: "HubSpot (UTM tracking)" },
148
+ ];
149
+
150
+ for (const plugin of utmPlugins) {
151
+ if (statSafe(path.join(pluginsDir, plugin.dir))?.isDirectory()) {
152
+ result.detected = true;
153
+ result.sources.push(`plugin: ${plugin.name}`);
154
+ }
155
+ }
156
+
157
+ // Check theme functions.php for UTM capture
158
+ const themesDir = path.join(cwd, "wp-content", "themes");
159
+ const themes = readdirSafe(themesDir);
160
+ for (const theme of themes) {
161
+ const functionsPhp = readFileSafe(path.join(themesDir, theme, "functions.php"));
162
+ if (functionsPhp && /utm_source|utm_campaign/i.test(functionsPhp)) {
163
+ result.detected = true;
164
+ result.sources.push(`theme: ${theme}/functions.php`);
165
+ }
166
+ }
167
+
168
+ return result;
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Detect content volume
173
+ // ---------------------------------------------------------------------------
174
+
175
+ function detectContentVolume(cwd) {
176
+ const result = { content_count: 0, product_count: 0 };
177
+
178
+ const postCount = execSafe("wp post list --post_type=post --post_status=publish --format=count 2>/dev/null", cwd);
179
+ if (postCount) result.content_count = parseInt(postCount) || 0;
180
+
181
+ const productCount = execSafe("wp post list --post_type=product --post_status=publish --format=count 2>/dev/null", cwd);
182
+ if (productCount) result.product_count = parseInt(productCount) || 0;
183
+
184
+ return result;
185
+ }
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Detect existing order meta with source fields
189
+ // ---------------------------------------------------------------------------
190
+
191
+ function detectOrderMeta(cwd) {
192
+ // Check if any completed orders have UTM meta
193
+ const orderMeta = execSafe(
194
+ `wp db query "SELECT COUNT(*) as c FROM $(wp db prefix 2>/dev/null)postmeta WHERE meta_key LIKE '%utm_source%'" --format=csv 2>/dev/null`,
195
+ cwd
196
+ );
197
+
198
+ if (orderMeta && /\d+/.test(orderMeta)) {
199
+ const count = parseInt(orderMeta.match(/\d+/)[0]);
200
+ return count > 0;
201
+ }
202
+
203
+ return false;
204
+ }
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // Main
208
+ // ---------------------------------------------------------------------------
209
+
210
+ function main() {
211
+ const cwd = parseCwd();
212
+
213
+ if (!statSafe(cwd)?.isDirectory()) {
214
+ console.error(`Error: directory not found: ${cwd}`);
215
+ process.exit(1);
216
+ }
217
+
218
+ const hasWoocommerce = detectWooCommerce(cwd);
219
+ const analytics = detectAnalyticsPlugin(cwd);
220
+ const utm = detectUtmTracking(cwd);
221
+ const volume = detectContentVolume(cwd);
222
+ const hasOrderMeta = detectOrderMeta(cwd);
223
+
224
+ const detected = hasWoocommerce && (analytics.detected || utm.detected || volume.content_count > 0);
225
+
226
+ const report = {
227
+ tool: "attribution_inspect",
228
+ version: TOOL_VERSION,
229
+ cwd,
230
+ detected,
231
+ has_woocommerce: hasWoocommerce,
232
+ analytics_plugin: analytics.plugin,
233
+ has_utm_tracking: utm.detected,
234
+ utm_sources: utm.sources,
235
+ content_count: volume.content_count,
236
+ product_count: volume.product_count,
237
+ has_order_attribution_meta: hasOrderMeta,
238
+ attribution_readiness: "unknown",
239
+ recommendations: [],
240
+ };
241
+
242
+ // Assess readiness
243
+ if (hasWoocommerce && utm.detected && analytics.detected && volume.content_count > 10) {
244
+ report.attribution_readiness = "high";
245
+ } else if (hasWoocommerce && (utm.detected || analytics.detected) && volume.content_count > 0) {
246
+ report.attribution_readiness = "medium";
247
+ } else if (hasWoocommerce && volume.content_count > 0) {
248
+ report.attribution_readiness = "low";
249
+ } else {
250
+ report.attribution_readiness = "not_ready";
251
+ }
252
+
253
+ // Recommendations
254
+ if (!hasWoocommerce) {
255
+ report.recommendations.push("WooCommerce not detected. Content-commerce attribution requires WooCommerce for sales data.");
256
+ }
257
+ if (hasWoocommerce && !utm.detected) {
258
+ report.recommendations.push("No UTM tracking detected. Install the UTM capture mu-plugin to link content visits to orders.");
259
+ }
260
+ if (hasWoocommerce && !analytics.detected) {
261
+ report.recommendations.push("No analytics plugin detected. Install MonsterInsights or Google Site Kit for traffic data.");
262
+ }
263
+ if (volume.content_count === 0) {
264
+ report.recommendations.push("No published content found. Create blog posts/content to drive traffic to products.");
265
+ }
266
+ if (volume.content_count > 0 && volume.product_count === 0) {
267
+ report.recommendations.push("Content exists but no products found. Verify WooCommerce products are published.");
268
+ }
269
+ if (hasWoocommerce && utm.detected && !hasOrderMeta) {
270
+ report.recommendations.push("UTM tracking is set up but no order attribution meta found yet. Place a test order to verify.");
271
+ }
272
+
273
+ console.log(JSON.stringify(report, null, 2));
274
+ process.exit(detected ? 0 : 1);
275
+ }
276
+
277
+ main();
@@ -167,3 +167,4 @@ Read: `references/webhooks.md`
167
167
  - For REST endpoint development, use the `wp-rest-api` skill
168
168
  - For authentication security, use the `wp-security` skill
169
169
  - For webhook configuration and management, use the `wp-webhooks` skill
170
+ - For scalable programmatic page generation with ISR/SSG, use the `wp-programmatic-seo` skill
@@ -168,3 +168,4 @@ Re-run: `node skills/wp-i18n/scripts/i18n_inspect.mjs --cwd=/path`
168
168
  - WordPress i18n Handbook: https://developer.wordpress.org/plugins/internationalization/
169
169
  - CLDR Plural Rules: https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
170
170
  - Translator Handbook: https://make.wordpress.org/polyglots/handbook/
171
+ - For multisite multi-language network orchestration (hreflang, content sync, international SEO), use the `wp-multilang-network` skill