@sadrmanesh/commerce-agent-skills 0.0.0-stage → 0.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 (201) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -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,12 @@
1
+ # Checkout UX and conversion
2
+
3
+ - Show shipping, tax, discounts, and the charged total before the pay control. Update that total when the address or shipping method changes.
4
+ - Allow guest checkout unless the shop has a written reason not to. Do not force account creation before payment.
5
+ - Validate on the server. Show field errors next to the fields and move focus to the first error.
6
+ - Keep the cart and the typed address when a payment is declined.
7
+ - Make submit idempotent and disable the pay control after the first accepted click.
8
+ - Label the pay control with the action, amount, and currency.
9
+ - Collect card data only in the provider's hosted fields or redirect.
10
+ - Use HTML autocomplete attributes and input types that match the country. Do not force another country's phone or postal-code mask.
11
+ - Confirm with an order number. A reload of the confirmation page must not create or charge an order.
12
+ - Repeat every check in a narrow viewport and with a keyboard. RTL layouts follow the document direction.
@@ -0,0 +1,11 @@
1
+ # Conversion event tracking
2
+
3
+ - Use the event names `view_item`, `add_to_cart`, `begin_checkout`, `add_shipping_info`, `add_payment_info`, `purchase`, and `refund`.
4
+ - Send `items` with a stable `item_id` for the purchasable unit.
5
+ - Set `value` to the sum of item price times quantity. Exclude tax and shipping. Send those as their own purchase fields.
6
+ - Require an ISO currency whenever `value` is set.
7
+ - Fire `purchase` only after payment is confirmed. `transaction_id` is the order number and must be non-empty.
8
+ - Reuse that `transaction_id` on reload and on `refund`. Do not mint a new id per page view.
9
+ - Do not put customer name, email, or phone on the event.
10
+ - Follow the shop's consent settings before loading advertising tags.
11
+ - Run `scripts/check-event.mjs` on the payload. It does not call an analytics service.
@@ -0,0 +1,11 @@
1
+ # Ecommerce SEO structured data
2
+
3
+ - Emit one Product node that matches the visible product. Do not add a second Product JSON-LD block on a WooCommerce page that already prints one.
4
+ - Prefer the `woocommerce_structured_data_product` filter on WooCommerce. On a custom storefront, render JSON-LD in the server HTML.
5
+ - Use a JSON number for `price` and an ISO 4217 `priceCurrency`. Do not put currency symbols or Persian digits in the price.
6
+ - A variant page marks up that variant's price, SKU, and availability. Do not mark a sold-out unit as `InStock`.
7
+ - Merchant listings need `name`, `image`, and one `Offer` with a price greater than zero. Product snippets can use `AggregateOffer` for a visible price range.
8
+ - If both `offers.price` and `priceSpecification` are set, the offer price is the one Google uses. Keep it positive for a merchant listing.
9
+ - Set `availability` from the unit's stock status. Map WooCommerce `onbackorder` to `BackOrder`.
10
+ - Keep `name` and `description` in the page language. Keep `hreflang` and canonical URLs consistent for translated pages.
11
+ - Run `scripts/check-product-jsonld.mjs` for the `merchant` or `snippet` profile before shipping the markup.
@@ -0,0 +1,11 @@
1
+ # Fraud and risk basics
2
+
3
+ - Let the payment provider collect card data. Do not store PAN or CVV. Store the provider payment id and result codes.
4
+ - Treat AVS, CVV, and 3-D Secure results as signals. Do not treat a match as proof or a mismatch as automatic fraud.
5
+ - Do not capture while 3-D Secure is still pending.
6
+ - Put high-amount AVS or CVV mismatches and velocity spikes into a review queue. Do not auto-capture the queue on a timer.
7
+ - Ignore a risk flag supplied by the browser.
8
+ - A payment amount or currency mismatch is not captured.
9
+ - Velocity keys use account, address hash, or provider customer id. They do not use a card number.
10
+ - Review decisions record the reviewer and call the provider idempotently for capture or void.
11
+ - Keep the cart and leave the order unpaid when the provider declines. Do not retry the charge in a loop.
@@ -0,0 +1,11 @@
1
+ # Google Merchant product feeds
2
+
3
+ - Emit one item per purchasable unit. Keep `id` stable and at most 50 characters. Share `item_group_id` across variants.
4
+ - Require `id`, `title` or `structured_title`, `description` or `structured_description`, `link`, `image_link`, `availability`, and `price`.
5
+ - Write `price` as major units with a period and an ISO code, such as `15.00 USD`. Availability is `in_stock`, `out_of_stock`, `preorder`, or `backorder`.
6
+ - Send `availability_date` for `preorder` and `backorder`.
7
+ - Match the landing page title, description, price, and availability. Do not put promotional text in the title.
8
+ - Put generated titles and descriptions in the structured attribute with `digital_source_type` `trained_algorithmic_media`.
9
+ - Submit a real GTIN when the product has one. Do not invent digits.
10
+ - Use images of at least 500 by 500 pixels. Merchant Center's stated enforcement date for that minimum is 31 January 2027.
11
+ - Run `scripts/check-feed-item.mjs` before export. It does not call Google.
@@ -0,0 +1,10 @@
1
+ # Headless Next.js storefront
2
+
3
+ - Render catalog price and availability in server HTML. Do not require client JavaScript for the price a crawler should see.
4
+ - Keep WooCommerce consumer secrets and Shopify Admin tokens in server environment variables. Do not import them from a client component.
5
+ - Use the public storefront API for carts. WooCommerce carts use the Store API, not `wc/v3`.
6
+ - Store the cart token in an HTTP-only cookie set by a Next.js route. Do not put it in `localStorage` when possession of the token changes the cart.
7
+ - Cache product fetches with a product tag. Do not cache cart responses on a shared public key.
8
+ - Revalidate the product tag from the server when a verified catalog webhook reports a price or stock change.
9
+ - Charge the total returned by the commerce server at checkout, not a total held only in React state.
10
+ - Format visible money for the locale, and keep machine amounts in ASCII on the request you send to payment.
@@ -0,0 +1,10 @@
1
+ # Inventory and multi-warehouse
2
+
3
+ - Key stock by SKU or variation and location.
4
+ - Keep `on_hand`, `reserved`, and safety stock. Sell only from `available`.
5
+ - Change stock through movement rows with a kind, quantity, and reference. Do not overwrite totals.
6
+ - Reserve with one conditional update or a row lock. Never read and then write in separate steps.
7
+ - Reservations expire. Release them on failed payment or timeout.
8
+ - Allocate to one location when it can fill the whole order. Split across the fewest locations otherwise.
9
+ - Stock in transit is not available at either end of a transfer.
10
+ - Returns go to inspection before they become available again.
@@ -0,0 +1,10 @@
1
+ # Iranian payment gateways
2
+
3
+ - Create the order unpaid and store the gateway `authority` before redirecting.
4
+ - Find the order on callback by `Authority` from your own record, not by an order id in the query.
5
+ - `Status=NOK` means failed or canceled. Do not call verify.
6
+ - `Status=OK` is not proof. Verify from the server with the same `amount` and `authority`.
7
+ - Verify code `100` marks the order paid once. Code `101` means already verified: no second receipt or stock change.
8
+ - One Toman (IRT) is ten Rial (IRR). Convert in one function and send the unit the gateway expects.
9
+ - Keep `merchant_id` on the server and out of logs.
10
+ - Reconcile unverified payments with a job, so paid shoppers are not refunded by the network.
@@ -0,0 +1,10 @@
1
+ # Marketplace search and ranking
2
+
3
+ - Index each purchasable unit. A variant with its own price or stock is its own document, with parent title and brand copied on.
4
+ - Normalize query text and indexed text the same way: Persian Yeh and Kaf, ASCII digits, ZWNJ removed for matching, bidi marks stripped.
5
+ - Do not stem Persian with an English analyzer, or English with a Persian analyzer.
6
+ - Filter facets by stable value ids. Localize labels. Multi-select inside one facet is OR unless the UI says otherwise. Across facets, combine with AND.
7
+ - An exact SKU or GTIN match outranks a fuzzy title match. In-stock units outrank out-of-stock units when both match.
8
+ - Mark sponsored placements in the UI. Do not fold them into the organic score without a label.
9
+ - Page with a keyset and a stable tiebreaker (`unit_id`), not a deep offset.
10
+ - Reindex from the catalog write path. A deleted variant must leave the index.
@@ -0,0 +1,11 @@
1
+ # MCP store tooling
2
+
3
+ - One tool per job, named `verb_noun`. Never a free-form REST passthrough.
4
+ - Set `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint` honestly. They are hints, so enforce scopes on the server too.
5
+ - Every tool has an object `inputSchema` with bounds; structured results use `outputSchema` and `structuredContent`.
6
+ - Report tool failures with `isError: true` and an actionable message.
7
+ - Default to read-only. Require a scope per write area.
8
+ - Refunds, deletes, and bulk changes take `dryRun` and need `confirm: true`.
9
+ - Cap list page sizes and return a way to get the next page.
10
+ - Redact personal data without the customer scope. Keep API keys out of output and logs.
11
+ - Remote servers use Streamable HTTP, check `Origin`, and use OAuth with RFC 9728 metadata.
@@ -0,0 +1,11 @@
1
+ # Multi-vendor payouts
2
+
3
+ - Put a `vendor_id` on every sellable line. Do not pay a line that has no vendor.
4
+ - Record commission as ledger entries. Do not rewrite the line price to remove the fee.
5
+ - Write payable entries only after the shopper's payment is confirmed. Authorization is not a payout.
6
+ - Hold entries until the stored refund window or delivery rule. Upcoming balance is not the payout sum.
7
+ - Stamp `payout_id` on entries in the same transaction that creates the batch. Retry the provider with the batch idempotency key.
8
+ - A refund adds reversing entries. Do not delete the original sale entries.
9
+ - Keep tax and shipping in their own entries, credited to the party the marketplace named.
10
+ - Sum the ledger at payout time. Do not also sum the original order totals.
11
+ - Record who is merchant of record. Do not assume it is the vendor.
@@ -0,0 +1,12 @@
1
+ # Order and webhook reliability
2
+
3
+ - Verify webhook signatures against the raw body with a constant-time compare, before trusting the JSON.
4
+ - Persist the event in a unique inbox, then return 2xx. A delivery attempt id is not an idempotency key.
5
+ - Dedupe WooCommerce webhooks on resource id, topic, and `date_modified_gmt`. `X-WC-Webhook-Delivery-ID` changes every attempt.
6
+ - Fetch the order or payment when the event is out of order or incomplete. Do not apply an older status over a newer one.
7
+ - Compare paid amounts in integer minor units.
8
+ - Make the paid transition idempotent so stock and receipt email run once.
9
+ - Write outbound webhooks from an outbox in the same transaction as the order change.
10
+ - Acknowledge a WooCommerce activation ping with 2xx and do not create an order from it.
11
+ - Return a retryable error only when the event was not stored. WooCommerce disables a webhook after more than five consecutive failures.
12
+ - Serve the handler on the exact delivery URL. WooCommerce does not follow redirects when it posts the body.
@@ -0,0 +1,12 @@
1
+ # Payment gateway integration
2
+
3
+ - Keep card numbers and CVV on the payment provider. Do not post or store them on the merchant server.
4
+ - Create the shop order in an unpaid status before redirecting the shopper.
5
+ - Send a stable idempotency key for each payment attempt, and reuse it when retrying that same attempt.
6
+ - Mark an order paid only from a signed webhook or a server-side retrieve of the provider payment. Ignore success flags on the browser return URL.
7
+ - Compare the paid amount and currency in integer minor units. Reject a mismatch.
8
+ - Make the paid transition idempotent. A second notice for the same provider payment id must not capture, email, or reduce stock again.
9
+ - Map provider states in one place. Unknown states stay unchanged and are logged.
10
+ - Create real refund records through the provider API. Do not only change the order status string.
11
+ - In WooCommerce, confirm capture with `$order->payment_complete( $transaction_id )`. Use `on-hold` when money is not confirmed. Do not write `_transaction_id` with `add_post_meta()`.
12
+ - Register a Checkout block payment method before declaring the gateway ready for block checkout.
@@ -0,0 +1,11 @@
1
+ # Product catalog modeling
2
+
3
+ - Treat each variant as the purchasable unit. A variable parent does not have its own SKU or price.
4
+ - Keep SKUs unique across simple products and variants.
5
+ - Define variation attributes as a closed option list. Each variant sets every variation attribute, and no two variants share a combination.
6
+ - Store prices as decimal strings in major units with an ISO 4217 currency. Do not persist binary floats.
7
+ - Put stock on the purchasable unit. Managed stock has a quantity. Unmanaged stock has a status and a null quantity.
8
+ - Do not maintain a second parent quantity that can disagree with its variants.
9
+ - Keep attribute option codes stable and translate only the display label.
10
+ - Validate the catalog document with `scripts/validate-catalog.mjs` before importing it.
11
+ - After writing WooCommerce variations, call `WC_Product_Variable::sync()` so the parent price range and stock status match the children.
@@ -0,0 +1,10 @@
1
+ # Product description guardrails
2
+
3
+ - Generate copy only from the source record: name, price, currency, locale, and listed facts.
4
+ - Omit a material, dimension, origin, or certification that is not in the facts. Do not substitute a typical value.
5
+ - State the price only as the source amount and ISO currency, or leave the price out.
6
+ - Refuse cure, clinically proven, guaranteed to, 100% effective, and risk-free claims unless that phrase is in `allowedClaims`.
7
+ - Write the description in the requested locale. Do not add facts while translating.
8
+ - Label generated Merchant Center text with `digital_source_type` `trained_algorithmic_media`.
9
+ - Keep titles within 150 characters and descriptions within 5000.
10
+ - Run `scripts/check-description.mjs`. A pass does not prove the description is accurate.
@@ -0,0 +1,10 @@
1
+ # Returns and refunds
2
+
3
+ - Keep each return as its own record (RMA) linked to the order, with lines, reason, and state.
4
+ - Check the return window, returnable flags, and already returned quantities on the server.
5
+ - Refund from the inspection result, not from the request.
6
+ - Restock only resellable units, as a `return` stock movement. Quarantine damaged units.
7
+ - Refund through the original payment method with an idempotency key. Store the provider refund id.
8
+ - Never refund more than was captured. Check earlier refunds inside the transaction.
9
+ - Refund tax in proportion. Refund shipping only when the policy says so.
10
+ - A partial refund keeps the order paid for the rest.
@@ -0,0 +1,11 @@
1
+ # RTL and Persian localization
2
+
3
+ - Set Persian pages to `lang="fa"` and `dir="rtl"`. In WordPress, rely on the `fa_IR` locale and `is_rtl()`.
4
+ - Use CSS logical properties (`margin-inline-start`, `text-align: start`). Do not fix layout by rewriting `left` and `right` in a copied stylesheet.
5
+ - Mirror back and forward icons. Do not mirror logos, payment marks, product photos, or numbers.
6
+ - Fold Arabic Yeh and Kaf to Persian Yeh and Kaf on input. Keep ZWNJ in stored and displayed text. Remove ZWNJ only in search tokens.
7
+ - Keep ASCII digits in APIs, SKUs, JSON, and payment amounts. Persian digits are a display format. Accept Arabic-Indic digits on input.
8
+ - Store WooCommerce order dates as Gregorian timestamps. A Jalali calendar is display-only.
9
+ - Use ISO `IRR` for rials. If the shop charges in toman, store one unit and label it. Do not divide by 10 only in the theme.
10
+ - Format prices with `wc_price()`. Translate strings with gettext placeholders, not concatenation.
11
+ - Load a font that covers Arabic script. Do not run digit conversion across an entire HTML document.
@@ -0,0 +1,10 @@
1
+ # Shopify app development
2
+
3
+ - Configure the API version and app-specific webhooks in `shopify.app.toml`. Do not hard-code an API version in a handler.
4
+ - Authenticate before trusting a shop domain. Embedded admin requests need a validated session token. App proxy signatures do not authorize shop changes.
5
+ - Use the GraphQL Admin API for new admin work, with the narrowest scope that mutation needs.
6
+ - Verify HTTPS webhooks with HMAC-SHA256 of the raw body and the app client secret. The header is `X-Shopify-Hmac-Sha256`. Compare header names case-insensitively.
7
+ - Dedupe webhook retries with `X-Shopify-Webhook-Id`. Do not trust topic or shop headers before the HMAC matches.
8
+ - Read GraphQL `userErrors` even when the HTTP status is 200.
9
+ - Keep the previous client secret only for the rotation window Shopify documents, up to an hour, and do not log tokens or secrets.
10
+ - Reject shop domains that are not anchored to one `myshopify.com` host.
@@ -0,0 +1,11 @@
1
+ # Storefront accessibility
2
+
3
+ - Target WCAG 2.2 AA from search to order confirmation.
4
+ - Use native `<button>`, `<a href>`, `<select>`, radio groups, and `<fieldset>` with `<legend>`.
5
+ - Every field has a visible `<label>` and an `autocomplete` token. Placeholders are not labels.
6
+ - Keep a visible focus style that sticky headers do not cover.
7
+ - Announce cart updates and result counts in a `role="status"` live region.
8
+ - Errors are text that names the field, with `aria-invalid` and focus moved to the summary.
9
+ - Contrast: 4.5:1 text, 3:1 large text and UI parts. Targets at least 24 by 24 CSS pixels.
10
+ - Drawers and modals move focus in and return it on close.
11
+ - Allow paste and password managers. Do not ask for the same data twice.
@@ -0,0 +1,10 @@
1
+ # Storefront performance
2
+
3
+ - Judge Core Web Vitals on field data at the 75th percentile, mobile and desktop apart.
4
+ - Good means LCP at most 2500 ms, INP at most 200 ms, CLS at most 0.1.
5
+ - Never lazy-load the main product image. Give it `fetchpriority="high"` and the right size.
6
+ - Set image dimensions and reserve space for banners and widgets.
7
+ - Break up long tasks and defer third-party scripts.
8
+ - Cache catalog pages for anonymous shoppers only. Bypass cart, checkout, account, and session cookies.
9
+ - Purge cached pages when price or stock changes.
10
+ - Recheck field data over 28 days after a change.
@@ -0,0 +1,10 @@
1
+ # Stripe payments
2
+
3
+ - Create the PaymentIntent or Checkout Session on the server from the order total, with `metadata[order_id]` and an `Idempotency-Key` per attempt.
4
+ - Send amounts as integers in the currency's smallest unit. Respect zero-decimal currencies.
5
+ - Only the `client_secret` or Checkout `url` reaches the browser. The `sk_` key stays on the server.
6
+ - Mark orders paid from a verified webhook: `payment_intent.succeeded`, or `checkout.session.completed` with `payment_status` `paid`.
7
+ - Verify `Stripe-Signature` on the raw body with the `whsec_` secret, a 300 second tolerance, and a constant-time compare.
8
+ - Deduplicate on `event.id`. Compare `amount_received` and `currency` with the order.
9
+ - Refund with `POST /v1/refunds` and an idempotency key. Track pending refunds from `refund.updated`.
10
+ - Never treat the `return_url` visit as proof of payment.
@@ -0,0 +1,10 @@
1
+ # Subscription billing
2
+
3
+ - Store the subscription separately from invoices: status, interval, and period start and end in UTC.
4
+ - Activate a paid plan only after the first charge is confirmed. A trial stores its end and does not pretend a charge succeeded.
5
+ - Create one renewal invoice per period. Retry that charge with the same idempotency key. Do not advance the period until the charge succeeds.
6
+ - Map provider statuses in one function. Unknown statuses do not move the contract.
7
+ - Cancel at period end is different from canceling now. Record which one the shopper chose.
8
+ - Prorate plan changes on their own invoice lines. Do not edit a paid invoice in place.
9
+ - On the first failed renewal, follow the dunning schedule. Do not delete the entitlement immediately unless the plan says so.
10
+ - Do not implement WooCommerce renewals with `wp_insert_post`. Use the subscription product the store already runs.
@@ -0,0 +1,10 @@
1
+ # Tax and shipping
2
+
3
+ - Take tax rates from the store's configuration or its tax service. Do not invent a percent in the theme or the client.
4
+ - Store each tax line with its rate id and amount. Do not recompute a historical order from today's rate table.
5
+ - Respect inclusive versus exclusive catalog prices. Do not mix them on one order.
6
+ - Match shipping zones from the destination. A missing zone is not a free rate. A configured zero cost is a free method.
7
+ - Recalculate tax and shipping on the server when the address or the cart changes, and show both before payment.
8
+ - Persist the chosen shipping method id and the quoted amount on the order.
9
+ - In WooCommerce, use `WC_Tax` and `WC_Shipping_Method`. Do not write a second rate table in JavaScript.
10
+ - Refund tax from the tax that was stored on the refunded lines.
@@ -0,0 +1,10 @@
1
+ # Vector product search
2
+
3
+ - Embed one vector per purchasable unit, not one vector per parent when variants differ.
4
+ - Use the same model, dimension, and similarity metric for the index and the query. Store `model_id` and a hash of the embedded text.
5
+ - Normalize text the same way as lexical search before embedding, including Persian folds.
6
+ - Keep SKU and GTIN lookup ahead of vector ranking. An exact identifier match wins.
7
+ - Filter facets and stock in the vector query. Do not rely on the embedding to enforce them.
8
+ - Leave price and stock out of the embedded text so a price change does not force a new vector.
9
+ - Re-embed when the text hash or the model id changes. Delete vectors for deleted units.
10
+ - Write down the lexical and vector weights. Do not treat a default weight as a measured result.
@@ -0,0 +1,10 @@
1
+ # WooCommerce plugin development
2
+
3
+ - Read and write orders and products with WooCommerce CRUD (`wc_get_order`, `wc_get_product`, setters, one `save()`). Do not use `get_post`, `get_post_meta`, `update_post_meta`, or `get_posts` for orders.
4
+ - Declare HPOS compatibility from the main plugin file on `before_woocommerce_init` via `FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true )` only after every order path is CRUD-based. Include a `WC tested up to` header.
5
+ - Hook both classic checkout and the Store API checkout. `woocommerce_checkout_create_order` does not run for the Checkout block.
6
+ - Register order admin UI on `wc_get_page_screen_id( 'shop-order' )`. Do not rely on `save_post_shop_order`.
7
+ - Authorize writes with a capability (`edit_shop_orders`, `edit_products`, `manage_woocommerce`) and a nonce. A nonce is not authentication.
8
+ - Create refunds with `wc_create_refund()`. Do not only set the order status to `refunded`.
9
+ - Leave stock reduction to WooCommerce payment and status transitions. Do not decrement quantity again in your hook.
10
+ - Wrap user-facing strings in gettext calls with the plugin text domain. Load a privately distributed text domain on `init`.
@@ -0,0 +1,10 @@
1
+ # WooCommerce REST API
2
+
3
+ - Call `/wp-json/wc/v3/` from the server. Do not send consumer secrets to the browser or to the Store API (`/wp-json/wc/store/v1/`).
4
+ - Authenticate over HTTPS with HTTP Basic auth: consumer key as the username, consumer secret as the password. Use query-string credentials only when the host strips `Authorization`, and never over plain HTTP.
5
+ - Bind each key to a user and a Read, Write, or Read/Write permission that matches the job. A deleted user disables the key.
6
+ - Page with `per_page` at most 100 and stop on `X-WP-TotalPages`. Compare times with the `*_gmt` fields.
7
+ - Treat creates as unsafe to retry. Store your own idempotency key in meta and look it up before posting again.
8
+ - Read every item in a batch response. A batch is not one transaction.
9
+ - Register webhooks with an explicit secret. Acknowledge the activation ping with 2xx and do not create an order from it.
10
+ - Verify `X-WC-Webhook-Signature` on the raw body. Do not dedupe solely on `X-WC-Webhook-Delivery-ID`.
@@ -0,0 +1,10 @@
1
+ # WordPress plugin security
2
+
3
+ - Check a capability with `current_user_can()`, not a role name. Use `edit_shop_orders`, `edit_products`, or `manage_woocommerce` for shop operations.
4
+ - Verify a nonce on cookie-authenticated state changes. A nonce does not authenticate the user and does not belong on a server-to-server webhook.
5
+ - Unslash request data, sanitize it to a known type, and escape on output for the HTML, attribute, or URL context you are writing.
6
+ - Pass SQL values through `$wpdb->prepare()`. Never build a table name or filesystem path from request input.
7
+ - Give every private REST route a `permission_callback` that checks a capability. Do not use `__return_true` for order, customer, or payment data.
8
+ - Authorize the specific object after you load it. A posted order id is not proof that this user may edit that order.
9
+ - Store structured meta as JSON or scalars. Do not `unserialize()` request data.
10
+ - Keep secrets, card data, and full `$_POST` payloads out of logs and order notes.
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Parse the YAML subset used by SKILL.md frontmatter in this repository.
3
+ * Supports top-level scalars, one-level nested maps, and folded/literal blocks.
4
+ */
5
+
6
+ const TOP_LEVEL_KEYS = new Set([
7
+ "name",
8
+ "description",
9
+ "license",
10
+ "compatibility",
11
+ "metadata",
12
+ "allowed-tools",
13
+ ]);
14
+
15
+ export function splitFrontmatter(text) {
16
+ const normalized = text.replace(/\r\n/g, "\n");
17
+ if (!normalized.startsWith("---\n")) {
18
+ throw new Error("SKILL.md must start with a --- frontmatter delimiter");
19
+ }
20
+ const end = normalized.indexOf("\n---\n", 4);
21
+ if (end === -1) {
22
+ throw new Error("SKILL.md is missing the closing --- frontmatter delimiter");
23
+ }
24
+ return {
25
+ yaml: normalized.slice(4, end),
26
+ body: normalized.slice(end + "\n---\n".length),
27
+ normalized,
28
+ };
29
+ }
30
+
31
+ export function parseFrontmatter(yaml) {
32
+ const lines = yaml.split("\n");
33
+ const root = {};
34
+ const seen = new Set();
35
+ let index = 0;
36
+
37
+ while (index < lines.length) {
38
+ const line = lines[index];
39
+ if (line.trim() === "" || /^\s*#/.test(line)) {
40
+ index += 1;
41
+ continue;
42
+ }
43
+ if (/^\s/.test(line)) {
44
+ throw new Error(`Unexpected indented line outside a block: ${line}`);
45
+ }
46
+ const match = line.match(/^([A-Za-z0-9][A-Za-z0-9_-]*):\s*(.*)$/);
47
+ if (!match) {
48
+ throw new Error(`Invalid frontmatter line: ${line}`);
49
+ }
50
+ const key = match[1];
51
+ const raw = match[2];
52
+ if (seen.has(key)) {
53
+ throw new Error(`Duplicate frontmatter field: ${key}`);
54
+ }
55
+ seen.add(key);
56
+
57
+ if (raw === "" || raw === "|" || raw === ">" || raw === "|-" || raw === ">-") {
58
+ const block = [];
59
+ index += 1;
60
+ while (index < lines.length && (lines[index].startsWith(" ") || lines[index].trim() === "")) {
61
+ block.push(lines[index]);
62
+ index += 1;
63
+ }
64
+ root[key] = raw === "" ? parseNestedMap(block, key) : parseBlockScalar(raw, block);
65
+ continue;
66
+ }
67
+
68
+ root[key] = parseScalar(raw, key);
69
+ index += 1;
70
+ }
71
+
72
+ return root;
73
+ }
74
+
75
+ function parseNestedMap(blockLines, parentKey) {
76
+ const value = {};
77
+ const seen = new Set();
78
+ for (const line of blockLines) {
79
+ if (line.trim() === "" || /^\s*#/.test(line)) {
80
+ continue;
81
+ }
82
+ const match = line.match(/^ ([A-Za-z0-9][A-Za-z0-9_-]*):\s*(.*)$/);
83
+ if (!match) {
84
+ throw new Error(`Invalid ${parentKey} entry: ${line}`);
85
+ }
86
+ if (match[2] === "") {
87
+ throw new Error(`Nested blocks under ${parentKey}.${match[1]} are not supported`);
88
+ }
89
+ if (seen.has(match[1])) {
90
+ throw new Error(`Duplicate ${parentKey} field: ${match[1]}`);
91
+ }
92
+ seen.add(match[1]);
93
+ value[match[1]] = parseScalar(match[2], `${parentKey}.${match[1]}`);
94
+ }
95
+ return value;
96
+ }
97
+
98
+ function parseBlockScalar(style, blockLines) {
99
+ const content = blockLines.map((line) => (line.startsWith(" ") ? line.slice(2) : line.trim() === "" ? "" : line));
100
+ while (content.length > 0 && content[content.length - 1] === "") {
101
+ content.pop();
102
+ }
103
+ const text = content.join("\n");
104
+ if (style.startsWith(">")) {
105
+ return text
106
+ .split(/\n{2,}/)
107
+ .map((paragraph) => paragraph.replace(/\n/g, " ").trim())
108
+ .join("\n\n")
109
+ .trim();
110
+ }
111
+ return text;
112
+ }
113
+
114
+ function parseScalar(raw, keyPath) {
115
+ const value = raw.trim();
116
+ if (value.startsWith('"')) {
117
+ if (!value.endsWith('"') || value.length < 2) {
118
+ throw new Error(`Unclosed double quote in ${keyPath}`);
119
+ }
120
+ return decodeDoubleQuoted(value.slice(1, -1), keyPath);
121
+ }
122
+ if (value.startsWith("'")) {
123
+ if (!value.endsWith("'") || value.length < 2) {
124
+ throw new Error(`Unclosed single quote in ${keyPath}`);
125
+ }
126
+ return value.slice(1, -1).replaceAll("''", "'");
127
+ }
128
+ if (value === "" || value === "~" || value === "null" || value === "Null" || value === "NULL") {
129
+ throw new Error(`${keyPath} must be a non-empty string`);
130
+ }
131
+ if (/^(true|false|True|False|TRUE|FALSE)$/.test(value) || /^-?\d/.test(value)) {
132
+ throw new Error(
133
+ `${keyPath} must be a quoted string so YAML parsers keep it a string (got ${value})`,
134
+ );
135
+ }
136
+ if (value.includes(": ") || value.startsWith("{") || value.startsWith("[") || value.includes(" #")) {
137
+ throw new Error(`${keyPath} contains YAML syntax; wrap it in double quotes`);
138
+ }
139
+ return value;
140
+ }
141
+
142
+ function decodeDoubleQuoted(text, keyPath) {
143
+ let out = "";
144
+ for (let i = 0; i < text.length; i += 1) {
145
+ if (text[i] !== "\\") {
146
+ out += text[i];
147
+ continue;
148
+ }
149
+ const next = text[i + 1];
150
+ if (next === undefined) {
151
+ throw new Error(`Trailing escape in ${keyPath}`);
152
+ }
153
+ const map = { n: "\n", t: "\t", '"': '"', "\\": "\\" };
154
+ if (!(next in map)) {
155
+ throw new Error(`Unsupported escape \\${next} in ${keyPath}`);
156
+ }
157
+ out += map[next];
158
+ i += 1;
159
+ }
160
+ return out;
161
+ }
162
+
163
+ export function assertSpecFrontmatter(data) {
164
+ const errors = [];
165
+ for (const key of Object.keys(data)) {
166
+ if (!TOP_LEVEL_KEYS.has(key)) {
167
+ errors.push(`Unknown frontmatter field "${key}". Use name, description, license, compatibility, metadata, or allowed-tools.`);
168
+ }
169
+ }
170
+
171
+ const name = data.name;
172
+ if (typeof name !== "string") {
173
+ errors.push("name is required");
174
+ } else if (name.length < 1 || name.length > 64) {
175
+ errors.push(`name must be 1-64 characters (got ${name.length})`);
176
+ } else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
177
+ errors.push(
178
+ "name must be lowercase letters, numbers, and single hyphens, and must not start or end with a hyphen",
179
+ );
180
+ }
181
+
182
+ const description = data.description;
183
+ if (typeof description !== "string" || description.trim() === "") {
184
+ errors.push("description is required");
185
+ } else if (description.length > 1024) {
186
+ errors.push(`description must be at most 1024 characters (got ${description.length})`);
187
+ }
188
+
189
+ if (data.license !== undefined && (typeof data.license !== "string" || data.license.trim() === "")) {
190
+ errors.push("license must be a non-empty string when set");
191
+ }
192
+
193
+ if (data.compatibility !== undefined) {
194
+ if (typeof data.compatibility !== "string" || data.compatibility.trim() === "") {
195
+ errors.push("compatibility must be a non-empty string when set");
196
+ } else if (data.compatibility.length > 500) {
197
+ errors.push(`compatibility must be at most 500 characters (got ${data.compatibility.length})`);
198
+ }
199
+ }
200
+
201
+ if (data.metadata !== undefined) {
202
+ if (data.metadata === null || typeof data.metadata !== "object" || Array.isArray(data.metadata)) {
203
+ errors.push("metadata must be a map of strings");
204
+ } else {
205
+ for (const [key, value] of Object.entries(data.metadata)) {
206
+ if (typeof value !== "string" || value.trim() === "") {
207
+ errors.push(`metadata.${key} must be a non-empty string`);
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ if (data["allowed-tools"] !== undefined && typeof data["allowed-tools"] !== "string") {
214
+ errors.push("allowed-tools must be a space-separated string when set");
215
+ }
216
+
217
+ return errors;
218
+ }
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: b2b-pricing
3
+ description: "Price for business buyers with customer-group price lists, quantity tiers, contract prices, minimum quantities, net terms, and tax exemption, resolved on the server. Use when adding wholesale or B2B prices, tiered pricing, quotes, or company accounts to a store."
4
+ license: MIT
5
+ compatibility: "The price resolver needs Node.js 20 or newer and no packages."
6
+ metadata:
7
+ author: Naser Sadrmanesh
8
+ version: "0.3.0"
9
+ ---
10
+
11
+ # B2B pricing
12
+
13
+ Business buyers see different prices for the same SKU. The price must be resolved by one server function from the buyer's account, the quantity, and the date, and the same function must run in the catalog, the cart, and the order. Data shapes are in [the price list notes](references/price-lists.md).
14
+
15
+ ## Procedure
16
+
17
+ 1. Attach buyers to a company account, and the company to one or more customer groups. A person can buy for more than one company; the cart belongs to one.
18
+ 2. Store prices as price lists: a list has a currency, a validity window, a priority, and entries of SKU, minimum quantity, and unit price or a percentage off the base price.
19
+ 3. Resolve the unit price in one function: contract price for this company, then group price lists by priority, then the public price. Within a list, use the highest tier whose minimum quantity the line meets.
20
+ 4. Show the buyer the next tier ("10 or more: 8.50 each") so they can choose the quantity.
21
+ 5. Enforce minimum order quantities and pack sizes (order multiples) in the cart, with a message, not only at checkout.
22
+ 6. Recalculate prices on every cart change and again when the order is placed. The price stored on the order line is final.
23
+
24
+ ## Resolution
25
+
26
+ Run `node scripts/resolve-price.mjs request.json lists.json` to see which list and tier win for a line. It returns the unit price, the source, and the next tier.
27
+
28
+ Decide and write down what happens when two rules apply. The usual answer is the contract price wins if one exists. Otherwise the lowest price from the active lists wins. Some stores use priority instead. Pick one; mixing them gives prices nobody can explain.
29
+
30
+ ## Terms, quotes, and tax
31
+
32
+ - Net terms (Net 30) are a payment method that creates an unpaid invoice with a due date. Check the company's credit limit against open invoices before allowing it.
33
+ - A quote is a price offer with an expiry, created by staff or requested by the buyer. Accepting it creates a cart with the quoted prices locked until the expiry.
34
+ - Tax-exempt buyers need a stored exemption certificate or a validated tax id. In the EU, a cross-border B2B sale to a buyer with a valid VAT number (checked through VIES) can use the reverse charge. Store the check result on the order.
35
+ - Show prices with or without tax according to what the buyer's group expects, and say which one is shown.
36
+
37
+ ## Gotchas
38
+
39
+ - Computing the B2B price in the browser lets anyone see or send another group's price. Resolve on the server and render the result.
40
+ - Caching a product page that shows a group price serves that price to every visitor. Vary the cache by group, or load the price separately.
41
+ - A tier is per line by default. If the store promises mix-and-match tiers across a product's variations, sum the quantity across those lines first.
42
+ - Percent-off entries on a list need a defined base: the public regular price, not the sale price, unless the store says otherwise.
43
+ - Changing a price list must not change orders already placed.
44
+
45
+ ## Verification
46
+
47
+ - A guest sees the public price. A buyer in the wholesale group sees the wholesale price on the product page, in the cart, and on the order.
48
+ - With the example list in the notes, 9 units cost 9.00 each (the 1+ tier) and 10 units cost 8.50 each (the 10+ tier).
49
+ - A company with a contract price gets it even when a group list is lower, if the store's rule says contract wins.
50
+ - An order below the minimum quantity cannot be placed.
51
+ - Net terms are refused when open invoices plus the order exceed the credit limit.
52
+
53
+ ## Evaluation
54
+
55
+ Worked examples and the checks they must satisfy are in [evals/tasks.json](evals/tasks.json).
@@ -0,0 +1,3 @@
1
+ Net 30 is offered only to companies with terms enabled. Before accepting the order, sum the company's open invoices and add this order total. If that exceeds the credit limit, refuse invoice payment and offer card payment instead.
2
+
3
+ If it fits, place the order as unpaid, create an invoice with a due date 30 days after the invoice date, and send it to the company's billing contact. Overdue invoices block further orders on terms.
@@ -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.