@nextcommerce/campaigns-os 1.48.0 → 1.52.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.
- package/CHANGELOG.md +539 -0
- package/agents/claude/CLAUDE.md +6 -5
- package/agents/codex/AGENTS.md +6 -5
- package/agents/copilot/copilot-instructions.md +3 -3
- package/agents/cursor/campaigns-os.mdc +3 -3
- package/campaign-spec/dist/rules/campaign-metadata.d.ts +5 -1
- package/campaign-spec/dist/rules/campaign-metadata.js +9 -2
- package/campaign-spec/dist/rules/design-source-shape.js +13 -3
- package/campaign-spec/dist/rules/sdk-version.js +2 -1
- package/compatibility.json +1 -1
- package/contracts/commerce-surface-catalog.json +26 -46
- package/contracts/effects.v1.json +254 -2
- package/contracts/release-ledger.json +1239 -0
- package/contracts/supported-surface.json +4 -4
- package/contracts/template-brand-contract.shared-commerce.v0.json +2 -2
- package/contracts/template-slot-manifest.shared-content-core.v0.json +24 -0
- package/docs/brand-theme-bridge.md +12 -6
- package/docs/build-packet.md +101 -12
- package/docs/campaign-build-brief.md +25 -1
- package/docs/effects.md +6 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +10 -0
- package/docs/qa-and-test-orders.md +66 -11
- package/docs/runtime-readiness.md +1 -1
- package/docs/sdk-storage-compatibility.md +1 -1
- package/docs/skills-revision.md +10 -10
- package/package.json +1 -1
- package/schemas/campaign-runtime-build-packet.v0.schema.json +4 -0
- package/schemas/campaigns-os-qa-verdict.v0.schema.json +8 -3
- package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +3 -3
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +4 -4
- package/skills/next-campaigns-os/SKILL.md +4 -4
- package/skills/next-campaigns-os/references/session-intake.md +7 -3
- package/skills/next-campaigns-os-setup/SKILL.md +3 -3
- package/skills/next-campaigns-polish/SKILL.md +5 -4
- package/skills/next-campaigns-qa/SKILL.md +6 -5
- package/skills.json +10 -10
- package/src/adapter-decision-contract.mjs +1 -1
- package/src/brand-theme.mjs +25 -2
- package/src/build-brief.mjs +68 -21
- package/src/built-site-scope.mjs +39 -6
- package/src/built-smoke-qc.mjs +1117 -0
- package/src/campaign-identity.mjs +36 -2
- package/src/cart-placeholders.mjs +730 -0
- package/src/cli.mjs +320 -42
- package/src/commercial-journey.mjs +65 -4
- package/src/commercial-parity.mjs +6 -1
- package/src/doctor/checks.mjs +291 -24
- package/src/doctor/inspect.mjs +53 -2
- package/src/doctor/next-step.mjs +1 -1
- package/src/install-mode.mjs +0 -8
- package/src/invocation.mjs +5 -2
- package/src/local-preview-policy.mjs +1 -1
- package/src/local-proof.mjs +4 -1
- package/src/polish-browser.mjs +218 -1
- package/src/polish-capture.mjs +1 -1
- package/src/polish-media-weight.mjs +492 -0
- package/src/polish-node.mjs +96 -4
- package/src/progress-node.mjs +5 -1
- package/src/qa-binding-evidence.mjs +21 -0
- package/src/qa-browser.mjs +338 -97
- package/src/qa-content-params.mjs +889 -0
- package/src/qa-node.mjs +114 -14
- package/src/qa-order-bump.mjs +22 -1
- package/src/qa-policy-links.mjs +1019 -0
- package/src/qa-tracking-params.mjs +1389 -0
- package/src/qa-url-privacy.mjs +168 -0
- package/src/qc-accept.mjs +446 -0
- package/src/qc-check-registry.mjs +83 -0
- package/src/qc-results.mjs +1049 -0
- package/src/sdk-attribute-index.mjs +71 -0
- package/src/sdk-markup.mjs +2 -2
- package/src/sdk-storage-compatibility.mjs +63 -3
- package/src/source-prep.mjs +37 -7
- package/src/stage-record.mjs +356 -36
- package/src/theme-gate.mjs +3 -3
|
@@ -259,8 +259,15 @@ function gateBase(subject) {
|
|
|
259
259
|
/**
|
|
260
260
|
* Evaluate cross-page campaign identity over built pages.
|
|
261
261
|
*
|
|
262
|
+
* A page marked `spec_page: false` is built output that no CampaignSpec page
|
|
263
|
+
* builds to. It is still scanned, because it is still served, but a finding
|
|
264
|
+
* that names it says so: such a file is usually left in `_site/` by an earlier
|
|
265
|
+
* build, or built from an HTML file copied into the source (a design export's
|
|
266
|
+
* `index.html` under `assets/`), and the repair is to remove it, not to retag
|
|
267
|
+
* it.
|
|
268
|
+
*
|
|
262
269
|
* @param {{ subject: object, pages: Array<{ page_id: string, route?: string|null,
|
|
263
|
-
* file?: string|null, content: string,
|
|
270
|
+
* file?: string|null, content: string, spec_page?: boolean,
|
|
264
271
|
* scripts?: Array<{ src: string, file?: string|null, content: string }> }> }} input
|
|
265
272
|
*/
|
|
266
273
|
export function evaluateCampaignIdentity({ subject, pages = [] } = {}) {
|
|
@@ -300,6 +307,7 @@ export function evaluateCampaignIdentity({ subject, pages = [] } = {}) {
|
|
|
300
307
|
code: CAMPAIGN_IDENTITY_KINDS.api_key_drift,
|
|
301
308
|
a: { page_id: firstKey.page_id, file: firstKey.where, source: firstKey.source, value: firstKey.value },
|
|
302
309
|
b: { page_id: observation.page_id, file: observation.where, source: observation.source, value: observation.value },
|
|
310
|
+
evidence: `API key differs across pages: ${firstKey.where} has ${quote(firstKey.value)} (${firstKey.source}) but ${observation.where} has ${quote(observation.value)} (${observation.source}).`,
|
|
303
311
|
message: `API key differs across pages: ${firstKey.where} has ${quote(firstKey.value)} (${firstKey.source}) but ${observation.where} has ${quote(observation.value)} (${observation.source}). One of these pages was borrowed from another campaign; make both name the same key.`,
|
|
304
312
|
});
|
|
305
313
|
break; // one finding per kind names the first pair; the rest follow from the same edit
|
|
@@ -316,6 +324,7 @@ export function evaluateCampaignIdentity({ subject, pages = [] } = {}) {
|
|
|
316
324
|
code: CAMPAIGN_IDENTITY_KINDS.funnel_drift,
|
|
317
325
|
a: { page_id: firstFunnel.page_id, file: firstFunnel.file || firstFunnel.page_id, value: firstFunnel.funnel },
|
|
318
326
|
b: { page_id: identity.page_id, file: identity.file || identity.page_id, value: identity.funnel },
|
|
327
|
+
evidence: `next-funnel differs across pages: ${firstFunnel.file || firstFunnel.page_id} has ${quote(firstFunnel.funnel)} but ${identity.file || identity.page_id} has ${quote(identity.funnel)}.`,
|
|
319
328
|
message: `next-funnel differs across pages: ${firstFunnel.file || firstFunnel.page_id} has ${quote(firstFunnel.funnel)} but ${identity.file || identity.page_id} has ${quote(identity.funnel)}. Orders from the second page attribute to the other funnel; set the same <meta name="next-funnel"> on both.`,
|
|
320
329
|
});
|
|
321
330
|
break;
|
|
@@ -336,6 +345,7 @@ export function evaluateCampaignIdentity({ subject, pages = [] } = {}) {
|
|
|
336
345
|
code: CAMPAIGN_IDENTITY_KINDS.funnel_missing,
|
|
337
346
|
a: { page_id: identity.page_id, file: identity.file || identity.page_id, value: null },
|
|
338
347
|
b: { page_id: firstFunnel.page_id, file: firstFunnel.file || firstFunnel.page_id, value: firstFunnel.funnel },
|
|
348
|
+
evidence: `${identity.file || identity.page_id} declares next-page-type=${quote(identity.page_type)} but no <meta name="next-funnel">, while ${firstFunnel.file || firstFunnel.page_id} carries ${quote(firstFunnel.funnel)}.`,
|
|
339
349
|
message: `${identity.file || identity.page_id} declares next-page-type=${quote(identity.page_type)} but no <meta name="next-funnel">, while ${firstFunnel.file || firstFunnel.page_id} carries ${quote(firstFunnel.funnel)}. Orders from the untagged page attribute differently from the rest; add the same next-funnel meta.`,
|
|
340
350
|
});
|
|
341
351
|
break;
|
|
@@ -354,13 +364,37 @@ export function evaluateCampaignIdentity({ subject, pages = [] } = {}) {
|
|
|
354
364
|
findings.push({
|
|
355
365
|
kind: "attribution_drift",
|
|
356
366
|
code: CAMPAIGN_IDENTITY_KINDS.attribution_drift,
|
|
357
|
-
a
|
|
367
|
+
// a is the page the expected tag came from: this page's own tag, or,
|
|
368
|
+
// when it has none, the campaign's first tagged page.
|
|
369
|
+
a: { page_id: identity.funnel ? identity.page_id : firstFunnel.page_id, file: expectedWhere, value: expected },
|
|
358
370
|
b: { page_id: identity.page_id, file: attribution.where, value: attribution.value },
|
|
371
|
+
evidence: `setAttribution({ funnel: ${quote(attribution.value)} }) in ${attribution.where} disagrees with next-funnel ${quote(expected)} in ${expectedWhere}.`,
|
|
359
372
|
message: `setAttribution({ funnel: ${quote(attribution.value)} }) in ${attribution.where} disagrees with next-funnel ${quote(expected)} in ${expectedWhere}. The call overrides the tag, so orders attribute to ${quote(attribution.value)}; change the call to ${quote(expected)} or remove it.`,
|
|
360
373
|
});
|
|
361
374
|
}
|
|
362
375
|
}
|
|
363
376
|
|
|
377
|
+
const strayFiles = new Map(scanned
|
|
378
|
+
.filter((page) => page.spec_page === false)
|
|
379
|
+
.map((page) => [page.page_id, page.file || page.page_id]));
|
|
380
|
+
for (const finding of findings) {
|
|
381
|
+
const stray = [...new Set([finding.a?.page_id, finding.b?.page_id]
|
|
382
|
+
.filter((pageId) => strayFiles.has(pageId))
|
|
383
|
+
.map((pageId) => strayFiles.get(pageId)))];
|
|
384
|
+
if (!stray.length) continue;
|
|
385
|
+
finding.stray_files = stray;
|
|
386
|
+
// The stray is the repair, whichever side of the finding it is on, so it
|
|
387
|
+
// leads, and the drift it causes follows as evidence only. The finding's
|
|
388
|
+
// own instruction (retag, or add a missing tag to a real page) is dropped:
|
|
389
|
+
// that edit would land on the wrong file.
|
|
390
|
+
const named = stray.length > 2 ? `${stray.slice(0, -1).join(", ")}, and ${stray.at(-1)}` : stray.join(" and ");
|
|
391
|
+
finding.message = `${named} ${stray.length === 1 ? "is" : "are"} not the built page of any CampaignSpec page: built output left by an earlier build, or an HTML file copied into the source (a design export's index.html under assets/ builds as its own page). Remove the source file if there is one, delete the built file, then rebuild and record the build again; do not retag either page. What it causes: ${finding.evidence}`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// evidence is the message's first sentence, kept apart only so a stray
|
|
395
|
+
// repair can quote it without the finding's own instruction.
|
|
396
|
+
for (const finding of findings) delete finding.evidence;
|
|
397
|
+
|
|
364
398
|
const identity = {
|
|
365
399
|
api_key: firstKey ? firstKey.value : null,
|
|
366
400
|
api_key_source: firstKey ? firstKey.source : null,
|