@nextcommerce/campaigns-os 1.43.2 → 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 +5 -0
- package/CHANGELOG.md +648 -5103
- package/README.md +32 -11
- 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/archive/CHANGELOG.2026-09-30.md +5111 -0
- package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
- package/contracts/effects.v1.json +1176 -113
- package/contracts/orientation-reason-codes.v1.json +7 -0
- package/contracts/release-ledger.json +2345 -6087
- 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 +158 -21
- package/docs/campaigns-os-build-flow.md +3 -3
- package/docs/design-source-package.md +73 -0
- package/docs/effects.md +50 -8
- 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/qa-and-test-orders.md +99 -13
- package/docs/release-ledger-authoring-guide.md +64 -4
- 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/built-site-scope.mjs +16 -4
- package/src/cli.mjs +280 -46
- package/src/commercial-parity.mjs +48 -2
- package/src/deviation.mjs +13 -1
- package/src/diagnostic.mjs +5 -2
- package/src/doctor/checks.mjs +320 -81
- package/src/doctor/inspect.mjs +55 -13
- package/src/doctor/source-provenance.mjs +184 -0
- package/src/invocation.mjs +4 -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/progress-node.mjs +3 -1
- package/src/qa-browser.mjs +538 -28
- package/src/qa-commercial-parity.mjs +48 -5
- package/src/qa-node.mjs +122 -7
- package/src/qa-test-order-topology.mjs +148 -0
- package/src/sdk-markup.mjs +72 -8
- package/src/source-html-intake.mjs +116 -0
- package/src/stage-record.mjs +551 -0
- package/src/upsell-selector-scope.mjs +112 -2
package/src/page-kit-sync.mjs
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// every other file are left as they are. This module is pure: the CLI does
|
|
15
15
|
// the reading, the writing, and the printing.
|
|
16
16
|
import {
|
|
17
|
+
isAuthoritativeEmptyStoreProfileValue,
|
|
17
18
|
isDemoResidue,
|
|
18
19
|
normalizeStoreProfileValue,
|
|
19
20
|
PAGE_KIT_STORE_PROFILE_FIELDS,
|
|
@@ -25,17 +26,23 @@ export const PAGE_KIT_SYNC_FIELDS = Object.freeze([...PAGE_KIT_STORE_PROFILE_FIE
|
|
|
25
26
|
|
|
26
27
|
// The field-by-field plan: what the entry holds, what the spec says, and
|
|
27
28
|
// whether a write is owed. `changes` are the fields whose value will move,
|
|
28
|
-
// `unchanged` already match, `not_in_spec` are governed fields
|
|
29
|
-
// not carry
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// shape (or the demo value itself),
|
|
33
|
-
//
|
|
29
|
+
// `unchanged` already match, `not_in_spec` are governed fields left as they
|
|
30
|
+
// are because the spec does not carry them or because its "" was not applied
|
|
31
|
+
// (doctor's `target_only` warning still applies), and `not_synced` are fields
|
|
32
|
+
// the target cannot be made authoritative for: an invalid or conflicting spec
|
|
33
|
+
// SDK pin, a spec value of the wrong type or shape (or the demo value itself),
|
|
34
|
+
// or starter demo residue in a field the spec does not carry. Absent and null
|
|
35
|
+
// spec values are "not carried"; an explicit empty (or whitespace-only) string
|
|
36
|
+
// blanks the starter demo value and otherwise leaves the target as it is.
|
|
37
|
+
// `spec_empty_not_applied` names that last case, the not_in_spec fields the
|
|
38
|
+
// spec sets to "" over a real, non-demo target value, which only a hand edit
|
|
39
|
+
// removes.
|
|
34
40
|
export function planPageKitSync({ spec, entry, waivedGates = [] } = {}) {
|
|
35
41
|
const target = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : {};
|
|
36
42
|
const changes = [];
|
|
37
43
|
const unchanged = [];
|
|
38
44
|
const notInSpec = [];
|
|
45
|
+
const specEmptyNotApplied = [];
|
|
39
46
|
const notSynced = [];
|
|
40
47
|
// A gate under an ACTIVE named-human waiver recorded a human accepting the
|
|
41
48
|
// target's current values; sync must not silently reverse that decision.
|
|
@@ -61,8 +68,15 @@ export function planPageKitSync({ spec, entry, waivedGates = [] } = {}) {
|
|
|
61
68
|
for (const field of PAGE_KIT_STORE_PROFILE_FIELDS) {
|
|
62
69
|
const raw = spec?.campaign?.[field];
|
|
63
70
|
const current = Object.hasOwn(target, field) ? target[field] : undefined;
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
// An explicit empty value blanks only the starter demo value, and confirms
|
|
72
|
+
// a target that already reads as empty. Any other target value is left as
|
|
73
|
+
// it is, as for a field the spec does not carry (doctor's target_only
|
|
74
|
+
// warning): Maps saved "" for every cleared store field before "" meant
|
|
75
|
+
// empty, so it never wipes a value someone entered.
|
|
76
|
+
const authoritativeEmpty = isAuthoritativeEmptyStoreProfileValue(field, raw);
|
|
77
|
+
const targetBlankOrDemo = current === undefined || current === null
|
|
78
|
+
|| (typeof current === "string" && (!normalizeStoreProfileValue(current) || isDemoResidue(field, normalizeStoreProfileValue(current))));
|
|
79
|
+
if (raw === undefined || raw === null || (authoritativeEmpty && !targetBlankOrDemo)) {
|
|
66
80
|
// Starter demo residue in a field the spec does not carry is the one
|
|
67
81
|
// state sync cannot end: doctor blocks on it without a waiver and there
|
|
68
82
|
// is no spec value to write over it. Say so instead of reporting a
|
|
@@ -75,13 +89,16 @@ export function planPageKitSync({ spec, entry, waivedGates = [] } = {}) {
|
|
|
75
89
|
});
|
|
76
90
|
} else {
|
|
77
91
|
notInSpec.push(field);
|
|
92
|
+
if (authoritativeEmpty) specEmptyNotApplied.push(field);
|
|
78
93
|
}
|
|
79
94
|
continue;
|
|
80
95
|
}
|
|
81
96
|
// A carried value the target cannot be made authoritative for: the wrong
|
|
82
97
|
// type (doctor's spec_invalid_type), the demo value itself, or a shape a
|
|
83
98
|
// template would put into an href unescaped.
|
|
84
|
-
|
|
99
|
+
// An explicit empty value reaching here is written as "" over the starter
|
|
100
|
+
// demo value.
|
|
101
|
+
const problem = typeof raw !== "string" ? "spec_invalid_type" : authoritativeEmpty ? null : storeProfileSpecValueProblem(field, raw);
|
|
85
102
|
if (problem) {
|
|
86
103
|
notSynced.push({ field, reason: problem, detail: NOT_SYNCED_DETAIL[problem](field, raw) });
|
|
87
104
|
continue;
|
|
@@ -91,9 +108,11 @@ export function planPageKitSync({ spec, entry, waivedGates = [] } = {}) {
|
|
|
91
108
|
const row = { field, before, after, source: `campaign.${field}` };
|
|
92
109
|
// The gate compares normalized forms, so a target that differs only in
|
|
93
110
|
// surrounding whitespace or Unicode normalization already passes; a
|
|
94
|
-
// rewrite would report a change doctor never saw.
|
|
111
|
+
// rewrite would report a change doctor never saw. For the same reason an
|
|
112
|
+
// absent or null target already agrees with an explicit empty value.
|
|
95
113
|
const alreadyMatches = before === after
|
|
96
|
-
|| (typeof before === "string" && normalizeStoreProfileValue(before) === after)
|
|
114
|
+
|| (typeof before === "string" && normalizeStoreProfileValue(before) === after)
|
|
115
|
+
|| (authoritativeEmpty && (before === undefined || before === null));
|
|
97
116
|
if (alreadyMatches) unchanged.push(row);
|
|
98
117
|
else if (storeProfileWaiver) notSynced.push({ field, reason: "waived", detail: waivedDetail(field, storeProfileWaiver, "page_kit.store_profile") });
|
|
99
118
|
else changes.push(row);
|
|
@@ -130,7 +149,7 @@ export function planPageKitSync({ spec, entry, waivedGates = [] } = {}) {
|
|
|
130
149
|
});
|
|
131
150
|
}
|
|
132
151
|
|
|
133
|
-
return { changes, unchanged, not_in_spec: notInSpec, not_synced: notSynced };
|
|
152
|
+
return { changes, unchanged, not_in_spec: notInSpec, spec_empty_not_applied: specEmptyNotApplied, not_synced: notSynced };
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
// Apply a plan to the parsed campaigns.json document. Mutates ONLY the
|
package/src/progress-node.mjs
CHANGED
|
@@ -11,6 +11,8 @@ import {resolveConsent,CANONICAL_REMIT_SCOPE,normalizeConsentScope,announceDefau
|
|
|
11
11
|
import {boundedResponseText,isLoopbackHostname} from './remit.mjs';
|
|
12
12
|
export const PROGRESS_OBSERVATION = Symbol('canonical progress observation');
|
|
13
13
|
export const PROGRESS_ENDPOINT = '/api/progress';
|
|
14
|
+
// A projection keeps the first PROGRESS_GATE_LIMIT continuation gates (the snapshot schema's gates maxItems).
|
|
15
|
+
export const PROGRESS_GATE_LIMIT = 16;
|
|
14
16
|
const accepted = (value,values,fallback='unknown')=>values.includes(value)?value:fallback;
|
|
15
17
|
const hash = value=>typeof value==='string'&&/^(?:sha256:)?[0-9a-f]{64}$/i.test(value)?`sha256:${value.replace(/^sha256:/i,'').toLowerCase()}`:null;
|
|
16
18
|
const id = value=>typeof value==='string'&&/^[A-Za-z0-9_-]{1,64}$/.test(value)?value:null;
|
|
@@ -56,7 +58,7 @@ export function projectProgressObservation({workspace,context,report,doctor,cont
|
|
|
56
58
|
binding:reportBound&&id(verdict.run_id)&&id(verdict.run_id)===id(report?.stages?.qa?.verdict_run_id)&&build&&qaSource===build&&doctor?.derived?.build_output_fingerprint?.status==='pass'&&hash(verdict.spec_hash)===localHash?'matching':'unconfirmed',
|
|
57
59
|
publish_state:accepted(qaResult?.qa_verdict_publish?.state,['skipped','ok','failed']),
|
|
58
60
|
}:null;
|
|
59
|
-
const gates=(Array.isArray(continuation?.gates)?continuation.gates:[]).slice(0,
|
|
61
|
+
const gates=(Array.isArray(continuation?.gates)?continuation.gates:[]).slice(0,PROGRESS_GATE_LIMIT).map(gate=>({id:accepted(gate?.id,PROGRESS_GATE_IDS),state:PROGRESS_GATE_IDS.includes(gate?.id)?accepted(gate?.status,['pass','blocked','waived','not_applicable']):'unknown'}));
|
|
60
62
|
const actions=[...new Set((Array.isArray(continuation?.next_actions)?continuation.next_actions:[]).slice(0,64).map(action=>accepted(action?.id,PROGRESS_ACTION_IDS)))];
|
|
61
63
|
const stage=accepted(continuation?.stage,PROGRESS_CONTINUATIONS);
|
|
62
64
|
const preview=typeof packet?.deploy?.preview_url==='string'&&packet.deploy.preview_url?packet.deploy.preview_url:null;
|