@nextcommerce/campaigns-os 1.60.0 → 1.61.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 (88) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +5 -5
  3. package/agents/claude/CLAUDE.md +15 -0
  4. package/agents/codex/AGENTS.md +15 -0
  5. package/agents/copilot/copilot-instructions.md +15 -0
  6. package/agents/cursor/campaigns-os.mdc +15 -0
  7. package/compatibility.json +1 -1
  8. package/contracts/brand-theme-target-tokens.next-core.v0.json +95 -25
  9. package/contracts/campaign-cart-sdk-support-policy.v0.json +2 -2
  10. package/contracts/commerce-surface-catalog.json +27 -26
  11. package/contracts/release-ledger.json +768 -0
  12. package/contracts/supported-surface.json +4 -4
  13. package/contracts/template-brand-contract.shared-commerce.v0.json +1 -1
  14. package/docs/access-model.md +20 -0
  15. package/docs/brand-theme-bridge.md +62 -3
  16. package/docs/build-packet.md +28 -1
  17. package/docs/campaign-build-brief.md +16 -0
  18. package/docs/campaigns-os-build-flow.md +61 -2
  19. package/docs/campaignspec-authoring-examples.md +116 -0
  20. package/docs/design-source-package.md +46 -2
  21. package/docs/entry-points.md +188 -0
  22. package/docs/local-setup.md +1 -1
  23. package/docs/orientation-contract-reference.md +1 -1
  24. package/docs/polish-evidence.md +40 -2
  25. package/docs/qa-and-test-orders.md +50 -2
  26. package/docs/quickstart.md +438 -0
  27. package/docs/readback.md +119 -6
  28. package/docs/runtime-readiness.md +1 -1
  29. package/docs/sdk-storage-compatibility.md +3 -1
  30. package/docs/skills-revision.md +10 -10
  31. package/docs/source-adapters.md +213 -0
  32. package/package.json +10 -3
  33. package/schemas/campaign-design-source-package.v0.schema.json +8 -0
  34. package/schemas/campaigns-os-readback.v2.schema.json +177 -1
  35. package/schemas/source-html-manifest.v0.schema.json +3 -0
  36. package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
  37. package/skills/campaign-readback-classification/SKILL.md +3 -3
  38. package/skills/campaign-run-evidence/SKILL.md +3 -3
  39. package/skills/contribution-intake/SKILL.md +3 -3
  40. package/skills/next-campaigns-build/SKILL.md +4 -4
  41. package/skills/next-campaigns-os/SKILL.md +4 -3
  42. package/skills/next-campaigns-os-setup/SKILL.md +3 -3
  43. package/skills/next-campaigns-polish/SKILL.md +6 -9
  44. package/skills/next-campaigns-qa/SKILL.md +3 -3
  45. package/skills/next-campaigns-sdk-bump/SKILL.md +117 -24
  46. package/skills.json +11 -11
  47. package/src/brand-theme.mjs +413 -8
  48. package/src/brief-summary.mjs +6 -2
  49. package/src/browser-launch.mjs +2 -1
  50. package/src/build-brief.mjs +9 -2
  51. package/src/built-site-scope.mjs +2 -2
  52. package/src/cli.mjs +52 -13
  53. package/src/design-file-reference.mjs +93 -0
  54. package/src/design-source-package.mjs +55 -1
  55. package/src/doctor/checks.mjs +8 -74
  56. package/src/doctor/ci.mjs +112 -0
  57. package/src/doctor/inspect.mjs +18 -0
  58. package/src/doctor/source-provenance.mjs +74 -0
  59. package/src/evidence-debt.mjs +119 -0
  60. package/src/parity/compare.mjs +316 -0
  61. package/src/parity/design-access.mjs +54 -0
  62. package/src/parity/element-parity-record.mjs +569 -0
  63. package/src/parity/probe.mjs +205 -0
  64. package/src/parity/snapshot.mjs +110 -0
  65. package/src/png-header.mjs +87 -0
  66. package/src/polish-browser.mjs +186 -9
  67. package/src/polish-captures-record.mjs +52 -0
  68. package/src/polish-deadline.mjs +14 -0
  69. package/src/polish-gate.mjs +25 -6
  70. package/src/polish-node.mjs +81 -7
  71. package/src/polish-visual-fixtures.mjs +74 -0
  72. package/src/polish-visual.mjs +606 -0
  73. package/src/qa-analytics-correctness.mjs +14 -2
  74. package/src/qa-analytics-parity.mjs +94 -5
  75. package/src/qa-node.mjs +39 -6
  76. package/src/qc-accept.mjs +148 -6
  77. package/src/qc-check-registry.mjs +37 -1
  78. package/src/qc-results.mjs +404 -16
  79. package/src/readback.mjs +57 -13
  80. package/src/sdk-attribute-index.mjs +11 -11
  81. package/src/sdk-markup.mjs +77 -2
  82. package/src/sdk-storage-compatibility.mjs +94 -4
  83. package/src/source-html-manifest.mjs +7 -0
  84. package/src/source-html-server.mjs +129 -0
  85. package/src/stage-record.mjs +45 -7
  86. package/src/theme-gate.mjs +2 -2
  87. package/src/visual-diff.mjs +296 -0
  88. package/src/visual-reference.mjs +126 -0
@@ -0,0 +1,74 @@
1
+ // Synthetic test data only. No browser, source HTML capture or merchant material.
2
+ import { deflateSync } from "node:zlib";
3
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { resolve } from "node:path";
6
+ import { crc32, sha256Hex, boundedDimensions, VISUAL_MAX_REFERENCE_BYTES } from "./png-header.mjs";
7
+ import { buildCapturesRecord, planVisualCapture, writePolishPng, containedWritePath, VISUAL_VIEWPORTS, VISUAL_MASKS } from "./polish-visual.mjs";
8
+
9
+ export function pngChunk(type, data = Buffer.alloc(0)) {
10
+ if (!Buffer.isBuffer(data) || data.length > VISUAL_MAX_REFERENCE_BYTES - 12) throw new RangeError("too_large");
11
+ const chunk = Buffer.alloc(12 + data.length);
12
+ chunk.writeUInt32BE(data.length); chunk.write(type, 4, 4, "ascii"); data.copy(chunk, 8);
13
+ chunk.writeUInt32BE(crc32(chunk.subarray(4, chunk.length - 4)), chunk.length - 4);
14
+ return chunk;
15
+ }
16
+ export function createRgba(width, height, color = [255, 255, 255, 255]) {
17
+ boundedDimensions(width, height);
18
+ const data = Buffer.alloc(width * height * 4);
19
+ for (let i = 0; i < data.length; i += 4) data.set(color, i);
20
+ return { width, height, data };
21
+ }
22
+ export function createRgbaPng(width, height, color = [255, 255, 255, 255], { raw = null, idat = null, interlace = 0 } = {}) {
23
+ // Explicit IDAT permits deliberately oversized headers without allocating pixels.
24
+ if (!idat) boundedDimensions(width, height);
25
+ else if (![width, height].every((n) => Number.isSafeInteger(n) && n > 0 && n <= 0x7fffffff)) throw new RangeError("image_too_large");
26
+ const ihdr = Buffer.alloc(13);
27
+ ihdr.writeUInt32BE(width); ihdr.writeUInt32BE(height, 4); ihdr[8] = 8; ihdr[9] = 6; ihdr[12] = interlace;
28
+ if (!idat) {
29
+ if (!raw) {
30
+ raw = Buffer.alloc(height * (1 + width * 4));
31
+ for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) raw.set(color, y * (1 + width * 4) + 1 + x * 4);
32
+ }
33
+ idat = deflateSync(raw);
34
+ }
35
+ return Buffer.concat([Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), pngChunk("IHDR", ihdr), pngChunk("IDAT", idat), pngChunk("IEND")]);
36
+ }
37
+ export const syntheticHash = (bytes) => `sha256:${sha256Hex(bytes)}`;
38
+ export const syntheticReport = () => ({ stages: { assembly: { build_fingerprint: `sha256:${"a".repeat(64)}` } } });
39
+ export function syntheticCapturesRecord(report, { routes = [{ page_id: "page", requested_route: "/page/" }], viewports = VISUAL_VIEWPORTS,
40
+ targetRepo = mkdtempSync(resolve(tmpdir(), "visual-record-")) } = {}) {
41
+ const plan = planVisualCapture({ route_scope: "all", routes, viewports });
42
+ if (!plan) return null;
43
+ const cells = plan.cells.map((cell) => {
44
+ const png = writePolishPng(targetRepo, "captures", createRgbaPng(cell.width, cell.height));
45
+ const shot = { status: "captured", png, full_height_css: cell.height,
46
+ crop: { x: 0, y: 0, width: cell.width, height: cell.height }, truncated: false };
47
+ return { ...cell, source_path_rel: null, source_sha256: null, status: "rendered",
48
+ networkidle: { status: "settled", duration_ms: 0 },
49
+ readiness: { fonts: { status: "settled", failed_faces: 0, waited_ms: 0 }, await: { status: "not_present", remaining_count: 0, waited_ms: 0 } },
50
+ external: cell.side === "source_html" ? { blocked_count: 0, blocked_websocket_count: 0, blocked_origins: [] } : null,
51
+ sections: { status: "no_section_keys", bands: [], duplicated_keys: [], capped: false },
52
+ actual: structuredClone(shot), stabilized: { ...structuredClone(shot), treatment: {
53
+ animations: "disabled", caret: "hide", mask_color: "#ff00ff", mask_selectors: VISUAL_MASKS, masks: [], mask_cap_reached: false } },
54
+ captured_at: "2026-01-01T00:00:00.000Z" };
55
+ });
56
+ return buildCapturesRecord({ buildFingerprint: report.stages.assembly.build_fingerprint, slug: "sample", routes, cells,
57
+ environment: { browser: "chromium/synthetic", device_scale_factor: 1 },
58
+ dependencies: { decoder: { status: "available", version: null }, diff: { status: "available", version: null } },
59
+ measuredAt: "2026-01-01T00:00:00.000Z" });
60
+ }
61
+
62
+ export function syntheticVisualFixture() {
63
+ const targetRepo = mkdtempSync(resolve(tmpdir(), "visual-core-"));
64
+ const sourceRoot = containedWritePath(targetRepo, "source");
65
+ const htmlPath = containedWritePath(targetRepo, "source/page.html");
66
+ mkdirSync(sourceRoot);
67
+ const html = Buffer.from("<!doctype html><p>synthetic source</p>");
68
+ writeFileSync(htmlPath, html);
69
+ const linkedSource = { kind: "html_file", identity: { sha256: syntheticHash(html), sha256_recorded: syntheticHash(html) }, provenance: { class: "declared_source", reasons: [] } };
70
+ return { targetRepo, sourceRoot, linkedSource, report: syntheticReport(),
71
+ recorded: { captured_at: "2026-01-01T00:00:00.000Z", source_ref_id: "html", scale_factor: 1 },
72
+ write(name, bytes) { const path = containedWritePath(sourceRoot, name); mkdirSync(resolve(path, ".."), { recursive: true }); writeFileSync(path, bytes); return path; },
73
+ cleanup() { rmSync(targetRepo, { recursive: true, force: true }); } };
74
+ }