@nextcommerce/campaigns-os 1.59.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 (96) hide show
  1. package/CHANGELOG.md +242 -0
  2. package/README.md +11 -10
  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 +1311 -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 +42 -13
  17. package/docs/campaign-build-brief.md +17 -1
  18. package/docs/campaigns-os-build-flow.md +62 -3
  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 +41 -3
  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 +5 -5
  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 +12 -16
  44. package/skills/next-campaigns-qa/SKILL.md +3 -3
  45. package/skills/next-campaigns-sdk-bump/SKILL.md +286 -0
  46. package/skills.json +18 -10
  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 +7 -14
  52. package/src/campaign-identity.mjs +6 -4
  53. package/src/cli.mjs +89 -73
  54. package/src/design-file-reference.mjs +93 -0
  55. package/src/design-source-package.mjs +55 -1
  56. package/src/doctor/checks.mjs +14 -77
  57. package/src/doctor/ci.mjs +112 -0
  58. package/src/doctor/inspect.mjs +20 -8
  59. package/src/doctor/next-step.mjs +6 -3
  60. package/src/doctor/source-provenance.mjs +74 -0
  61. package/src/evidence-debt.mjs +119 -0
  62. package/src/orchestration-stage-contract.mjs +6 -3
  63. package/src/page-kit-build-summary.mjs +21 -1
  64. package/src/page-kit-campaign-config.mjs +19 -0
  65. package/src/parity/compare.mjs +316 -0
  66. package/src/parity/design-access.mjs +54 -0
  67. package/src/parity/element-parity-record.mjs +569 -0
  68. package/src/parity/probe.mjs +205 -0
  69. package/src/parity/snapshot.mjs +110 -0
  70. package/src/png-header.mjs +87 -0
  71. package/src/polish-browser.mjs +186 -9
  72. package/src/polish-captures-record.mjs +52 -0
  73. package/src/polish-deadline.mjs +14 -0
  74. package/src/polish-gate.mjs +26 -11
  75. package/src/polish-node.mjs +81 -7
  76. package/src/polish-visual-fixtures.mjs +74 -0
  77. package/src/polish-visual.mjs +606 -0
  78. package/src/progress-node.mjs +2 -1
  79. package/src/qa-analytics-correctness.mjs +14 -2
  80. package/src/qa-analytics-parity.mjs +94 -5
  81. package/src/qa-node.mjs +39 -6
  82. package/src/qc-accept.mjs +148 -6
  83. package/src/qc-check-registry.mjs +37 -1
  84. package/src/qc-results.mjs +404 -16
  85. package/src/readback.mjs +57 -13
  86. package/src/sdk-attribute-index.mjs +11 -11
  87. package/src/sdk-markup.mjs +77 -2
  88. package/src/sdk-storage-compatibility.mjs +94 -4
  89. package/src/source-html-manifest.mjs +7 -0
  90. package/src/source-html-server.mjs +129 -0
  91. package/src/spec-derive.mjs +24 -9
  92. package/src/stage-ledger.mjs +27 -1
  93. package/src/stage-record.mjs +63 -9
  94. package/src/theme-gate.mjs +2 -2
  95. package/src/visual-diff.mjs +296 -0
  96. 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
+ }