@nextcommerce/campaigns-os 1.43.1 → 1.46.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/AGENTS.md +9 -2
- package/CHANGELOG.md +1099 -5103
- package/README.md +34 -13
- package/agents/claude/CLAUDE.md +1 -1
- package/agents/codex/AGENTS.md +1 -1
- package/agents/copilot/copilot-instructions.md +1 -1
- package/agents/cursor/campaigns-os.mdc +1 -1
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +2 -2
- package/campaign-spec/dist/rules/analytics-contract-shape.js +2 -2
- package/campaign-spec/dist/rules/store-profile-shape.d.ts +5 -1
- package/campaign-spec/dist/rules/store-profile-shape.js +8 -10
- package/campaign-spec/dist/types.d.ts +2 -2
- package/contracts/agent-relevant-change-policy.v1.json +5 -0
- package/contracts/archive/CHANGELOG.2026-09-30.md +5111 -0
- package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
- package/contracts/effects.v1.json +1184 -121
- package/contracts/orientation-reason-codes.v1.json +7 -0
- package/contracts/release-ledger.json +2190 -5260
- package/contracts/supported-surface.json +7 -4
- package/contracts/template-slot-manifest.shared-content-core.v0.json +403 -0
- package/docs/brand-theme-bridge.md +81 -0
- package/docs/build-packet.md +222 -23
- package/docs/campaigns-os-build-flow.md +4 -3
- package/docs/design-source-package.md +162 -15
- package/docs/effects.md +66 -12
- package/docs/gateway-login.md +3 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +42 -2
- package/docs/polish-evidence.md +74 -0
- package/docs/progress-snapshots.md +10 -6
- package/docs/qa-and-test-orders.md +230 -20
- package/docs/release-ledger-authoring-guide.md +70 -8
- package/docs/runtime-readiness.md +1 -1
- package/docs/sdk-storage-compatibility.md +1 -1
- package/docs/skills-revision.md +10 -10
- package/docs/supported-surface.md +2 -2
- package/docs/versioning.md +4 -1
- package/package.json +1 -1
- package/schemas/campaigns-os-release-ledger.v1.schema.json +32 -2
- package/schemas/campaigns-os-tooling-orientation.v1.schema.json +1 -0
- package/skills/campaign-lifecycle-orientation/SKILL.md +16 -5
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +7 -6
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +7 -6
- package/skills/next-campaigns-os/SKILL.md +7 -7
- package/skills/next-campaigns-os/references/session-intake.md +9 -3
- package/skills/next-campaigns-os-setup/SKILL.md +5 -5
- package/skills/next-campaigns-polish/SKILL.md +28 -9
- package/skills/next-campaigns-qa/SKILL.md +7 -4
- package/skills.json +10 -10
- package/src/brand-theme.mjs +320 -20
- package/src/build-brief.mjs +6 -4
- package/src/built-script-syntax.mjs +480 -0
- package/src/built-site-scope.mjs +16 -4
- package/src/campaigns-api-key.mjs +99 -0
- package/src/cli-helpers.mjs +118 -0
- package/src/cli.mjs +1530 -7580
- package/src/commercial-parity.mjs +48 -2
- package/src/design-source-package.mjs +1 -1
- package/src/design-source-publication.mjs +898 -0
- package/src/deviation.mjs +13 -1
- package/src/diagnostic.mjs +6 -2
- package/src/directory-lock.mjs +270 -0
- package/src/doctor/checks.mjs +4654 -0
- package/src/doctor/inspect.mjs +678 -0
- package/src/doctor/next-step.mjs +731 -0
- package/src/doctor/source-provenance.mjs +184 -0
- package/src/install-invocation.mjs +29 -0
- package/src/invocation.mjs +183 -0
- package/src/live-campaign-refs.mjs +466 -0
- package/src/login.mjs +2 -2
- package/src/page-kit-store-profile.mjs +69 -12
- package/src/page-kit-sync.mjs +31 -12
- package/src/private-template-source.mjs +1 -1
- package/src/progress-node.mjs +9 -36
- package/src/proof-policy.mjs +1 -1
- package/src/qa-analytics-correctness.mjs +3 -0
- package/src/qa-binding-evidence.mjs +76 -11
- package/src/qa-browser.mjs +1316 -105
- package/src/qa-build-scope.mjs +47 -0
- package/src/qa-commercial-parity.mjs +48 -5
- package/src/qa-node.mjs +339 -19
- package/src/qa-test-order-topology.mjs +148 -0
- package/src/sdk-markup.mjs +72 -8
- package/src/source-html-intake.mjs +117 -1
- package/src/source-html-manifest.mjs +9 -2
- package/src/stage-ledger.mjs +28 -0
- package/src/stage-record.mjs +551 -0
- package/src/target-lock.mjs +54 -0
- package/src/template-brand-contract.mjs +17 -1
- package/src/upsell-selector-scope.mjs +112 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { resolveBuiltSiteScope } from "./built-site-scope.mjs";
|
|
2
|
+
import { runtimeRelativeRouteForSpecValue } from "./route-identity.mjs";
|
|
3
|
+
|
|
4
|
+
// Use the recorded declaration, not a doctor's cached derived scope: failed
|
|
5
|
+
// intake also writes skip_reason mappings, which must never hide missing pages.
|
|
6
|
+
export function applyQaBuildScope(topologies, { packet, report, targetRepo, publicRouteSlug } = {}) {
|
|
7
|
+
const declared = report?.stages?.prepare_build?.declared_out_of_scope;
|
|
8
|
+
if (!Array.isArray(declared) || !packet) return { topologies, excludedPages: [] };
|
|
9
|
+
const declaredIds = new Set(declared.map(page => page?.page_id));
|
|
10
|
+
const skippedIds = new Set((packet.source_html?.pages || [])
|
|
11
|
+
.filter(page => declaredIds.has(page.page_id) && !page.path && typeof page.skip_reason === "string" && page.skip_reason.trim())
|
|
12
|
+
.map(page => page.page_id));
|
|
13
|
+
if (!skippedIds.size) return { topologies, excludedPages: [] };
|
|
14
|
+
const built = targetRepo ? resolveBuiltSiteScope(targetRepo, { slug: publicRouteSlug }) : null;
|
|
15
|
+
const normalizeRoute = route => runtimeRelativeRouteForSpecValue(route, publicRouteSlug).replace(/^\/+|\/+$/g, "").replace(/(?:^|\/)index\.html$/, "").replace(/\/$/, "");
|
|
16
|
+
const builtRoutes = new Set((built?.pages || []).map(page => normalizeRoute(page.route)));
|
|
17
|
+
const excludedPages = [];
|
|
18
|
+
const scoped = topologies.map(topology => ({
|
|
19
|
+
...topology,
|
|
20
|
+
partial_build_scope: topology.pages.some(page => skippedIds.has(page.page_id)),
|
|
21
|
+
pages: topology.pages.filter(page => {
|
|
22
|
+
if (!skippedIds.has(page.page_id)) return true;
|
|
23
|
+
// Without a resolved URL we cannot prove which output file represents
|
|
24
|
+
// this page. Keep it in QA so unresolved/materialized pages are not
|
|
25
|
+
// silently hidden as unbuilt declarations.
|
|
26
|
+
if (!page.url) return true;
|
|
27
|
+
// An explicitly materialized stock page rejoins QA. A declaration alone
|
|
28
|
+
// is not an instruction to build it, nor proof that it exists.
|
|
29
|
+
let route;
|
|
30
|
+
try { route = new URL(page.url).pathname; } catch { return true; }
|
|
31
|
+
if (builtRoutes.has(normalizeRoute(route))) return true;
|
|
32
|
+
excludedPages.push(page);
|
|
33
|
+
return false;
|
|
34
|
+
}),
|
|
35
|
+
}));
|
|
36
|
+
return { topologies: scoped, excludedPages };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function specForQaScope(spec, excludedPages = []) {
|
|
40
|
+
const excluded = new Set(excludedPages.map(page => page.page_id));
|
|
41
|
+
if (Array.isArray(spec.funnel_pages) && !spec.funnels?.length) {
|
|
42
|
+
return { ...spec, funnel_pages: spec.funnel_pages.filter(page => !excluded.has(page.id)) };
|
|
43
|
+
}
|
|
44
|
+
return { ...spec, funnels: (spec.funnels || []).map(funnel => ({
|
|
45
|
+
...funnel, pages: (funnel.pages || []).filter(page => !excluded.has(page.id)),
|
|
46
|
+
})) };
|
|
47
|
+
}
|
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
CommercialParityLimitError,
|
|
12
12
|
createCommercialParityReport,
|
|
13
13
|
extractCommercialClaims,
|
|
14
|
+
recurringClaimAbsences,
|
|
15
|
+
recurringClaimAbsentAssertions,
|
|
14
16
|
} from "./commercial-parity.mjs";
|
|
17
|
+
import { extractRenderedPackageRefs } from "./live-campaign-refs.mjs";
|
|
15
18
|
|
|
16
19
|
export const COMMERCIAL_QA_LIMITS = Object.freeze({
|
|
17
20
|
max_html_bytes: 2 * 1024 * 1024,
|
|
@@ -171,10 +174,15 @@ function captureFailure(page, code, error) {
|
|
|
171
174
|
|
|
172
175
|
export function captureCommercialClaims(page, html) {
|
|
173
176
|
try {
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
return {
|
|
178
|
+
...extractCommercialClaims(html, {
|
|
179
|
+
pageId: present(page?.page_id) ? String(page.page_id) : null,
|
|
180
|
+
url: page?.url,
|
|
181
|
+
}),
|
|
182
|
+
// Every package the page renders, by the refs doctor reads, so a
|
|
183
|
+
// subscription whose rebill copy yielded no claim is still seen (#533).
|
|
184
|
+
rendered_package_refs: [...extractRenderedPackageRefs(html)],
|
|
185
|
+
};
|
|
178
186
|
} catch (error) {
|
|
179
187
|
const code = error instanceof CommercialParityLimitError
|
|
180
188
|
? error.code
|
|
@@ -398,6 +406,29 @@ export function planCommercialParity(spec, { maxScenarios = COMMERCIAL_QA_LIMITS
|
|
|
398
406
|
};
|
|
399
407
|
}
|
|
400
408
|
|
|
409
|
+
// A package recurs when the spec says so, or carries a recurring price and a
|
|
410
|
+
// cadence. Page rows override the campaign catalog per ref, as the journey does.
|
|
411
|
+
function isSubscriptionPackage(pkg) {
|
|
412
|
+
return pkg?.is_recurring === true
|
|
413
|
+
|| (present(pkg?.price_recurring) && (present(pkg?.interval_count) || present(pkg?.interval)));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function subscriptionPackageRefsByPage(spec, pages) {
|
|
417
|
+
const packageRef = (pkg) => (present(pkg?.ref_id) ? String(pkg.ref_id) : present(pkg?.package_id) ? String(pkg.package_id) : null);
|
|
418
|
+
const catalog = new Map(array(spec?.packages).map((pkg) => [packageRef(pkg), pkg]).filter(([ref]) => ref));
|
|
419
|
+
const byPage = new Map();
|
|
420
|
+
array(pages).forEach((page) => {
|
|
421
|
+
if (!present(page?.id)) return;
|
|
422
|
+
const refs = new Set();
|
|
423
|
+
array(page.packages).forEach((pkg) => {
|
|
424
|
+
const ref = packageRef(pkg);
|
|
425
|
+
if (ref && isSubscriptionPackage({ ...(catalog.get(ref) || {}), ...pkg })) refs.add(ref);
|
|
426
|
+
});
|
|
427
|
+
if (refs.size) byPage.set(String(page.id), refs);
|
|
428
|
+
});
|
|
429
|
+
return byPage;
|
|
430
|
+
}
|
|
431
|
+
|
|
401
432
|
function countBy(values, keyFor) {
|
|
402
433
|
const counts = {};
|
|
403
434
|
values.forEach((value) => {
|
|
@@ -473,6 +504,7 @@ function compactReport(report, journey, plan, executed, issues, observedClaims,
|
|
|
473
504
|
unresolved_voucher_claims: report.unresolved_voucher_claims,
|
|
474
505
|
serialized_assertion_count: report.serialized_assertion_count,
|
|
475
506
|
omitted_assertion_count: report.omitted_assertion_count,
|
|
507
|
+
recurring_claim_absences: array(report.recurring_claim_absences),
|
|
476
508
|
observed_claims: observedClaims,
|
|
477
509
|
claim_limit: claimLimit,
|
|
478
510
|
finding_count: report.findings.length,
|
|
@@ -539,6 +571,7 @@ export function unavailableCommercialReport(code, { status = "not_run" } = {}) {
|
|
|
539
571
|
unresolved_voucher_claims: 0,
|
|
540
572
|
serialized_assertion_count: 0,
|
|
541
573
|
omitted_assertion_count: 0,
|
|
574
|
+
recurring_claim_absences: [],
|
|
542
575
|
observed_claims: 0,
|
|
543
576
|
claim_limit: COMMERCIAL_QA_LIMITS.max_aggregate_claims,
|
|
544
577
|
finding_count: 0,
|
|
@@ -625,8 +658,18 @@ export async function runCommercialParity({
|
|
|
625
658
|
maxAssertions,
|
|
626
659
|
countsOnly: aggregateClaimOverflow,
|
|
627
660
|
});
|
|
661
|
+
// Subscription packages a page renders with no recurring claim read for
|
|
662
|
+
// them: incomplete evidence (an issue), and a warning naming the package.
|
|
663
|
+
const absences = aggregateClaimOverflow
|
|
664
|
+
? []
|
|
665
|
+
: recurringClaimAbsences(captures, subscriptionPackageRefsByPage(planningSpec, commercialPlanning.pages));
|
|
666
|
+
absences.forEach(() => issues.push({ code: "recurring_claim_absent" }));
|
|
667
|
+
const absentAssertions = recurringClaimAbsentAssertions(absences, {
|
|
668
|
+
maxAssertions: Math.max(0, maxAssertions - parity.assertions.length),
|
|
669
|
+
});
|
|
670
|
+
parity.recurring_claim_absences = absences;
|
|
628
671
|
return {
|
|
629
|
-
assertions: parity.assertions,
|
|
672
|
+
assertions: [...parity.assertions, ...absentAssertions],
|
|
630
673
|
commercial: compactReport(
|
|
631
674
|
parity,
|
|
632
675
|
parityJourney,
|