@nextcommerce/campaigns-os 1.50.0 → 1.53.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 (87) hide show
  1. package/CHANGELOG.md +474 -0
  2. package/agents/claude/CLAUDE.md +4 -2
  3. package/agents/codex/AGENTS.md +3 -1
  4. package/agents/copilot/copilot-instructions.md +3 -1
  5. package/agents/cursor/campaigns-os.mdc +3 -1
  6. package/campaign-spec/dist/rules/campaign-metadata.d.ts +5 -1
  7. package/campaign-spec/dist/rules/campaign-metadata.js +9 -2
  8. package/campaign-spec/dist/rules/design-source-shape.js +13 -3
  9. package/campaign-spec/dist/rules/sdk-version.js +2 -1
  10. package/compatibility.json +1 -1
  11. package/contracts/commerce-surface-catalog.json +26 -46
  12. package/contracts/effects.v1.json +282 -14
  13. package/contracts/release-ledger.json +1233 -0
  14. package/contracts/supported-surface.json +2 -2
  15. package/contracts/template-brand-contract.shared-commerce.v0.json +2 -2
  16. package/contracts/template-slot-manifest.shared-content-core.v0.json +24 -0
  17. package/docs/brand-theme-bridge.md +2 -0
  18. package/docs/build-packet.md +148 -23
  19. package/docs/campaign-build-brief.md +57 -2
  20. package/docs/effects.md +14 -0
  21. package/docs/local-setup.md +1 -1
  22. package/docs/orientation-contract-reference.md +1 -1
  23. package/docs/polish-evidence.md +12 -0
  24. package/docs/progress-snapshots.md +2 -4
  25. package/docs/qa-and-test-orders.md +47 -4
  26. package/docs/readback.md +19 -2
  27. package/docs/runtime-readiness.md +1 -1
  28. package/docs/sdk-storage-compatibility.md +1 -1
  29. package/docs/skills-revision.md +10 -10
  30. package/package.json +1 -1
  31. package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
  32. package/skills/campaign-readback-classification/SKILL.md +3 -3
  33. package/skills/campaign-run-evidence/SKILL.md +3 -3
  34. package/skills/contribution-intake/SKILL.md +3 -3
  35. package/skills/next-campaigns-build/SKILL.md +5 -3
  36. package/skills/next-campaigns-os/SKILL.md +11 -20
  37. package/skills/next-campaigns-os/references/session-intake.md +17 -9
  38. package/skills/next-campaigns-os-setup/SKILL.md +4 -3
  39. package/skills/next-campaigns-polish/SKILL.md +7 -5
  40. package/skills/next-campaigns-qa/SKILL.md +8 -5
  41. package/skills.json +10 -10
  42. package/src/adapter-decision-contract.mjs +1 -1
  43. package/src/brand-theme.mjs +33 -20
  44. package/src/brief-summary.mjs +275 -0
  45. package/src/build-brief.mjs +229 -30
  46. package/src/built-site-scope.mjs +39 -6
  47. package/src/built-smoke-qc.mjs +1117 -0
  48. package/src/campaign-identity.mjs +36 -2
  49. package/src/cart-placeholders.mjs +730 -0
  50. package/src/cli.mjs +467 -143
  51. package/src/commercial-journey.mjs +65 -4
  52. package/src/commercial-parity.mjs +6 -1
  53. package/src/contrast.mjs +649 -0
  54. package/src/design-source-publication.mjs +110 -3
  55. package/src/doctor/checks.mjs +415 -33
  56. package/src/doctor/inspect.mjs +53 -2
  57. package/src/doctor/next-step.mjs +16 -7
  58. package/src/input-currency.mjs +394 -0
  59. package/src/input-refresh.mjs +708 -0
  60. package/src/invocation.mjs +5 -2
  61. package/src/local-preview-policy.mjs +1 -1
  62. package/src/local-proof.mjs +4 -1
  63. package/src/polish-browser.mjs +660 -141
  64. package/src/polish-capture.mjs +1 -1
  65. package/src/polish-deadline.mjs +7 -0
  66. package/src/polish-media-weight.mjs +492 -0
  67. package/src/polish-node.mjs +400 -28
  68. package/src/polish-readability.mjs +828 -0
  69. package/src/progress-node.mjs +19 -3
  70. package/src/qa-browser.mjs +439 -180
  71. package/src/qa-content-params.mjs +889 -0
  72. package/src/qa-node.mjs +116 -13
  73. package/src/qa-order-bump.mjs +22 -1
  74. package/src/qa-policy-links.mjs +1019 -0
  75. package/src/qa-tracking-params.mjs +1389 -0
  76. package/src/qa-url-privacy.mjs +168 -0
  77. package/src/qc-accept.mjs +482 -0
  78. package/src/qc-check-registry.mjs +91 -0
  79. package/src/qc-results.mjs +1341 -0
  80. package/src/readback.mjs +58 -10
  81. package/src/sdk-attribute-index.mjs +71 -0
  82. package/src/sdk-markup.mjs +2 -2
  83. package/src/sdk-storage-compatibility.mjs +63 -3
  84. package/src/source-prep.mjs +37 -7
  85. package/src/stage-ledger.mjs +235 -44
  86. package/src/stage-record.mjs +165 -33
  87. package/src/tooling-setup.mjs +1 -1
@@ -104,11 +104,72 @@ function selectedShipping(page) {
104
104
  }
105
105
 
106
106
  // One predicate for "this checkout row is an order bump": the spec marks a
107
- // bump with `is_upsell: true` on a non-upsell page. Every consumer that must
107
+ // bump with `is_order_bump: true` (the schema's and authoring guide's flag) or
108
+ // `is_upsell: true` on a non-upsell page. Every consumer that must
108
109
  // tell a bump from a main selector row (parity scenarios, bump deltas, the QA
109
- // selector-tier planner) reads this, so a marker change lands in one place.
110
+ // selector-tier planner, `next`'s order-bump QA command) reads this, so a
111
+ // marker change lands in one place.
110
112
  export function isBumpRow(row) {
111
- return Boolean(row?.is_upsell);
113
+ return Boolean(row?.is_order_bump || row?.is_upsell);
114
+ }
115
+
116
+ // A checkout package row's ref, with the doctor's specPackageRecords
117
+ // tolerance (ref_id, then package_id, then id).
118
+ function checkoutRowRef(pkg) {
119
+ return [pkg.ref_id, pkg.package_id, pkg.id]
120
+ .map((value) => (value == null ? "" : String(value).trim()))
121
+ .find(Boolean);
122
+ }
123
+
124
+ // Order bumps declared on a checkout page (isBumpRow rows), as refs in
125
+ // declaration order. They are add-ons to a selected tier, not tiers, so the
126
+ // QA tier planner reports and skips them and `next` names the --cart run that
127
+ // toggles them.
128
+ export function declaredOrderBumps(checkoutPage) {
129
+ const refs = [];
130
+ for (const pkg of array(checkoutPage?.packages)) {
131
+ if (!pkg || typeof pkg !== "object" || !isBumpRow(pkg)) continue;
132
+ const ref = checkoutRowRef(pkg);
133
+ if (ref && !refs.includes(ref)) refs.push(ref);
134
+ }
135
+ return refs;
136
+ }
137
+
138
+ // Selector tiers are the packages a spec declares on a checkout page, in
139
+ // declaration order. Order-bump rows are add-ons offered alongside the
140
+ // selected tier, not tiers of their own: they never become a tier.
141
+ export function declaredSelectorTiers(checkoutPage) {
142
+ const records = [];
143
+ const quantitiesByRef = new Map();
144
+ for (const pkg of array(checkoutPage?.packages)) {
145
+ if (!pkg || typeof pkg !== "object" || isBumpRow(pkg)) continue;
146
+ const ref = checkoutRowRef(pkg);
147
+ const declaredQuantity = Number(pkg.qty ?? pkg.quantity ?? 1);
148
+ if (!ref || !Number.isInteger(declaredQuantity) || declaredQuantity < 1) continue;
149
+ records.push({ pkg, ref, declaredQuantity });
150
+ if (!quantitiesByRef.has(ref)) quantitiesByRef.set(ref, new Set());
151
+ quantitiesByRef.get(ref).add(declaredQuantity);
152
+ }
153
+
154
+ const tiers = [];
155
+ const seen = new Set();
156
+ for (const { pkg, ref, declaredQuantity } of records) {
157
+ // A unique ref is a catalog package bought once, even when that package's
158
+ // own composition is 3x. Only repeated declarations of the SAME ref at
159
+ // different quantities express shopper purchase multipliers (e.g. a 1x
160
+ // and a 2x package).
161
+ const quantity = quantitiesByRef.get(ref).size > 1 ? declaredQuantity : 1;
162
+ const identity = `${ref}:${quantity}`;
163
+ if (seen.has(identity)) continue;
164
+ seen.add(identity);
165
+ const label = [pkg.name, pkg.title].find((value) => typeof value === "string" && value.trim());
166
+ tiers.push({
167
+ ref,
168
+ quantity,
169
+ declared_by: label ? label.trim() : undefined,
170
+ });
171
+ }
172
+ return tiers;
112
173
  }
113
174
 
114
175
  function lineForRow(row) {
@@ -932,7 +993,7 @@ function makePage(scenarios, catalog) {
932
993
  row_index: row.row_index,
933
994
  package_id: row.package_id,
934
995
  quantity: row.quantity,
935
- ...(row.is_upsell ? { is_upsell: true } : {}),
996
+ ...(isBumpRow(row) ? { is_upsell: true } : {}),
936
997
  name: row.name,
937
998
  state: PricingState.Unresolved,
938
999
  reason: pageReason,
@@ -607,7 +607,12 @@ function partitionCaptures(capturesValue, { countsOnly = false } = {}) {
607
607
 
608
608
  function priceComparisonClaims(capture, page) {
609
609
  const claims = array(capture?.price_claims);
610
- if (!exactMoneyFact(page?.representative_total)) {
610
+ // A voucher on the page that the plan cannot price (no calculated pair for
611
+ // its code) can move the shown price away from the planned total, so a
612
+ // difference there says nothing about the page. Its price claims stay
613
+ // unresolved, which already keeps coverage incomplete, rather than being
614
+ // reported as mismatches against the list-price total.
615
+ if (!exactMoneyFact(page?.representative_total) || voucherComparisonClaims(capture, page).unresolved > 0) {
611
616
  return { compared: [], unresolved: claims.length };
612
617
  }
613
618
  const bindings = [...new Set(claims.map((claim) => String(claim.binding)))];