@sadrmanesh/commerce-agent-skills 0.0.0-stage → 0.3.1

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 (201) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -2
  4. package/bin/commerce-agent-skills.mjs +368 -0
  5. package/package.json +52 -4
  6. package/rules/b2b-pricing.md +11 -0
  7. package/rules/catalog-import-and-migration.md +11 -0
  8. package/rules/checkout-ux-and-conversion.md +12 -0
  9. package/rules/conversion-event-tracking.md +11 -0
  10. package/rules/ecommerce-seo-structured-data.md +11 -0
  11. package/rules/fraud-and-risk-basics.md +11 -0
  12. package/rules/google-merchant-product-feeds.md +11 -0
  13. package/rules/headless-nextjs-storefront.md +10 -0
  14. package/rules/inventory-and-multi-warehouse.md +10 -0
  15. package/rules/iranian-payment-gateways.md +10 -0
  16. package/rules/marketplace-search-and-ranking.md +10 -0
  17. package/rules/mcp-store-tooling.md +11 -0
  18. package/rules/multi-vendor-payouts.md +11 -0
  19. package/rules/order-and-webhook-reliability.md +12 -0
  20. package/rules/payment-gateway-integration.md +12 -0
  21. package/rules/product-catalog-modeling.md +11 -0
  22. package/rules/product-description-guardrails.md +10 -0
  23. package/rules/returns-and-refunds.md +10 -0
  24. package/rules/rtl-and-persian-localization.md +11 -0
  25. package/rules/shopify-app-development.md +10 -0
  26. package/rules/storefront-accessibility.md +11 -0
  27. package/rules/storefront-performance.md +10 -0
  28. package/rules/stripe-payments.md +10 -0
  29. package/rules/subscription-billing.md +10 -0
  30. package/rules/tax-and-shipping.md +10 -0
  31. package/rules/vector-product-search.md +10 -0
  32. package/rules/woocommerce-plugin-development.md +10 -0
  33. package/rules/woocommerce-rest-api.md +10 -0
  34. package/rules/wordpress-plugin-security.md +10 -0
  35. package/scripts/lib/frontmatter.mjs +218 -0
  36. package/skills/b2b-pricing/SKILL.md +55 -0
  37. package/skills/b2b-pricing/evals/examples/net-terms.md +3 -0
  38. package/skills/b2b-pricing/evals/examples/tiered-wholesale.md +3 -0
  39. package/skills/b2b-pricing/evals/tasks.json +19 -0
  40. package/skills/b2b-pricing/references/price-lists.md +46 -0
  41. package/skills/b2b-pricing/scripts/resolve-price.mjs +93 -0
  42. package/skills/catalog-import-and-migration/SKILL.md +58 -0
  43. package/skills/catalog-import-and-migration/evals/examples/platform-migration-urls.md +3 -0
  44. package/skills/catalog-import-and-migration/evals/examples/rerunnable-import.md +3 -0
  45. package/skills/catalog-import-and-migration/evals/tasks.json +20 -0
  46. package/skills/catalog-import-and-migration/references/field-mapping.md +43 -0
  47. package/skills/catalog-import-and-migration/scripts/check-import-rows.mjs +75 -0
  48. package/skills/catalog-order.txt +29 -0
  49. package/skills/checkout-ux-and-conversion/SKILL.md +86 -0
  50. package/skills/checkout-ux-and-conversion/evals/examples/guest-total.md +3 -0
  51. package/skills/checkout-ux-and-conversion/evals/examples/single-submit.md +3 -0
  52. package/skills/checkout-ux-and-conversion/evals/tasks.json +30 -0
  53. package/skills/checkout-ux-and-conversion/references/checkout-review.md +41 -0
  54. package/skills/conversion-event-tracking/SKILL.md +64 -0
  55. package/skills/conversion-event-tracking/evals/examples/purchase-once.md +3 -0
  56. package/skills/conversion-event-tracking/evals/examples/variant-item-id.md +3 -0
  57. package/skills/conversion-event-tracking/evals/tasks.json +30 -0
  58. package/skills/conversion-event-tracking/references/events.md +36 -0
  59. package/skills/conversion-event-tracking/scripts/check-event.mjs +79 -0
  60. package/skills/ecommerce-seo-structured-data/SKILL.md +84 -0
  61. package/skills/ecommerce-seo-structured-data/assets/example-product.jsonld +15 -0
  62. package/skills/ecommerce-seo-structured-data/evals/examples/extend-woocommerce-schema.md +8 -0
  63. package/skills/ecommerce-seo-structured-data/evals/examples/variant-offer.md +7 -0
  64. package/skills/ecommerce-seo-structured-data/evals/tasks.json +29 -0
  65. package/skills/ecommerce-seo-structured-data/references/product-jsonld.md +83 -0
  66. package/skills/ecommerce-seo-structured-data/scripts/check-product-jsonld.mjs +211 -0
  67. package/skills/fraud-and-risk-basics/SKILL.md +66 -0
  68. package/skills/fraud-and-risk-basics/evals/examples/review-queue.md +3 -0
  69. package/skills/fraud-and-risk-basics/evals/examples/wait-for-3ds.md +3 -0
  70. package/skills/fraud-and-risk-basics/evals/tasks.json +30 -0
  71. package/skills/google-merchant-product-feeds/SKILL.md +70 -0
  72. package/skills/google-merchant-product-feeds/assets/example-item.json +11 -0
  73. package/skills/google-merchant-product-feeds/evals/examples/generated-title.md +3 -0
  74. package/skills/google-merchant-product-feeds/evals/examples/stable-variant-id.md +3 -0
  75. package/skills/google-merchant-product-feeds/evals/tasks.json +30 -0
  76. package/skills/google-merchant-product-feeds/references/attributes.md +39 -0
  77. package/skills/google-merchant-product-feeds/scripts/check-feed-item.mjs +92 -0
  78. package/skills/headless-nextjs-storefront/SKILL.md +69 -0
  79. package/skills/headless-nextjs-storefront/evals/examples/revalidate-stock.md +7 -0
  80. package/skills/headless-nextjs-storefront/evals/examples/server-price.md +3 -0
  81. package/skills/headless-nextjs-storefront/evals/tasks.json +30 -0
  82. package/skills/headless-nextjs-storefront/references/cache-and-cart.md +39 -0
  83. package/skills/inventory-and-multi-warehouse/SKILL.md +61 -0
  84. package/skills/inventory-and-multi-warehouse/evals/examples/reserve-last-unit.md +3 -0
  85. package/skills/inventory-and-multi-warehouse/evals/examples/split-order.md +3 -0
  86. package/skills/inventory-and-multi-warehouse/evals/tasks.json +20 -0
  87. package/skills/inventory-and-multi-warehouse/references/platforms.md +40 -0
  88. package/skills/inventory-and-multi-warehouse/scripts/allocate-stock.mjs +72 -0
  89. package/skills/iranian-payment-gateways/SKILL.md +64 -0
  90. package/skills/iranian-payment-gateways/evals/examples/toman-store.md +3 -0
  91. package/skills/iranian-payment-gateways/evals/examples/zarinpal-callback.md +3 -0
  92. package/skills/iranian-payment-gateways/evals/tasks.json +20 -0
  93. package/skills/iranian-payment-gateways/references/zarinpal-v4.md +44 -0
  94. package/skills/iranian-payment-gateways/scripts/check-verify.mjs +62 -0
  95. package/skills/marketplace-search-and-ranking/SKILL.md +93 -0
  96. package/skills/marketplace-search-and-ranking/evals/examples/index-variant.md +3 -0
  97. package/skills/marketplace-search-and-ranking/evals/examples/normalize-query.md +7 -0
  98. package/skills/marketplace-search-and-ranking/evals/tasks.json +29 -0
  99. package/skills/marketplace-search-and-ranking/references/document-and-ranking.md +56 -0
  100. package/skills/marketplace-search-and-ranking/scripts/normalize-search-text.mjs +40 -0
  101. package/skills/mcp-store-tooling/SKILL.md +49 -0
  102. package/skills/mcp-store-tooling/evals/examples/refund-tool-design.md +3 -0
  103. package/skills/mcp-store-tooling/evals/examples/remote-server-auth.md +3 -0
  104. package/skills/mcp-store-tooling/evals/tasks.json +20 -0
  105. package/skills/mcp-store-tooling/references/mcp-notes.md +67 -0
  106. package/skills/mcp-store-tooling/scripts/check-tool-definitions.mjs +69 -0
  107. package/skills/multi-vendor-payouts/SKILL.md +71 -0
  108. package/skills/multi-vendor-payouts/evals/examples/retry-payout.md +3 -0
  109. package/skills/multi-vendor-payouts/evals/examples/split-paid-order.md +3 -0
  110. package/skills/multi-vendor-payouts/evals/tasks.json +30 -0
  111. package/skills/multi-vendor-payouts/references/ledger.md +35 -0
  112. package/skills/order-and-webhook-reliability/SKILL.md +99 -0
  113. package/skills/order-and-webhook-reliability/evals/examples/dedupe-resource-version.md +3 -0
  114. package/skills/order-and-webhook-reliability/evals/examples/verify-raw-body.md +3 -0
  115. package/skills/order-and-webhook-reliability/evals/tasks.json +29 -0
  116. package/skills/order-and-webhook-reliability/references/woocommerce-webhooks.md +58 -0
  117. package/skills/order-and-webhook-reliability/scripts/verify-hmac-signature.mjs +78 -0
  118. package/skills/payment-gateway-integration/SKILL.md +90 -0
  119. package/skills/payment-gateway-integration/evals/examples/confirmed-capture.md +9 -0
  120. package/skills/payment-gateway-integration/evals/examples/replayed-webhook.md +3 -0
  121. package/skills/payment-gateway-integration/evals/tasks.json +30 -0
  122. package/skills/payment-gateway-integration/references/woocommerce-gateway.md +67 -0
  123. package/skills/product-catalog-modeling/SKILL.md +91 -0
  124. package/skills/product-catalog-modeling/assets/example-catalog.json +45 -0
  125. package/skills/product-catalog-modeling/evals/examples/create-variation.md +10 -0
  126. package/skills/product-catalog-modeling/evals/examples/sync-parent.md +7 -0
  127. package/skills/product-catalog-modeling/evals/tasks.json +29 -0
  128. package/skills/product-catalog-modeling/references/variation-model.md +71 -0
  129. package/skills/product-catalog-modeling/scripts/validate-catalog.mjs +234 -0
  130. package/skills/product-description-guardrails/SKILL.md +84 -0
  131. package/skills/product-description-guardrails/evals/examples/facts-only.md +3 -0
  132. package/skills/product-description-guardrails/evals/examples/label-generated.md +3 -0
  133. package/skills/product-description-guardrails/evals/tasks.json +30 -0
  134. package/skills/product-description-guardrails/references/claims.md +24 -0
  135. package/skills/product-description-guardrails/scripts/check-description.mjs +61 -0
  136. package/skills/returns-and-refunds/SKILL.md +55 -0
  137. package/skills/returns-and-refunds/evals/examples/refund-after-inspection.md +3 -0
  138. package/skills/returns-and-refunds/evals/examples/woocommerce-partial-refund.md +3 -0
  139. package/skills/returns-and-refunds/evals/tasks.json +20 -0
  140. package/skills/returns-and-refunds/references/rma-states.md +37 -0
  141. package/skills/rtl-and-persian-localization/SKILL.md +81 -0
  142. package/skills/rtl-and-persian-localization/evals/examples/document-direction.md +7 -0
  143. package/skills/rtl-and-persian-localization/evals/examples/rial-display.md +7 -0
  144. package/skills/rtl-and-persian-localization/evals/tasks.json +30 -0
  145. package/skills/rtl-and-persian-localization/references/persian-text.md +51 -0
  146. package/skills/rtl-and-persian-localization/scripts/normalize-persian.mjs +62 -0
  147. package/skills/shopify-app-development/SKILL.md +73 -0
  148. package/skills/shopify-app-development/evals/examples/dedupe-delivery.md +3 -0
  149. package/skills/shopify-app-development/evals/examples/verify-webhook.md +3 -0
  150. package/skills/shopify-app-development/evals/tasks.json +29 -0
  151. package/skills/shopify-app-development/references/auth-and-webhooks.md +44 -0
  152. package/skills/storefront-accessibility/SKILL.md +59 -0
  153. package/skills/storefront-accessibility/evals/examples/add-to-cart-announcement.md +3 -0
  154. package/skills/storefront-accessibility/evals/examples/checkout-errors.md +3 -0
  155. package/skills/storefront-accessibility/evals/tasks.json +20 -0
  156. package/skills/storefront-accessibility/references/wcag-checklist.md +41 -0
  157. package/skills/storefront-performance/SKILL.md +59 -0
  158. package/skills/storefront-performance/evals/examples/cache-without-leaking-carts.md +3 -0
  159. package/skills/storefront-performance/evals/examples/slow-product-lcp.md +3 -0
  160. package/skills/storefront-performance/evals/tasks.json +20 -0
  161. package/skills/storefront-performance/references/web-vitals.md +55 -0
  162. package/skills/storefront-performance/scripts/check-web-vitals.mjs +49 -0
  163. package/skills/stripe-payments/SKILL.md +72 -0
  164. package/skills/stripe-payments/evals/examples/checkout-session-delayed.md +3 -0
  165. package/skills/stripe-payments/evals/examples/mark-paid-from-webhook.md +5 -0
  166. package/skills/stripe-payments/evals/examples/partial-refund.md +3 -0
  167. package/skills/stripe-payments/evals/tasks.json +27 -0
  168. package/skills/stripe-payments/references/stripe-objects.md +54 -0
  169. package/skills/stripe-payments/scripts/verify-stripe-signature.mjs +62 -0
  170. package/skills/subscription-billing/SKILL.md +76 -0
  171. package/skills/subscription-billing/evals/examples/cancel-at-period-end.md +3 -0
  172. package/skills/subscription-billing/evals/examples/one-renewal.md +3 -0
  173. package/skills/subscription-billing/evals/tasks.json +30 -0
  174. package/skills/subscription-billing/references/states.md +27 -0
  175. package/skills/tax-and-shipping/SKILL.md +61 -0
  176. package/skills/tax-and-shipping/evals/examples/store-tax-lines.md +3 -0
  177. package/skills/tax-and-shipping/evals/examples/unknown-zone.md +3 -0
  178. package/skills/tax-and-shipping/evals/tasks.json +30 -0
  179. package/skills/tax-and-shipping/references/woocommerce.md +22 -0
  180. package/skills/vector-product-search/SKILL.md +66 -0
  181. package/skills/vector-product-search/evals/examples/reembed-on-title.md +3 -0
  182. package/skills/vector-product-search/evals/examples/sku-before-vector.md +3 -0
  183. package/skills/vector-product-search/evals/tasks.json +30 -0
  184. package/skills/vector-product-search/references/index.md +40 -0
  185. package/skills/woocommerce-plugin-development/SKILL.md +84 -0
  186. package/skills/woocommerce-plugin-development/assets/main-plugin-file.php +28 -0
  187. package/skills/woocommerce-plugin-development/evals/examples/declare-hpos.md +9 -0
  188. package/skills/woocommerce-plugin-development/evals/examples/save-order-meta.md +9 -0
  189. package/skills/woocommerce-plugin-development/evals/tasks.json +32 -0
  190. package/skills/woocommerce-plugin-development/references/hooks.md +46 -0
  191. package/skills/woocommerce-plugin-development/references/hpos.md +84 -0
  192. package/skills/woocommerce-rest-api/SKILL.md +90 -0
  193. package/skills/woocommerce-rest-api/evals/examples/https-basic-auth.md +8 -0
  194. package/skills/woocommerce-rest-api/evals/examples/idempotent-order-create.md +7 -0
  195. package/skills/woocommerce-rest-api/evals/tasks.json +30 -0
  196. package/skills/woocommerce-rest-api/references/endpoints.md +66 -0
  197. package/skills/wordpress-plugin-security/SKILL.md +121 -0
  198. package/skills/wordpress-plugin-security/evals/examples/admin-form-nonce.md +9 -0
  199. package/skills/wordpress-plugin-security/evals/examples/private-rest-route.md +9 -0
  200. package/skills/wordpress-plugin-security/evals/tasks.json +31 -0
  201. package/skills/wordpress-plugin-security/references/review-checklist.md +31 -0
@@ -0,0 +1,3 @@
1
+ The unit price is resolved on the server by one function used by the product page, the cart, and order placement. The wholesale group has a price list with two entries for the mug: `minQuantity` 1 at 9.00 and `minQuantity` 10 at 8.50.
2
+
3
+ A wholesale buyer ordering 9 mugs gets 9.00 each and sees "10 or more: 8.50 each". At 10 mugs the line is priced at 8.50 each. A guest matches no list and pays the public 11.00. The product page cache varies by group so the wholesale price is never shown to guests.
@@ -0,0 +1,19 @@
1
+ {
2
+ "tasks": [
3
+ {
4
+ "id": "tiered-wholesale",
5
+ "task": "Wholesale buyers pay 9.00 per mug, or 8.50 each from 10 mugs. Guests pay 11.00. Describe where the price is computed and what a wholesale buyer sees for 9 and for 10 mugs.",
6
+ "expects": {
7
+ "must_include": ["server", "price list", "minQuantity", "9.00", "8.50"],
8
+ "must_not_include": ["compute the price in JavaScript in the browser"]
9
+ }
10
+ },
11
+ {
12
+ "id": "net-terms",
13
+ "task": "A company account wants to pay by invoice on Net 30 terms. What checks run before the order is accepted?",
14
+ "expects": {
15
+ "must_include": ["credit limit", "open invoices", "due date"]
16
+ }
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,46 @@
1
+ # Price lists
2
+
3
+ ## Shape
4
+
5
+ ```json
6
+ {
7
+ "id": "wholesale-2026",
8
+ "currency": "EUR",
9
+ "groups": ["wholesale"],
10
+ "priority": 10,
11
+ "validFrom": "2026-01-01",
12
+ "validTo": "2026-12-31",
13
+ "entries": [
14
+ { "sku": "MUG-RED", "minQuantity": 1, "unitPrice": 9.00 },
15
+ { "sku": "MUG-RED", "minQuantity": 10, "unitPrice": 8.50 },
16
+ { "sku": "MUG-RED", "minQuantity": 50, "percentOff": 20 }
17
+ ]
18
+ }
19
+ ```
20
+
21
+ A contract is a price list with `companies` instead of `groups`.
22
+
23
+ ## Rules
24
+
25
+ | Rule | Recommendation |
26
+ | --- | --- |
27
+ | Which lists apply | Lists for the buyer's company (contracts), then the buyer's groups, valid on the order date, in the cart currency |
28
+ | Tier inside a list | Highest `minQuantity` that is at most the line quantity |
29
+ | Several lists apply | Contract first. Then the lowest unit price, or the highest priority if the store chooses that. Record the rule in one place. |
30
+ | `percentOff` base | The public regular price of the SKU |
31
+ | Rounding | Round the unit price to the currency's minor unit, then multiply |
32
+
33
+ ## Request for the resolver script
34
+
35
+ ```json
36
+ {
37
+ "sku": "MUG-RED",
38
+ "quantity": 12,
39
+ "company": "acme",
40
+ "groups": ["wholesale"],
41
+ "date": "2026-10-30",
42
+ "currency": "EUR",
43
+ "basePrice": 11.0,
44
+ "strategy": "lowest"
45
+ }
46
+ ```
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ // Resolve a B2B unit price from price lists.
3
+ // Usage: node resolve-price.mjs <request.json> <lists.json>
4
+ import { readFile } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ function roundMoney(value, decimals = 2) {
9
+ const factor = 10 ** decimals;
10
+ return Math.round(value * factor) / factor;
11
+ }
12
+
13
+ function inWindow(list, date) {
14
+ if (!date) return true;
15
+ if (list.validFrom && date < list.validFrom) return false;
16
+ if (list.validTo && date > list.validTo) return false;
17
+ return true;
18
+ }
19
+
20
+ function tierFor(list, request) {
21
+ const entries = (list.entries ?? [])
22
+ .filter((entry) => entry.sku === request.sku)
23
+ .sort((a, b) => a.minQuantity - b.minQuantity);
24
+ const eligible = entries.filter((entry) => entry.minQuantity <= request.quantity);
25
+ if (eligible.length === 0) return null;
26
+ const entry = eligible[eligible.length - 1];
27
+ const next = entries.find((candidate) => candidate.minQuantity > request.quantity) ?? null;
28
+ return { entry, next };
29
+ }
30
+
31
+ function unitPrice(entry, basePrice, decimals) {
32
+ if (typeof entry.unitPrice === "number") return roundMoney(entry.unitPrice, decimals);
33
+ if (typeof entry.percentOff === "number") {
34
+ if (typeof basePrice !== "number") throw new Error(`${entry.sku}: percentOff needs basePrice`);
35
+ return roundMoney(basePrice * (1 - entry.percentOff / 100), decimals);
36
+ }
37
+ throw new Error(`${entry.sku}: an entry needs unitPrice or percentOff`);
38
+ }
39
+
40
+ /**
41
+ * Returns { unitPrice, source, list, minQuantity, nextTier }.
42
+ * Contract lists (with `companies`) win. Otherwise `strategy` is "lowest" (default) or "priority".
43
+ */
44
+ export function resolvePrice(request, lists, { decimals = 2 } = {}) {
45
+ if (!Number.isInteger(request.quantity) || request.quantity <= 0) throw new Error("quantity must be a positive whole number");
46
+ const active = lists.filter(
47
+ (list) => inWindow(list, request.date) && (!request.currency || !list.currency || list.currency === request.currency),
48
+ );
49
+ const contracts = active.filter((list) => request.company && (list.companies ?? []).includes(request.company));
50
+ const groupLists = active.filter((list) => (list.groups ?? []).some((group) => (request.groups ?? []).includes(group)));
51
+
52
+ const candidates = [];
53
+ for (const [source, pool] of [["contract", contracts], ["group", groupLists]]) {
54
+ for (const list of pool) {
55
+ const tier = tierFor(list, request);
56
+ if (!tier) continue;
57
+ candidates.push({
58
+ unitPrice: unitPrice(tier.entry, request.basePrice, decimals),
59
+ source,
60
+ list: list.id,
61
+ priority: list.priority ?? 0,
62
+ minQuantity: tier.entry.minQuantity,
63
+ nextTier: tier.next
64
+ ? { minQuantity: tier.next.minQuantity, unitPrice: unitPrice(tier.next, request.basePrice, decimals) }
65
+ : null,
66
+ });
67
+ }
68
+ if (source === "contract" && candidates.length > 0) break;
69
+ }
70
+
71
+ if (candidates.length === 0) {
72
+ if (typeof request.basePrice !== "number") throw new Error(`${request.sku}: no price list applies and no basePrice`);
73
+ return { unitPrice: roundMoney(request.basePrice, decimals), source: "public", list: null, minQuantity: 1, nextTier: null };
74
+ }
75
+ const strategy = request.strategy ?? "lowest";
76
+ candidates.sort((a, b) =>
77
+ strategy === "priority" ? b.priority - a.priority || a.unitPrice - b.unitPrice : a.unitPrice - b.unitPrice || b.priority - a.priority,
78
+ );
79
+ const { priority: _priority, ...best } = candidates[0];
80
+ return best;
81
+ }
82
+
83
+ const isCli = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
84
+ if (isCli) {
85
+ const [requestFile, listsFile] = process.argv.slice(2);
86
+ if (!requestFile || !listsFile) {
87
+ console.error("Usage: node resolve-price.mjs <request.json> <lists.json>");
88
+ process.exit(2);
89
+ }
90
+ const request = JSON.parse(await readFile(requestFile, "utf8"));
91
+ const lists = JSON.parse(await readFile(listsFile, "utf8"));
92
+ console.log(JSON.stringify(resolvePrice(request, lists), null, 2));
93
+ }
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: catalog-import-and-migration
3
+ description: "Import, export, and migrate product catalogs with stable SKU keys, a dry run, parent-before-variation order, batched writes, and URL redirects. Use when moving a store between platforms, bulk importing CSV or JSON products, or exporting a catalog for another system."
4
+ license: MIT
5
+ compatibility: "The row checker needs Node.js 20 or newer and no packages."
6
+ metadata:
7
+ author: Naser Sadrmanesh
8
+ version: "0.3.1"
9
+ ---
10
+
11
+ # Catalog import and migration
12
+
13
+ An import is a repeatable job, not a one-off script. Run it against a copy first, make it safe to run twice, and keep a report of every row. Column maps between platforms are in [the field mapping notes](references/field-mapping.md).
14
+
15
+ ## Procedure
16
+
17
+ 1. Export the source with every field you need: products, variations, attributes, categories, images, stock, prices, SEO fields, and old URLs. Keep the raw export unchanged.
18
+ 2. Map fields to the target in one file. Normalize units, currencies, decimals, and booleans there, not in ad hoc fixes.
19
+ 3. Choose a stable key. Use the SKU when every sellable unit has a unique one; otherwise keep the source id in a dedicated field such as `meta:source_id`. Never key on the product name.
20
+ 4. Check every row before writing anything: `node scripts/check-import-rows.mjs rows.json` reports duplicate SKUs, variations without a parent, invalid prices, and negative stock.
21
+ 5. Do a dry run that reports creates, updates, and skips per row without writing.
22
+ 6. Import in order: categories and attributes, then parent products, then variations, then images and relations (upsells, bundles). A variation needs its parent's id.
23
+ 7. Write in batches (the WooCommerce batch endpoint takes up to 100 items per request) and record the target id for every source key, so a rerun updates instead of duplicating.
24
+ 8. Create 301 redirects from every old product and category URL to the new one.
25
+ 9. Compare counts and totals: products, variations, stock units, and a sample of prices.
26
+
27
+ ## Idempotent runs
28
+
29
+ Look up each row by its key before deciding create or update. Store `source key -> target id` after every batch. A crash halfway through then resumes without duplicates.
30
+
31
+ ## Images
32
+
33
+ Download source images once, deduplicate by content hash, and upload them to the target media library before linking them. Hotlinking to the old store breaks when it shuts down.
34
+
35
+ ## Exports
36
+
37
+ Export with a documented column set and UTF-8. Include ids, SKUs, and parent references so the file can be imported back. Use `.` as the decimal separator and ISO 8601 dates. Escape fields with quotes according to RFC 4180 when writing CSV.
38
+
39
+ ## Gotchas
40
+
41
+ - Spreadsheet programs strip leading zeros from SKUs and barcodes and turn long numbers into scientific notation. Treat them as text.
42
+ - Importing variations before their parent creates orphans or fails. Sort parents first.
43
+ - Prices with a comma decimal (`19,90`) parse as 1990 or fail in some importers.
44
+ - Running the import twice without a key creates every product twice.
45
+ - Missing redirects lose search rankings and break links in old emails.
46
+ - Customer passwords usually cannot move between platforms. Plan a reset email instead of copying hashes the target cannot read.
47
+
48
+ ## Verification
49
+
50
+ - The row checker reports no errors before the real run.
51
+ - A second run of the same file updates rows and creates nothing.
52
+ - Product, variation, and stock totals match the source.
53
+ - Every old URL in a sample returns 301 to the right new page.
54
+ - A variable product shows the same options and per-variation prices as the source.
55
+
56
+ ## Evaluation
57
+
58
+ Worked examples and the checks they must satisfy are in [evals/tasks.json](evals/tasks.json).
@@ -0,0 +1,3 @@
1
+ Export every old product and category URL with its source id. After the import, map each source id to the new URL and create a permanent 301 redirect for each.
2
+
3
+ Test a sample of old URLs, including ones from email campaigns and the sitemap, and check each returns 301 to the right page. Submit the new sitemap so search engines move their index to the new URLs.
@@ -0,0 +1,3 @@
1
+ Use the SKU as the key and check every row first: duplicate SKUs, variations whose `Parent` is missing, and comma decimals. Then do a dry run that reports create, update, or skip for each row without writing.
2
+
3
+ Import categories and attributes, then variable parents, then variations under each parent. Write with the batch endpoint, 100 items per request, and after each batch save the map from source key to WooCommerce id. If the job crashes, rerunning it looks up each SKU first and updates instead of creating duplicates.
@@ -0,0 +1,20 @@
1
+ {
2
+ "tasks": [
3
+ {
4
+ "id": "rerunnable-import",
5
+ "task": "Write the plan for importing 20,000 products with variations from a CSV into WooCommerce so that the job can crash and be run again safely.",
6
+ "expects": {
7
+ "must_include": ["SKU", "dry run", "Parent", "batch", "source key"],
8
+ "must_not_include": ["match products by name"]
9
+ }
10
+ },
11
+ {
12
+ "id": "platform-migration-urls",
13
+ "task": "A store moves from one platform to another and the product URLs change. What keeps search rankings and old links working?",
14
+ "expects": {
15
+ "must_include": ["301", "redirect"],
16
+ "must_not_include": ["302"]
17
+ }
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,43 @@
1
+ # Field mapping
2
+
3
+ ## WooCommerce CSV importer columns
4
+
5
+ The built-in product CSV importer reads these columns, among others.
6
+
7
+ | Column | Notes |
8
+ | --- | --- |
9
+ | `ID` | Existing product id. Leave empty to create. |
10
+ | `Type` | `simple`, `variable`, `variation`, `grouped`, `external` |
11
+ | `SKU` | Unique per product and variation. Used to match existing products when "Update existing products" is checked. |
12
+ | `Name` | Product name. Variations can leave it empty. |
13
+ | `Parent` | For a variation: the parent's SKU, or `id:123` |
14
+ | `Regular price`, `Sale price` | Decimal with `.` |
15
+ | `Stock`, `In stock?` | Quantity, and `1` or `0` |
16
+ | `Categories` | Comma-separated, `>` for hierarchy: `Clothing > Shirts` |
17
+ | `Images` | Comma-separated URLs. The first is the main image. |
18
+ | `Attribute 1 name`, `Attribute 1 value(s)`, `Attribute 1 visible`, `Attribute 1 global` | Variable products list all values. Variations list their one value. |
19
+ | `Meta: _custom_key` | Custom meta |
20
+
21
+ For more control, use the REST API: create the parent with `POST products`, then variations with `POST products/{id}/variations/batch`.
22
+
23
+ ## Common platform mappings
24
+
25
+ | Concept | WooCommerce | Shopify | Generic JSON |
26
+ | --- | --- | --- | --- |
27
+ | Product | product | product | `product` |
28
+ | Sellable unit | variation | variant | `variant` |
29
+ | Option | attribute | option (up to 3 per product) | `options` |
30
+ | Price | `regular_price` | `price` | `price` |
31
+ | Compare-at price | `regular_price` with a `sale_price` | `compareAtPrice` | `listPrice` |
32
+ | Stock | `stock_quantity` | inventory level per location | `stock` |
33
+ | Handle or slug | `slug` | `handle` | `slug` |
34
+
35
+ Shopify allows at most three options per product, and a limited number of variants per product, so a WooCommerce product with four attributes must be split or flattened before migrating.
36
+
37
+ ## Report file
38
+
39
+ Write one line per source row:
40
+
41
+ ```json
42
+ {"key":"TEE-BLUE-M","action":"update","targetId":5123,"warnings":[]}
43
+ ```
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ // Check catalog rows before an import.
3
+ // Usage: node check-import-rows.mjs <rows.json>
4
+ // rows.json: [{ "sku": "TEE", "type": "variable", "name": "Tee" }, { "sku": "TEE-M", "type": "variation", "parent": "TEE", "regularPrice": "19.90", "stock": 4 }]
5
+ import { readFile } from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const TYPES = new Set(["simple", "variable", "variation", "grouped", "external"]);
10
+ const PRICE = /^\d+(?:\.\d{1,4})?$/;
11
+
12
+ /** Returns a list of problems, one string per problem, with the row number (1-based). */
13
+ export function checkImportRows(rows) {
14
+ if (!Array.isArray(rows)) return ["rows must be an array"];
15
+ const errors = [];
16
+ const seen = new Map();
17
+ const parents = new Map();
18
+ rows.forEach((row, index) => {
19
+ const label = `row ${index + 1}`;
20
+ if (!row || typeof row !== "object") {
21
+ errors.push(`${label}: must be an object`);
22
+ return;
23
+ }
24
+ const type = row.type ?? "simple";
25
+ if (!TYPES.has(type)) errors.push(`${label}: unknown type ${type}`);
26
+ const sku = typeof row.sku === "string" ? row.sku.trim() : "";
27
+ if (sku === "") {
28
+ errors.push(`${label}: missing sku`);
29
+ } else if (seen.has(sku)) {
30
+ errors.push(`${label}: duplicate sku ${sku} (first on row ${seen.get(sku)})`);
31
+ } else {
32
+ seen.set(sku, index + 1);
33
+ if (type === "variable") parents.set(sku, index + 1);
34
+ }
35
+ if (typeof row.sku === "number") errors.push(`${label}: sku must be text, not a number`);
36
+ if (type !== "variation" && (typeof row.name !== "string" || row.name.trim() === "")) errors.push(`${label}: missing name`);
37
+ for (const field of ["regularPrice", "salePrice"]) {
38
+ if (row[field] === undefined || row[field] === "") continue;
39
+ if (!PRICE.test(String(row[field]))) errors.push(`${label}: ${field} "${row[field]}" must use digits and a . decimal`);
40
+ }
41
+ if (row.salePrice && row.regularPrice && Number(row.salePrice) >= Number(row.regularPrice)) {
42
+ errors.push(`${label}: salePrice must be lower than regularPrice`);
43
+ }
44
+ if (row.stock !== undefined && (!Number.isInteger(row.stock) || row.stock < 0)) {
45
+ errors.push(`${label}: stock must be a whole number of 0 or more`);
46
+ }
47
+ });
48
+ rows.forEach((row, index) => {
49
+ if (row?.type !== "variation") return;
50
+ const label = `row ${index + 1}`;
51
+ if (!row.parent) {
52
+ errors.push(`${label}: variation without parent`);
53
+ } else if (!parents.has(row.parent)) {
54
+ errors.push(`${label}: parent ${row.parent} is not a variable product in this file`);
55
+ } else if (parents.get(row.parent) > index + 1) {
56
+ errors.push(`${label}: parent ${row.parent} comes after its variation; sort parents first`);
57
+ }
58
+ });
59
+ return errors;
60
+ }
61
+
62
+ const isCli = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
63
+ if (isCli) {
64
+ const [file] = process.argv.slice(2);
65
+ if (!file) {
66
+ console.error("Usage: node check-import-rows.mjs <rows.json>");
67
+ process.exit(2);
68
+ }
69
+ const errors = checkImportRows(JSON.parse(await readFile(file, "utf8")));
70
+ if (errors.length > 0) {
71
+ for (const error of errors) console.error(`- ${error}`);
72
+ process.exit(1);
73
+ }
74
+ console.log("Rows look importable.");
75
+ }
@@ -0,0 +1,29 @@
1
+ woocommerce-plugin-development
2
+ woocommerce-rest-api
3
+ wordpress-plugin-security
4
+ payment-gateway-integration
5
+ product-catalog-modeling
6
+ marketplace-search-and-ranking
7
+ checkout-ux-and-conversion
8
+ rtl-and-persian-localization
9
+ order-and-webhook-reliability
10
+ ecommerce-seo-structured-data
11
+ multi-vendor-payouts
12
+ shopify-app-development
13
+ headless-nextjs-storefront
14
+ google-merchant-product-feeds
15
+ tax-and-shipping
16
+ subscription-billing
17
+ fraud-and-risk-basics
18
+ vector-product-search
19
+ product-description-guardrails
20
+ conversion-event-tracking
21
+ stripe-payments
22
+ iranian-payment-gateways
23
+ inventory-and-multi-warehouse
24
+ returns-and-refunds
25
+ b2b-pricing
26
+ catalog-import-and-migration
27
+ storefront-accessibility
28
+ storefront-performance
29
+ mcp-store-tooling
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: checkout-ux-and-conversion
3
+ description: "Design checkout so the full cost is visible before payment, guest purchase works, and field or payment errors can be corrected without losing the cart. Use when changing the checkout flow, address form, payment choice, or order confirmation page."
4
+ license: MIT
5
+ metadata:
6
+ author: Naser Sadrmanesh
7
+ version: "0.3.1"
8
+ ---
9
+
10
+ # Checkout UX and conversion
11
+
12
+ Use this skill when changing the checkout a shopper sees. Payment capture rules live in the payment skill. Persian and RTL layout lives in the localization skill. Field-level review questions are in [the checkout review](references/checkout-review.md).
13
+
14
+ This skill does not claim a conversion rate. It states conditions the checkout has to meet so a shopper can finish an order they already decided to place.
15
+
16
+ ## Procedure
17
+
18
+ 1. Show merchandise, discounts, shipping, tax, and the amount that will be charged before the pay control.
19
+ 2. Allow a guest purchase. Offer account creation after the order, or as an optional choice that does not block payment.
20
+ 3. Validate fields inline, next to the field, in the shopper's language. Move focus to the first error.
21
+ 4. Keep the cart and the entered address when payment is declined.
22
+ 5. Disable the pay control after the first accepted submit, and make the order request idempotent so a double submit creates one order.
23
+ 6. Confirm with an order number and a plain statement of what was charged. Do not offer a second pay control on that page.
24
+
25
+ ## Cost
26
+
27
+ - Shipping method and shipping cost are chosen before payment. If the address changes the cost, update the total before the shopper pays.
28
+ - A fee or payment surcharge is a line in the total, not a surprise on the provider's page.
29
+ - The currency is visible. If you display a second currency, the charged currency is the one in the pay button label.
30
+ - Coupon entry does not clear the address or the selected shipping method.
31
+
32
+ ## Address and contact
33
+
34
+ - Ask for the fields the order needs. Drop a field rather than marking it optional and ignoring it.
35
+ - Use autocomplete attributes from the HTML specification: `given-name`, `family-name`, `address-line1`, `address-line2`, `address-level1`, `address-level2`, `postal-code`, `country`, `email`, `tel`.
36
+ - Set input types: `email`, `tel`, and an appropriate keyboard for the postal code. Do not force a North American phone mask on other countries.
37
+ - When the country changes which fields are required, update the form before submit. WooCommerce does this through country locale settings. Do not keep a second hard-coded field list that disagrees with `woocommerce_get_country_locale`.
38
+ - The country control comes before fields whose labels and requirements depend on it.
39
+
40
+ ## Payment choice
41
+
42
+ - List only methods the shop can capture and refund.
43
+ - Each method shows its name and, when it adds a cost, that cost.
44
+ - Card entry uses the provider's hosted fields or a redirect. The merchant page does not collect PAN or CVV.
45
+ - The primary control states the action and the amount, for example "Pay 19.00 USD". A generic "Submit" is not enough when more than one control is on the page.
46
+ - Express wallets, when you offer them, create the same server-side order and idempotency path as the regular form.
47
+
48
+ ## Errors
49
+
50
+ - Field errors name the field and how to fix it. Do not rely on color alone.
51
+ - A payment decline says that the payment was not taken and that the shopper can try another method. Do not say the order was placed.
52
+ - A network timeout says the outcome is unknown and points to the order status or a retrieve. Do not immediately start a second charge. Reuse the same idempotency key.
53
+ - Keep the shopper's typed values. Re-render the form with those values and the error.
54
+
55
+ ## Confirmation
56
+
57
+ - Show the order number, the charged total, and the shipping destination.
58
+ - Send the receipt email once, from the paid transition, not from the browser page load.
59
+ - A reload of the confirmation page does not place another order.
60
+
61
+ ## Mobile and accessibility
62
+
63
+ - One column when the viewport is narrow. The total stays visible near the pay control.
64
+ - Tap targets are large enough to hit without zooming. Error text is not only a tooltip.
65
+ - Every control has a label. Errors use `aria-invalid` and are referenced from the field.
66
+ - Focus order follows the visual order, including in RTL layouts.
67
+
68
+ ## Gotchas
69
+
70
+ - Hiding shipping cost until after the pay button is the usual reason a shopper abandons at the last step. Show it earlier.
71
+ - Creating the account, and requiring the shopper to leave checkout to verify email, loses the order in progress. Save the order first.
72
+ - A pay button that stays enabled can create two pending orders. Disable it and dedupe on the server.
73
+ - Validation that only runs in the browser will be skipped. Repeat every rule on the server.
74
+ - Third-party badge images do not replace a real returns or shipping policy link.
75
+
76
+ ## Verification
77
+
78
+ - A guest can pay without creating a password.
79
+ - Changing the address updates shipping and the pay-button amount before payment.
80
+ - A declined payment preserves the cart and the address.
81
+ - Submitting twice creates one order and charges once.
82
+ - Keyboard-only use reaches every field, the error, and the pay control. In a narrow viewport the total is still visible with the pay control.
83
+
84
+ ## Evaluation
85
+
86
+ Worked examples and the checks they must satisfy are in [evals/tasks.json](evals/tasks.json).
@@ -0,0 +1,3 @@
1
+ Show the full cost and allow a guest purchase.
2
+
3
+ The shipping method and tax are visible before the pay control. A guest can pay. Account creation is offered after the order, not as a gate.
@@ -0,0 +1,3 @@
1
+ After the first accepted submit, disable the pay control and send a stable idempotency key for that attempt.
2
+
3
+ The server returns the original order when the same key is retried. The cart remains if the provider declines.
@@ -0,0 +1,30 @@
1
+ {
2
+ "tasks": [
3
+ {
4
+ "id": "guest-total",
5
+ "task": "Let a guest see shipping and the charged total before paying, without creating a password first.",
6
+ "expects": {
7
+ "must_include": [
8
+ "guest",
9
+ "shipping"
10
+ ],
11
+ "must_not_include": [
12
+ "password is required"
13
+ ]
14
+ }
15
+ },
16
+ {
17
+ "id": "single-submit",
18
+ "task": "Stop a double click on the pay control from creating two orders for one cart.",
19
+ "expects": {
20
+ "must_include": [
21
+ "idempotency",
22
+ "disable"
23
+ ],
24
+ "must_not_include": [
25
+ "Math.random()"
26
+ ]
27
+ }
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,41 @@
1
+ # Checkout review
2
+
3
+ Walk a real order in the browser. Record pass or fail. Do not treat a screenshot of the empty form as a review.
4
+
5
+ ## Before payment
6
+
7
+ - Guest checkout is available, or the shop has a written reason it is not.
8
+ - Line items, discounts, shipping, tax, and total match what the pay control will charge.
9
+ - The shipping method can be changed without retyping the address.
10
+ - A coupon or gift card failure leaves the rest of the form intact.
11
+ - The pay button names the amount and currency.
12
+
13
+ ## Address
14
+
15
+ - Required fields match the selected country.
16
+ - Autocomplete attributes are set on contact and address fields.
17
+ - Phone and postal code inputs do not apply a mask from a different country.
18
+ - Error text is next to the field and is announced to assistive technology.
19
+
20
+ ## Payment
21
+
22
+ - Only live, refundable methods are listed.
23
+ - Card data is entered in hosted fields or on the provider page.
24
+ - The first submit disables the pay control.
25
+ - A second submit does not create a second order.
26
+ - A decline keeps the cart.
27
+
28
+ ## After payment
29
+
30
+ - The confirmation page shows the order number and the charged total.
31
+ - Reloading confirmation does not charge again.
32
+ - The receipt email is sent once.
33
+ - The order is unpaid until the payment skill's server-side confirmation runs. The confirmation page does not itself mark the order paid.
34
+
35
+ ## RTL
36
+
37
+ - Totals align to the correct side for the document direction.
38
+ - The pay control is in the focus order a shopper expects for that direction.
39
+ - Payment marks and card logos are not mirrored.
40
+
41
+ Use the Persian and RTL skill for the layout rules behind these checks.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: conversion-event-tracking
3
+ description: "Send commerce analytics events with stable names, item ids, and a purchase event that fires once from a confirmed order. Use when adding or changing view_item, add_to_cart, begin_checkout, purchase, or refund tracking."
4
+ license: MIT
5
+ compatibility: "The event checker requires Node.js 20 or newer. No network access."
6
+ metadata:
7
+ author: Naser Sadrmanesh
8
+ version: "0.3.1"
9
+ ---
10
+
11
+ # Conversion event tracking
12
+
13
+ Use this skill for measurement events. Checkout behavior is the checkout skill. Firing `purchase` before the payment skill confirms the order will count money you did not take.
14
+
15
+ Event names and parameter rules below follow Google's GA4 recommended ecommerce events. Other analytics tools should use the same payload shape in this project so there is one server-side record. Re-check GA4's event reference if a report disagrees with a parameter.
16
+
17
+ ## Procedure
18
+
19
+ 1. Use the recommended names: `view_item`, `add_to_cart`, `begin_checkout`, `add_shipping_info`, `add_payment_info`, `purchase`, and `refund`.
20
+ 2. Send `items` with `item_id` (the SKU or stable unit id) and `item_name`. Include `price` and `quantity` when you send `value`.
21
+ 3. Set `value` to the sum of `price * quantity`. Do not include shipping or tax in `value`. Send those as `shipping` and `tax` on `purchase`.
22
+ 4. If `value` is set, set `currency` to the order's ISO 4217 code.
23
+ 5. Fire `purchase` once, from the server-confirmed payment, with a non-empty `transaction_id` equal to the order number.
24
+ 6. Respect the shop's consent settings before loading advertising tags. Do not describe that as a universal legal rule. The shop's policy decides where consent is required.
25
+
26
+ Parameter checks are in [the event notes](references/events.md). Run `node scripts/check-event.mjs --file event.json`. Worked examples and checks are in [evals/tasks.json](evals/tasks.json).
27
+
28
+ ## Purchase
29
+
30
+ `transaction_id` is required for `purchase`. GA4 deduplicates purchases that share a transaction id on web streams. An empty string is a bad id: Google documents that purchases with `transaction_id=""` are deduplicated together. Never send an empty id.
31
+
32
+ The thank-you page may render the event only if a reload will send the same `transaction_id`. Prefer sending `purchase` from the server when payment is confirmed, and treat the browser event as a duplicate of that id rather than a second order.
33
+
34
+ `value` excludes tax and shipping. A 10.00 item, 1.00 tax, and 2.00 shipping is `value: 10`, `tax: 1`, `shipping: 2`, not `value: 13`.
35
+
36
+ ## Items
37
+
38
+ - `item_id` is the purchasable unit, not only the parent, when variants have different prices.
39
+ - `quantity` is an integer greater than zero for a purchase line.
40
+ - Do not put the customer's name, email, or phone on the item or the event.
41
+
42
+ ## Consent and duplicates
43
+
44
+ - If the shop requires consent for advertising measurement, do not load those tags before the choice is stored.
45
+ - Order-state analytics the merchant runs on their own server can still record the paid order. Keep that separate from a browser advertising tag.
46
+ - `refund` uses the same `transaction_id` as the purchase and the refunded `items` or value. Do not reuse a purchase event to mean a refund.
47
+
48
+ ## Gotchas
49
+
50
+ - Firing `purchase` on button click counts failed payments.
51
+ - A new transaction id on every page load defeats deduplication and over-counts.
52
+ - Summing tax into `value` overstates item revenue in GA4's definition.
53
+ - Debug views that use a constant transaction id will collapse real orders together if that code ships.
54
+
55
+ ## Verification
56
+
57
+ - The checker accepts a purchase whose `value` equals the item sum and rejects a purchase with an empty `transaction_id`.
58
+ - A declined payment does not emit `purchase`.
59
+ - Reloading the confirmation page does not create a second transaction id.
60
+ - Item ids on variant products are variant SKUs.
61
+
62
+ ## Evaluation
63
+
64
+ Worked examples and the checks they must satisfy are in [evals/tasks.json](evals/tasks.json).
@@ -0,0 +1,3 @@
1
+ Send purchase from the confirmed order.
2
+
3
+ `transaction_id` is `1042`. `value` is the sum of item price times quantity. Tax and shipping are separate fields. A reload uses the same transaction id.
@@ -0,0 +1,3 @@
1
+ Fire `add_to_cart` with `item_id` `SHIRT-BLUE` and the variant price.
2
+
3
+ Do not emit purchase from the button click. Purchase waits for payment confirmation.