@nextcommerce/campaigns-os 1.50.0 → 1.53.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 +474 -0
- package/agents/claude/CLAUDE.md +4 -2
- package/agents/codex/AGENTS.md +3 -1
- package/agents/copilot/copilot-instructions.md +3 -1
- package/agents/cursor/campaigns-os.mdc +3 -1
- 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 +282 -14
- package/contracts/release-ledger.json +1233 -0
- package/contracts/supported-surface.json +2 -2
- 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 +2 -0
- package/docs/build-packet.md +148 -23
- package/docs/campaign-build-brief.md +57 -2
- package/docs/effects.md +14 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +12 -0
- package/docs/progress-snapshots.md +2 -4
- package/docs/qa-and-test-orders.md +47 -4
- package/docs/readback.md +19 -2
- 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/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 +5 -3
- package/skills/next-campaigns-os/SKILL.md +11 -20
- package/skills/next-campaigns-os/references/session-intake.md +17 -9
- package/skills/next-campaigns-os-setup/SKILL.md +4 -3
- package/skills/next-campaigns-polish/SKILL.md +7 -5
- package/skills/next-campaigns-qa/SKILL.md +8 -5
- package/skills.json +10 -10
- package/src/adapter-decision-contract.mjs +1 -1
- package/src/brand-theme.mjs +33 -20
- package/src/brief-summary.mjs +275 -0
- package/src/build-brief.mjs +229 -30
- 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 +467 -143
- package/src/commercial-journey.mjs +65 -4
- package/src/commercial-parity.mjs +6 -1
- package/src/contrast.mjs +649 -0
- package/src/design-source-publication.mjs +110 -3
- package/src/doctor/checks.mjs +415 -33
- package/src/doctor/inspect.mjs +53 -2
- package/src/doctor/next-step.mjs +16 -7
- package/src/input-currency.mjs +394 -0
- package/src/input-refresh.mjs +708 -0
- 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 +660 -141
- package/src/polish-capture.mjs +1 -1
- package/src/polish-deadline.mjs +7 -0
- package/src/polish-media-weight.mjs +492 -0
- package/src/polish-node.mjs +400 -28
- package/src/polish-readability.mjs +828 -0
- package/src/progress-node.mjs +19 -3
- package/src/qa-browser.mjs +439 -180
- package/src/qa-content-params.mjs +889 -0
- package/src/qa-node.mjs +116 -13
- 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 +482 -0
- package/src/qc-check-registry.mjs +91 -0
- package/src/qc-results.mjs +1341 -0
- package/src/readback.mjs +58 -10
- 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-ledger.mjs +235 -44
- package/src/stage-record.mjs +165 -33
- package/src/tooling-setup.mjs +1 -1
|
@@ -7,7 +7,11 @@ import { writeThemeArtifacts } from "./brand-theme.mjs";
|
|
|
7
7
|
import { cloneJson, filesystemPathsMatch, isObject, isNonEmptyString, optionalString, readJson, resolveFromFile, sha256File } from "./cli-helpers.mjs";
|
|
8
8
|
import { DESIGN_SOURCE_PACKAGE_REL_PATH, createDesignSourcePackageArtifactReference, hashSerializedDesignSourcePackage, serializeDesignSourcePackage, synthesizeHtmlFunnelDesignSourcePackage, validateDesignSourcePackage } from "./design-source-package.mjs";
|
|
9
9
|
import { stampDoctorProducer } from "./doctor-sidecar.mjs";
|
|
10
|
+
import { stageWriteInputs } from "./input-currency.mjs";
|
|
11
|
+
import { cmd } from "./install-invocation.mjs";
|
|
10
12
|
import { ASSEMBLY_REPORT_STAGE_KEYS } from "./orchestration-stage-contract.mjs";
|
|
13
|
+
import { HOST_STRIPPED_CODE } from "./source-html-intake.mjs";
|
|
14
|
+
import { archiveForForceReset, stageHistoryPresent } from "./stage-ledger.mjs";
|
|
11
15
|
import { targetLockPath, withTargetLock } from "./target-lock.mjs";
|
|
12
16
|
import { resolveTemplateFamilyDesignSource } from "./template-reference.mjs";
|
|
13
17
|
|
|
@@ -71,12 +75,93 @@ function guardAssemblyReportOverwrite(reportPath, args, { announced = null } = {
|
|
|
71
75
|
const unannounced = announced ? stageKeys.filter((key) => !announced.includes(key)) : stageKeys;
|
|
72
76
|
if (unannounced.length > 0) {
|
|
73
77
|
console.warn(
|
|
74
|
-
`[campaigns-os prepare-build] --force: overwriting assembly report at ${reportPath}; clearing stage evidence for: ${unannounced.join(", ")}.`,
|
|
78
|
+
`[campaigns-os prepare-build] --force: overwriting assembly report at ${reportPath}; clearing stage evidence for: ${unannounced.join(", ")}. Completed build, Polish and QA records are archived into their stage history, which is kept.`,
|
|
75
79
|
);
|
|
76
80
|
}
|
|
77
81
|
return stageKeys;
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
// Operator state on the report an intake would replace, one line per item:
|
|
85
|
+
// recorded waivers and warning accepts, stage history, an applied theme, and
|
|
86
|
+
// report evidence other than intake's own host-strip lines.
|
|
87
|
+
function assemblyReportOperatorState(existingReport) {
|
|
88
|
+
if (!isObject(existingReport)) return [];
|
|
89
|
+
const items = [];
|
|
90
|
+
const count = (value) => (Array.isArray(value) ? value.length : 0);
|
|
91
|
+
if (count(existingReport.waivers)) items.push(`waivers (${count(existingReport.waivers)} recorded waiver${count(existingReport.waivers) === 1 ? "" : "s"})`);
|
|
92
|
+
if (count(existingReport.qc_accepts)) items.push(`qc_accepts (${count(existingReport.qc_accepts)} recorded warning accept${count(existingReport.qc_accepts) === 1 ? "" : "s"})`);
|
|
93
|
+
if (stageHistoryPresent(existingReport)) items.push("stage history (superseded build, Polish or QA records)");
|
|
94
|
+
if (existingReport.theme?.status === "applied") items.push("theme (status applied)");
|
|
95
|
+
const evidence = Array.isArray(existingReport.evidence) ? existingReport.evidence.filter((entry) => !(isObject(entry) && entry.code === HOST_STRIPPED_CODE)) : [];
|
|
96
|
+
if (evidence.length) items.push(`evidence (${evidence.length} recorded line${evidence.length === 1 ? "" : "s"})`);
|
|
97
|
+
return items;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Packet settings recorded since the last intake that this run would
|
|
101
|
+
// overwrite (every setting `qa policy set` records): each named with the
|
|
102
|
+
// value on disk and the value this run writes.
|
|
103
|
+
const INTAKE_PACKET_SETTINGS = Object.freeze([
|
|
104
|
+
["deploy.target", (packet) => packet?.deploy?.target],
|
|
105
|
+
["deploy.preview_url", (packet) => packet?.deploy?.preview_url],
|
|
106
|
+
["deploy.production_url", (packet) => packet?.deploy?.production_url],
|
|
107
|
+
["qa.proof_policy.order_path_depth", (packet) => packet?.qa?.proof_policy?.order_path_depth],
|
|
108
|
+
["campaign.allowed_domains_confirmed", (packet) => packet?.campaign?.allowed_domains_confirmed],
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
function packetSettingChanges(packetPath, planned) {
|
|
112
|
+
if (!isObject(planned) || !existsSync(packetPath)) return [];
|
|
113
|
+
let existing;
|
|
114
|
+
try {
|
|
115
|
+
existing = readJson(packetPath);
|
|
116
|
+
} catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const items = [];
|
|
120
|
+
for (const [field, read] of INTAKE_PACKET_SETTINGS) {
|
|
121
|
+
const onDisk = read(existing) ?? null;
|
|
122
|
+
const written = read(planned) ?? null;
|
|
123
|
+
if (onDisk !== written) items.push(`${field} (recorded ${JSON.stringify(onDisk)}; this run would write ${JSON.stringify(written)})`);
|
|
124
|
+
}
|
|
125
|
+
return items;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Intake replaces the report and packet whole, so recorded operator state
|
|
129
|
+
// would be lost silently. Without --force it refuses and names each item;
|
|
130
|
+
// with --force it names each item as cleared (once).
|
|
131
|
+
function guardIntakeOperatorState(reportPath, packetPath, { force, planned = null, announced = [] }) {
|
|
132
|
+
let existingReport = null;
|
|
133
|
+
try {
|
|
134
|
+
existingReport = existsSync(reportPath) ? readJson(reportPath) : null;
|
|
135
|
+
} catch {
|
|
136
|
+
existingReport = null;
|
|
137
|
+
}
|
|
138
|
+
const items = [...assemblyReportOperatorState(existingReport), ...packetSettingChanges(packetPath, planned)];
|
|
139
|
+
if (!items.length) return announced;
|
|
140
|
+
if (force !== true) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Rerunning prepare-build/start/build would discard recorded operator state: ${items.join("; ")}. `
|
|
143
|
+
+ `To save brief answers or a CampaignSpec change without discarding anything, run ${cmd("record")} brief or ${cmd("record")} spec instead. `
|
|
144
|
+
+ "To rerun intake anyway, pass the original deploy and order-path flags, or --force to clear the listed state (destructive; completed build, Polish and QA records are archived into stage history). "
|
|
145
|
+
+ `After --force, record a cleared packet setting again with ${cmd("qa")} policy set.`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const unannounced = items.filter((item) => !announced.includes(item));
|
|
149
|
+
if (unannounced.length) {
|
|
150
|
+
console.warn(`[campaigns-os prepare-build] --force: clearing recorded operator state: ${unannounced.join("; ")}.`);
|
|
151
|
+
}
|
|
152
|
+
return [...announced, ...unannounced];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The report this run replaces, read for the --force archive; null when there
|
|
156
|
+
// is none or it cannot be read.
|
|
157
|
+
function readReplacedReport(reportPath) {
|
|
158
|
+
try {
|
|
159
|
+
return existsSync(reportPath) ? readJson(reportPath) : null;
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
80
165
|
function artifactRelativePath(artifactPath, targetPath) {
|
|
81
166
|
const rel = relative(dirname(resolve(artifactPath)), resolve(targetPath)).replaceAll("\\", "/");
|
|
82
167
|
return rel || ".";
|
|
@@ -806,12 +891,20 @@ export async function withDesignSourcePublication({ targetRepo, outputs, force =
|
|
|
806
891
|
// fetched, and a run waiting here cannot replace it meanwhile.
|
|
807
892
|
publishSpec?.();
|
|
808
893
|
let announcedStageEvidence = guardAssemblyReportOverwrite(reportPath, { force });
|
|
894
|
+
// Operator state is checked once the run knows the packet settings it
|
|
895
|
+
// would write, before the package.
|
|
896
|
+
let plannedPacketSettings = null;
|
|
897
|
+
let announcedOperatorState = null;
|
|
898
|
+
const checkOperatorState = () => {
|
|
899
|
+
announcedOperatorState = guardIntakeOperatorState(reportPath, packetPath, { force, planned: plannedPacketSettings, announced: announcedOperatorState || [] });
|
|
900
|
+
};
|
|
809
901
|
// Stage producers commit the report without this lock, so evidence can land
|
|
810
902
|
// while the run works. Re-check before each write that is not rolled back:
|
|
811
903
|
// the theme artifacts, then the JSON outputs. --force names a stage once.
|
|
812
904
|
const recheckStageEvidence = () => {
|
|
813
905
|
const found = guardAssemblyReportOverwrite(reportPath, { force }, { announced: announcedStageEvidence });
|
|
814
906
|
announcedStageEvidence = [...new Set([...announcedStageEvidence, ...found])];
|
|
907
|
+
checkOperatorState();
|
|
815
908
|
};
|
|
816
909
|
let completed = 0;
|
|
817
910
|
const enter = (index, { once = false } = {}) => {
|
|
@@ -821,8 +914,13 @@ export async function withDesignSourcePublication({ targetRepo, outputs, force =
|
|
|
821
914
|
let designSourcePackage = null;
|
|
822
915
|
return fn(Object.freeze({
|
|
823
916
|
paths,
|
|
824
|
-
|
|
917
|
+
// `plannedPacket`: the part of the packet this run would write that
|
|
918
|
+
// carries the settings INTAKE_PACKET_SETTINGS names, checked against
|
|
919
|
+
// the packet on disk.
|
|
920
|
+
package({ plannedPacket = null, ...inputs }) {
|
|
825
921
|
enter(0, { once: true });
|
|
922
|
+
plannedPacketSettings = plannedPacket;
|
|
923
|
+
checkOperatorState();
|
|
826
924
|
// Reject deterministic destination failures before publishing the fixed
|
|
827
925
|
// DSP. The DSP intentionally precedes the theme and JSON artifacts that
|
|
828
926
|
// reference it; a later I/O race may therefore leave a valid DSP for a
|
|
@@ -858,8 +956,17 @@ export async function withDesignSourcePublication({ targetRepo, outputs, force =
|
|
|
858
956
|
completed = Math.max(completed, 2);
|
|
859
957
|
return written;
|
|
860
958
|
},
|
|
861
|
-
publish({ packet, brief, context, report }) {
|
|
959
|
+
publish({ packet, brief, context, report: reseeded }) {
|
|
862
960
|
enter(2, { once: true });
|
|
961
|
+
// The records this run replaces stay visible: completed build, Polish
|
|
962
|
+
// and QA records go to their stage history (only --force reaches here
|
|
963
|
+
// with any), and each stage's history and input change carry over.
|
|
964
|
+
const now = new Date().toISOString();
|
|
965
|
+
const bound = { briefMaterial: reseeded?.build_brief?.material ?? null, specMaterial: reseeded?.identity?.spec_material_hash ?? null };
|
|
966
|
+
const report = archiveForForceReset(readReplacedReport(reportPath), reseeded, {
|
|
967
|
+
now,
|
|
968
|
+
detectChange: (key, record) => stageWriteInputs(key, bound).detectChange(record),
|
|
969
|
+
});
|
|
863
970
|
// Before the report goes out, narrow the pending record to exactly what the
|
|
864
971
|
// report will say: the package's own hash when it is synthesized, nothing
|
|
865
972
|
// when it is adopted. A candidate this run or an earlier failed one never
|