@nextcommerce/campaigns-os 1.33.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 +204 -0
- package/CHANGELOG.md +5002 -0
- package/CONTEXT.md +685 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +368 -0
- package/agents/claude/CLAUDE.md +32 -0
- package/agents/codex/AGENTS.md +27 -0
- package/agents/copilot/copilot-instructions.md +14 -0
- package/agents/cursor/campaigns-os.mdc +13 -0
- package/bin/campaigns-os.mjs +38 -0
- package/campaign-spec/README.md +138 -0
- package/campaign-spec/dist/analytics-vocabulary.d.ts +47 -0
- package/campaign-spec/dist/analytics-vocabulary.js +74 -0
- package/campaign-spec/dist/index.d.ts +40 -0
- package/campaign-spec/dist/index.js +77 -0
- package/campaign-spec/dist/normalize.d.ts +22 -0
- package/campaign-spec/dist/normalize.js +41 -0
- package/campaign-spec/dist/routing.d.ts +190 -0
- package/campaign-spec/dist/routing.js +263 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +42 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.js +303 -0
- package/campaign-spec/dist/rules/assembly-hints-shape.d.ts +42 -0
- package/campaign-spec/dist/rules/assembly-hints-shape.js +191 -0
- package/campaign-spec/dist/rules/campaign-metadata.d.ts +14 -0
- package/campaign-spec/dist/rules/campaign-metadata.js +40 -0
- package/campaign-spec/dist/rules/checkout-has-success-url.d.ts +31 -0
- package/campaign-spec/dist/rules/checkout-has-success-url.js +66 -0
- package/campaign-spec/dist/rules/cycle-detection.d.ts +12 -0
- package/campaign-spec/dist/rules/cycle-detection.js +141 -0
- package/campaign-spec/dist/rules/design-source-shape.d.ts +29 -0
- package/campaign-spec/dist/rules/design-source-shape.js +142 -0
- package/campaign-spec/dist/rules/downsell-without-upsell.d.ts +11 -0
- package/campaign-spec/dist/rules/downsell-without-upsell.js +40 -0
- package/campaign-spec/dist/rules/exit-intent-validation.d.ts +23 -0
- package/campaign-spec/dist/rules/exit-intent-validation.js +147 -0
- package/campaign-spec/dist/rules/funnel-count.d.ts +9 -0
- package/campaign-spec/dist/rules/funnel-count.js +37 -0
- package/campaign-spec/dist/rules/funnel-hypothesis-length.d.ts +22 -0
- package/campaign-spec/dist/rules/funnel-hypothesis-length.js +60 -0
- package/campaign-spec/dist/rules/funnel-identity.d.ts +15 -0
- package/campaign-spec/dist/rules/funnel-identity.js +57 -0
- package/campaign-spec/dist/rules/funnel-weight-sum.d.ts +20 -0
- package/campaign-spec/dist/rules/funnel-weight-sum.js +66 -0
- package/campaign-spec/dist/rules/index.d.ts +69 -0
- package/campaign-spec/dist/rules/index.js +131 -0
- package/campaign-spec/dist/rules/offer-ref-integrity.d.ts +13 -0
- package/campaign-spec/dist/rules/offer-ref-integrity.js +59 -0
- package/campaign-spec/dist/rules/package-pricing-sanity.d.ts +12 -0
- package/campaign-spec/dist/rules/package-pricing-sanity.js +42 -0
- package/campaign-spec/dist/rules/page-count.d.ts +11 -0
- package/campaign-spec/dist/rules/page-count.js +31 -0
- package/campaign-spec/dist/rules/page-id-uniqueness.d.ts +14 -0
- package/campaign-spec/dist/rules/page-id-uniqueness.js +47 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.d.ts +8 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.js +126 -0
- package/campaign-spec/dist/rules/promo-codes-shape.d.ts +30 -0
- package/campaign-spec/dist/rules/promo-codes-shape.js +187 -0
- package/campaign-spec/dist/rules/route-field-ignored-for-page-type.d.ts +33 -0
- package/campaign-spec/dist/rules/route-field-ignored-for-page-type.js +81 -0
- package/campaign-spec/dist/rules/route-target-resolves.d.ts +32 -0
- package/campaign-spec/dist/rules/route-target-resolves.js +112 -0
- package/campaign-spec/dist/rules/schema-version.d.ts +21 -0
- package/campaign-spec/dist/rules/schema-version.js +53 -0
- package/campaign-spec/dist/rules/sdk-version.d.ts +26 -0
- package/campaign-spec/dist/rules/sdk-version.js +96 -0
- package/campaign-spec/dist/rules/shipping-countries-shape.d.ts +10 -0
- package/campaign-spec/dist/rules/shipping-countries-shape.js +30 -0
- package/campaign-spec/dist/rules/shipping-methods-present.d.ts +10 -0
- package/campaign-spec/dist/rules/shipping-methods-present.js +26 -0
- package/campaign-spec/dist/rules/store-profile-shape.d.ts +30 -0
- package/campaign-spec/dist/rules/store-profile-shape.js +127 -0
- package/campaign-spec/dist/rules/thank-you-requirement.d.ts +16 -0
- package/campaign-spec/dist/rules/thank-you-requirement.js +50 -0
- package/campaign-spec/dist/rules/unknown-top-level-fields.d.ts +28 -0
- package/campaign-spec/dist/rules/unknown-top-level-fields.js +114 -0
- package/campaign-spec/dist/rules/upsell-has-packages.d.ts +9 -0
- package/campaign-spec/dist/rules/upsell-has-packages.js +35 -0
- package/campaign-spec/dist/rules/upsell-routing-complete.d.ts +10 -0
- package/campaign-spec/dist/rules/upsell-routing-complete.js +45 -0
- package/campaign-spec/dist/rules/upsell-without-checkout.d.ts +11 -0
- package/campaign-spec/dist/rules/upsell-without-checkout.js +44 -0
- package/campaign-spec/dist/rules/variant-labels-shape.d.ts +28 -0
- package/campaign-spec/dist/rules/variant-labels-shape.js +86 -0
- package/campaign-spec/dist/sdk-version-parse.d.ts +43 -0
- package/campaign-spec/dist/sdk-version-parse.js +62 -0
- package/campaign-spec/dist/types.d.ts +674 -0
- package/campaign-spec/dist/types.js +40 -0
- package/campaign-spec/package.json +12 -0
- package/compatibility.json +25 -0
- package/contracts/agent-relevant-change-policy.v1.json +111 -0
- package/contracts/brand-theme-source-defaults.figma-sections-export.v0.json +32 -0
- package/contracts/brand-theme-target-tokens.next-core.v0.json +65 -0
- package/contracts/campaign-cart-checkout-field-contract.v0.json +45 -0
- package/contracts/campaign-cart-sdk-support-policy.v0.json +11 -0
- package/contracts/commerce-surface-catalog.json +2452 -0
- package/contracts/fixtures/orientation/canonicalization/v1.json +34 -0
- package/contracts/fixtures/orientation/envelope/current.json +95 -0
- package/contracts/fixtures/orientation/envelope/freshness_unknown.json +97 -0
- package/contracts/fixtures/orientation/envelope/legacy_baseline.json +95 -0
- package/contracts/fixtures/orientation/envelope/orientation_available.json +136 -0
- package/contracts/fixtures/orientation/envelope/recovered_interrupted_update.json +136 -0
- package/contracts/fixtures/orientation/envelope/refused.json +100 -0
- package/contracts/fixtures/orientation/envelope/restart_required.json +137 -0
- package/contracts/fixtures/orientation/envelope/updated.json +135 -0
- package/contracts/fixtures/orientation/hostile-target/README.md +61 -0
- package/contracts/fixtures/orientation/hostile-target/manifest.json +82 -0
- package/contracts/fixtures/orientation/hostile-target/repo/CHANGELOG.md +18 -0
- package/contracts/fixtures/orientation/hostile-target/repo/bin/intended.mjs +12 -0
- package/contracts/fixtures/orientation/hostile-target/repo/bin/tripwire.mjs +15 -0
- package/contracts/fixtures/orientation/hostile-target/repo/contracts/release-ledger.json +48 -0
- package/contracts/fixtures/orientation/hostile-target/repo/contracts/supported-surface.json +17 -0
- package/contracts/fixtures/orientation/hostile-target/repo/docs/example-contract.md +13 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-checkout +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-merge +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/pre-commit +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/package.json +15 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/tripwire.mjs +9 -0
- package/contracts/fixtures/orientation/hostile-target/repo/package.json +20 -0
- package/contracts/fixtures/orientation/hostile-target/repo/schemas/example.v0.schema.json +15 -0
- package/contracts/fixtures/orientation/release-gate/cases.json +1073 -0
- package/contracts/fixtures/runtime-recipe/accept/current.json +299 -0
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +294 -0
- package/contracts/fixtures/runtime-recipe/dist-states.json +51 -0
- package/contracts/fixtures/runtime-recipe/manifest.json +85 -0
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +297 -0
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +251 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +310 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +299 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/assembly-report.json +180 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/build-context.json +115 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/doctor-output.json +29 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/qa-verdict.json +27 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/campaign-runtime.build.json +141 -0
- package/contracts/migration-sidecar-bundle.v0.json +149 -0
- package/contracts/orientation-limits.v1.json +41 -0
- package/contracts/orientation-reason-codes.v1.json +196 -0
- package/contracts/private-template-sources.json +8 -0
- package/contracts/release-ledger.json +4598 -0
- package/contracts/reserved-skill-names.json +13 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +299 -0
- package/contracts/supported-surface.json +180 -0
- package/contracts/template-brand-contract.apollo-mv-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.apollo.v0.json +27 -0
- package/contracts/template-brand-contract.demeter.v0.json +27 -0
- package/contracts/template-brand-contract.olympus-mv-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.olympus-mv-two-step.v0.json +28 -0
- package/contracts/template-brand-contract.olympus.v0.json +27 -0
- package/contracts/template-brand-contract.shared-commerce.v0.json +190 -0
- package/contracts/template-brand-contract.shop-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.shop-three-step.v0.json +29 -0
- package/contracts/template-slot-manifest.apollo-mv-single-step.v0.json +14 -0
- package/contracts/template-slot-manifest.apollo.v0.json +12 -0
- package/contracts/template-slot-manifest.demeter.v0.json +30 -0
- package/contracts/template-slot-manifest.olympus-mv-single-step.v0.json +14 -0
- package/contracts/template-slot-manifest.olympus-mv-two-step.v0.json +15 -0
- package/contracts/template-slot-manifest.olympus.v0.json +12 -0
- package/contracts/template-slot-manifest.shared-content-core.v0.json +4254 -0
- package/contracts/template-slot-manifest.shop-single-step.v0.json +47 -0
- package/contracts/template-slot-manifest.shop-three-step.v0.json +33 -0
- package/docs/brand-theme-bridge.md +159 -0
- package/docs/build-packet.md +1300 -0
- package/docs/campaign-build-brief.md +145 -0
- package/docs/campaign-standardization-report.md +329 -0
- package/docs/campaigns-os-build-flow.md +117 -0
- package/docs/design-source-package.md +784 -0
- package/docs/legacy-migration.md +58 -0
- package/docs/migration-sidecar-bundle.md +139 -0
- package/docs/orientation-contract-reference.md +1220 -0
- package/docs/polish-evidence.md +502 -0
- package/docs/qa-and-test-orders.md +1691 -0
- package/docs/release-ledger-authoring-guide.md +274 -0
- package/docs/runtime-readiness.md +211 -0
- package/docs/supported-surface.md +83 -0
- package/docs/versioning.md +55 -0
- package/docs/workflow-findings-sidecar.md +588 -0
- package/package.json +135 -0
- package/prompts/first-build.md +27 -0
- package/prompts/friction-log.md +30 -0
- package/schemas/campaign-build-brief.v1.schema.json +149 -0
- package/schemas/campaign-design-source-package.v0.schema.json +697 -0
- package/schemas/campaign-runtime-assembly-report.v0.schema.json +390 -0
- package/schemas/campaign-runtime-build-context.v0.schema.json +339 -0
- package/schemas/campaign-runtime-build-packet.v0.schema.json +452 -0
- package/schemas/campaign-spec.v4.schema.json +582 -0
- package/schemas/campaigns-os-doctor-output.v0.schema.json +43 -0
- package/schemas/campaigns-os-legacy-migration-inventory.v0.schema.json +112 -0
- package/schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json +38 -0
- package/schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json +57 -0
- package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +150 -0
- package/schemas/campaigns-os-qa-verdict.v0.schema.json +438 -0
- package/schemas/campaigns-os-release-ledger.v1.schema.json +162 -0
- package/schemas/campaigns-os-run-record.v0.schema.json +343 -0
- package/schemas/campaigns-os-runtime-recipe.v1.schema.json +313 -0
- package/schemas/campaigns-os-sidecar-bundle-conformance.v0.schema.json +78 -0
- package/schemas/campaigns-os-tooling-orientation.v1.schema.json +381 -0
- package/schemas/campaigns-os-workflow-finding.v0.schema.json +134 -0
- package/schemas/source-html-manifest.v0.schema.json +252 -0
- package/skills/next-campaigns-build/SKILL.md +73 -0
- package/skills/next-campaigns-os/SKILL.md +101 -0
- package/skills/next-campaigns-os/references/session-intake.md +160 -0
- package/skills/next-campaigns-os-setup/SKILL.md +22 -0
- package/skills/next-campaigns-polish/SKILL.md +145 -0
- package/skills/next-campaigns-qa/SKILL.md +92 -0
- package/skills.json +56 -0
- package/skills.sh +64 -0
- package/src/adapter-decision-contract.mjs +333 -0
- package/src/brand-theme.mjs +1151 -0
- package/src/browser-launch.mjs +79 -0
- package/src/build-brief.mjs +781 -0
- package/src/built-site-scope.mjs +312 -0
- package/src/campaign-ecosystem.mjs +734 -0
- package/src/campaign-identity.mjs +405 -0
- package/src/campaign-workspace.mjs +127 -0
- package/src/checkpoint-waiver.mjs +302 -0
- package/src/cli.mjs +13442 -0
- package/src/commercial-journey.mjs +1119 -0
- package/src/commercial-parity.mjs +965 -0
- package/src/consent.mjs +347 -0
- package/src/content-residue.mjs +322 -0
- package/src/deadline.mjs +81 -0
- package/src/design-source-package.mjs +2604 -0
- package/src/deviation.mjs +107 -0
- package/src/doctor-check-registry.mjs +49 -0
- package/src/doctor-sidecar.mjs +106 -0
- package/src/finding-cause.mjs +557 -0
- package/src/findings.mjs +326 -0
- package/src/fs-identity.mjs +68 -0
- package/src/gate-actions.mjs +105 -0
- package/src/html-scan.mjs +53 -0
- package/src/install-mode.mjs +272 -0
- package/src/legacy-migration.d.ts +128 -0
- package/src/legacy-migration.mjs +510 -0
- package/src/lifecycle.mjs +338 -0
- package/src/local-proof.mjs +401 -0
- package/src/map-pin-writeback.mjs +210 -0
- package/src/orchestration-stage-contract.mjs +81 -0
- package/src/package-install-fixture.mjs +33 -0
- package/src/page-kit-build-summary.mjs +175 -0
- package/src/page-kit-campaign-config.mjs +57 -0
- package/src/page-kit-sdk-version.mjs +392 -0
- package/src/page-kit-store-profile.mjs +369 -0
- package/src/page-kit-sync.mjs +162 -0
- package/src/polish-browser.mjs +867 -0
- package/src/polish-capture.mjs +1094 -0
- package/src/polish-deadline.mjs +51 -0
- package/src/polish-gate.mjs +739 -0
- package/src/polish-node.mjs +639 -0
- package/src/polish-page-load.mjs +1100 -0
- package/src/private-template-source.mjs +237 -0
- package/src/proof-policy.mjs +82 -0
- package/src/qa-analytics-correctness.mjs +307 -0
- package/src/qa-analytics-errors.mjs +38 -0
- package/src/qa-analytics-parity.mjs +699 -0
- package/src/qa-binding-evidence.mjs +140 -0
- package/src/qa-browser.mjs +6608 -0
- package/src/qa-cart-entry.mjs +406 -0
- package/src/qa-commercial-parity.mjs +641 -0
- package/src/qa-node.mjs +3620 -0
- package/src/qa-order-bump.mjs +381 -0
- package/src/qa-parity-capture.mjs +428 -0
- package/src/qa-parity-fixture.mjs +359 -0
- package/src/qa-publish.mjs +362 -0
- package/src/qa-purchase-data-layer.mjs +263 -0
- package/src/qa-route-probe.mjs +272 -0
- package/src/qa-sidecar.mjs +188 -0
- package/src/qa-test-order-topology.mjs +207 -0
- package/src/qa-url-privacy.mjs +13 -0
- package/src/qa-verdict-discovery.mjs +192 -0
- package/src/qa-verdict-publish.mjs +105 -0
- package/src/qa-verdict.mjs +287 -0
- package/src/remit.mjs +388 -0
- package/src/repo-scan.mjs +83 -0
- package/src/route-identity.mjs +133 -0
- package/src/run-record-closeout.mjs +229 -0
- package/src/run-record.mjs +839 -0
- package/src/run-session.mjs +226 -0
- package/src/runtime-state-ignore.mjs +113 -0
- package/src/sdk-attribute-index.mjs +212 -0
- package/src/sdk-markup.mjs +358 -0
- package/src/sdk-meta-tags.mjs +52 -0
- package/src/shell-token.mjs +7 -0
- package/src/sidecar-bundle.mjs +399 -0
- package/src/source-asset-crawl.mjs +469 -0
- package/src/source-html-intake.mjs +627 -0
- package/src/source-html-manifest.mjs +276 -0
- package/src/source-prep.mjs +284 -0
- package/src/spec-derive-store.mjs +431 -0
- package/src/spec-derive.mjs +514 -0
- package/src/spec-fetch.mjs +66 -0
- package/src/spec-hash.mjs +48 -0
- package/src/spec-identity.mjs +27 -0
- package/src/stage-ledger.mjs +509 -0
- package/src/standardization-report.mjs +1297 -0
- package/src/template-brand-contract.mjs +473 -0
- package/src/template-freshness.mjs +196 -0
- package/src/template-reference.mjs +81 -0
- package/src/template-slot-manifest.mjs +150 -0
- package/src/text-safety.mjs +62 -0
- package/src/theme-gate.mjs +185 -0
- package/src/upsell-selector-scope.mjs +299 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const ASSEMBLY_REPORT_STAGE_KEYS = Object.freeze([
|
|
2
|
+
"prepare_build",
|
|
3
|
+
"doctor",
|
|
4
|
+
"setup",
|
|
5
|
+
"assembly",
|
|
6
|
+
"polish",
|
|
7
|
+
"deploy",
|
|
8
|
+
"qa",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export const NEXT_STAGE_CONTRACTS = Object.freeze([
|
|
12
|
+
Object.freeze({
|
|
13
|
+
cliStage: "setup",
|
|
14
|
+
reportKey: "setup",
|
|
15
|
+
}),
|
|
16
|
+
Object.freeze({
|
|
17
|
+
cliStage: "build",
|
|
18
|
+
reportKey: "assembly",
|
|
19
|
+
}),
|
|
20
|
+
Object.freeze({
|
|
21
|
+
cliStage: "polish",
|
|
22
|
+
reportKey: "polish",
|
|
23
|
+
}),
|
|
24
|
+
Object.freeze({
|
|
25
|
+
cliStage: "deploy",
|
|
26
|
+
reportKey: "deploy",
|
|
27
|
+
}),
|
|
28
|
+
Object.freeze({
|
|
29
|
+
cliStage: "qa",
|
|
30
|
+
reportKey: "qa",
|
|
31
|
+
}),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
export const NEXT_STAGE_ORDER = Object.freeze(NEXT_STAGE_CONTRACTS.map((contract) => contract.cliStage));
|
|
35
|
+
|
|
36
|
+
const NEXT_STAGE_BY_CLI_STAGE = new Map(
|
|
37
|
+
NEXT_STAGE_CONTRACTS.map((contract) => [contract.cliStage, contract])
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export function nextStageContractForCliStage(cliStage) {
|
|
41
|
+
return NEXT_STAGE_BY_CLI_STAGE.get(String(cliStage || "")) || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function reportKeyForCliStage(cliStage) {
|
|
45
|
+
return nextStageContractForCliStage(cliStage)?.reportKey || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Status values that count as terminal under PREFIX matching — so
|
|
50
|
+
* "completed", "completed_with_warnings", and "completed_partial" all
|
|
51
|
+
* count as terminal under "completed". This matches how the existing
|
|
52
|
+
* stages already report sub-statuses (see report.stages.assembly.status
|
|
53
|
+
* shapes in src/cli.mjs and qa/shared/qa-verdict.js). Shared here so the
|
|
54
|
+
* `next` picker, the session progress summary and the Assembly Report's own
|
|
55
|
+
* derived summary (src/stage-ledger.mjs) read one definition of "done".
|
|
56
|
+
*/
|
|
57
|
+
export const STAGE_TERMINAL_STATUS_PREFIXES = Object.freeze(["completed", "skipped"]);
|
|
58
|
+
|
|
59
|
+
export function stageIsTerminal(status) {
|
|
60
|
+
const normalized = String(status || "");
|
|
61
|
+
return STAGE_TERMINAL_STATUS_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function stageIsBlocked(status) {
|
|
65
|
+
return String(status || "") === "blocked";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Owner and skill for each stage the picker can name. The doctor's `next`
|
|
69
|
+
// block and the Assembly Report's `next` block are both projections of the
|
|
70
|
+
// same ladder the `next` command walks, so all three spell a stage, its owner
|
|
71
|
+
// and its skill the same way.
|
|
72
|
+
export const NEXT_STAGE_OWNERS = Object.freeze({
|
|
73
|
+
"prepare-build": Object.freeze({ owner: "operator", default_skill: "next-campaigns-os" }),
|
|
74
|
+
"doctor-blocked": Object.freeze({ owner: "operator", default_skill: "next-campaigns-os" }),
|
|
75
|
+
setup: Object.freeze({ owner: "setup", default_skill: "next-campaigns-os-setup" }),
|
|
76
|
+
build: Object.freeze({ owner: "build", default_skill: "next-campaigns-build" }),
|
|
77
|
+
polish: Object.freeze({ owner: "polish", default_skill: "next-campaigns-polish" }),
|
|
78
|
+
deploy: Object.freeze({ owner: "operator", default_skill: "next-campaigns-os" }),
|
|
79
|
+
qa: Object.freeze({ owner: "qa", default_skill: "next-campaigns-qa" }),
|
|
80
|
+
done: Object.freeze({ owner: "qa", default_skill: "next-campaigns-os" }),
|
|
81
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Test fixture: a REAL package install of this checkout, staged under a
|
|
2
|
+
// temporary install root, so a test can run the CLI the way a consumer's
|
|
3
|
+
// `npx campaigns-os …` does (install mode detected from the enclosing
|
|
4
|
+
// node_modules, pinned commit read from the lockfile npm would have written).
|
|
5
|
+
// Shared by the tooling-status and page-kit sync suites; not part of the
|
|
6
|
+
// supported surface.
|
|
7
|
+
import { cpSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const ROOT = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
12
|
+
|
|
13
|
+
export const PIN_SHA = "236d7fc454c877e3c07337237ee9e19303c5cc15";
|
|
14
|
+
|
|
15
|
+
export function stageRealPackageInstall(installRoot) {
|
|
16
|
+
const pkgRoot = join(installRoot, "node_modules", "@nextcommerce", "campaigns-os");
|
|
17
|
+
mkdirSync(pkgRoot, { recursive: true });
|
|
18
|
+
for (const entry of ["agents", "bin", "src", "campaign-spec", "contracts", "schemas", "skills", "skills.json", "package.json"]) {
|
|
19
|
+
cpSync(join(ROOT, entry), join(pkgRoot, entry), { recursive: true, dereference: true });
|
|
20
|
+
}
|
|
21
|
+
symlinkSync(join(ROOT, "node_modules"), join(pkgRoot, "node_modules"), "dir");
|
|
22
|
+
writeFileSync(join(installRoot, "node_modules", ".package-lock.json"), JSON.stringify({
|
|
23
|
+
name: "npx",
|
|
24
|
+
lockfileVersion: 3,
|
|
25
|
+
packages: {
|
|
26
|
+
"node_modules/@nextcommerce/campaigns-os": {
|
|
27
|
+
version: "0.1.0-alpha.0",
|
|
28
|
+
resolved: `git+ssh://git@github.com/NextCommerceCo/campaigns-os.git#${PIN_SHA}`,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
return pkgRoot;
|
|
33
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// Page Kit build summary gate: consumes the machine-readable summary that
|
|
2
|
+
// `campaign-build --json` (next-campaign-page-kit >= 0.1.4) emits, so doctor
|
|
3
|
+
// verifies what Page Kit actually built — resolved routes, per-page status,
|
|
4
|
+
// and shape warnings — instead of only inspecting rendered _site HTML.
|
|
5
|
+
//
|
|
6
|
+
// Doctrine: dogfood runs on pre-assembled AI HTML never tripped Page Kit
|
|
7
|
+
// routing surprises (NESTED_NO_PERMALINK, DUPLICATE_OUTPUT) because those
|
|
8
|
+
// pages were hand-tuned to be correct. Real designer-exported source will
|
|
9
|
+
// trip them. The build step must capture the summary as an artifact:
|
|
10
|
+
//
|
|
11
|
+
// npx campaign-build --json > .campaign-runtime/page-kit-build-summary.json
|
|
12
|
+
//
|
|
13
|
+
// and this module turns that artifact into doctor issues every consumer
|
|
14
|
+
// (next, doctor, qa) shares. A missing artifact is a warning, not a block,
|
|
15
|
+
// so target repos on page-kit < 0.1.4 keep working while the fleet upgrades.
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
20
|
+
export const PAGE_KIT_BUILD_SUMMARY_REL_PATH = ".campaign-runtime/page-kit-build-summary.json";
|
|
21
|
+
export const PAGE_KIT_BUILD_SUMMARY_CAPTURE_COMMAND = `npx campaign-build --json > ${PAGE_KIT_BUILD_SUMMARY_REL_PATH}`;
|
|
22
|
+
export const PAGE_KIT_BUILD_SUMMARY_MIN_PAGE_KIT = "0.1.4";
|
|
23
|
+
|
|
24
|
+
// Page Kit shape warnings that mean the rendered funnel can silently differ
|
|
25
|
+
// from what the author intended; once assembly is complete these escalate to
|
|
26
|
+
// the same severity as a failed page. Exact-match against the page-kit
|
|
27
|
+
// warning-code contract (lib/engine/build.js in NextCommerceCo/campaign-page-kit);
|
|
28
|
+
// when page-kit adds a funnel-corrupting code, it must be added here explicitly —
|
|
29
|
+
// unknown codes deliberately stay non-blocking warnings.
|
|
30
|
+
const ESCALATED_WARNING_CODES = new Set(["DUPLICATE_OUTPUT", "NO_CAMPAIGN"]);
|
|
31
|
+
|
|
32
|
+
function safeMtimeMs(path) {
|
|
33
|
+
try {
|
|
34
|
+
return statSync(path).mtimeMs;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readPageKitBuildSummary(targetRepo) {
|
|
41
|
+
const path = join(targetRepo, PAGE_KIT_BUILD_SUMMARY_REL_PATH);
|
|
42
|
+
if (!existsSync(path)) return { path, summary: null, error: null };
|
|
43
|
+
try {
|
|
44
|
+
const summary = JSON.parse(readFileSync(path, "utf8"));
|
|
45
|
+
if (!summary || typeof summary !== "object" || !Array.isArray(summary.pages)) {
|
|
46
|
+
return { path, summary: null, error: "summary is not an object with a pages[] array" };
|
|
47
|
+
}
|
|
48
|
+
return { path, summary, error: null };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return { path, summary: null, error: error.message };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Evaluate the captured Page Kit build summary for one campaign.
|
|
56
|
+
*
|
|
57
|
+
* Pure with respect to doctor state: returns { errors, warnings, ready }
|
|
58
|
+
* issue lists ({ code, message, detail? } / strings for ready) and lets the
|
|
59
|
+
* caller route them through its own addIssue so formatting stays uniform.
|
|
60
|
+
*
|
|
61
|
+
* Returns empty lists when there is nothing to verify yet (no built _site
|
|
62
|
+
* for the campaign), mirroring the built_output.* checks' gating.
|
|
63
|
+
*/
|
|
64
|
+
export function evaluatePageKitBuildSummary({
|
|
65
|
+
targetRepo,
|
|
66
|
+
publicRouteSlug,
|
|
67
|
+
activePages = [],
|
|
68
|
+
assemblyComplete = false,
|
|
69
|
+
builtPathForPage = null,
|
|
70
|
+
}) {
|
|
71
|
+
const errors = [];
|
|
72
|
+
const warnings = [];
|
|
73
|
+
const ready = [];
|
|
74
|
+
const result = { errors, warnings, ready };
|
|
75
|
+
|
|
76
|
+
if (!targetRepo || !publicRouteSlug) return result;
|
|
77
|
+
const siteRoot = join(targetRepo, "_site", publicRouteSlug);
|
|
78
|
+
if (!existsSync(siteRoot)) return result;
|
|
79
|
+
|
|
80
|
+
const issueTarget = assemblyComplete ? errors : warnings;
|
|
81
|
+
const { path, summary, error } = readPageKitBuildSummary(targetRepo);
|
|
82
|
+
|
|
83
|
+
if (!summary && !error) {
|
|
84
|
+
warnings.push({
|
|
85
|
+
code: "built_output.build_summary_missing",
|
|
86
|
+
message:
|
|
87
|
+
`No Page Kit build summary at ${PAGE_KIT_BUILD_SUMMARY_REL_PATH}. ` +
|
|
88
|
+
`Capture one with \`${PAGE_KIT_BUILD_SUMMARY_CAPTURE_COMMAND}\` (requires next-campaign-page-kit >= ${PAGE_KIT_BUILD_SUMMARY_MIN_PAGE_KIT}) ` +
|
|
89
|
+
`so doctor can verify resolved routes and Page Kit shape warnings instead of guessing from rendered HTML alone.`,
|
|
90
|
+
detail: { expected_path: PAGE_KIT_BUILD_SUMMARY_REL_PATH },
|
|
91
|
+
});
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!summary) {
|
|
96
|
+
warnings.push({
|
|
97
|
+
code: "built_output.build_summary_invalid",
|
|
98
|
+
message: `Page Kit build summary at ${PAGE_KIT_BUILD_SUMMARY_REL_PATH} is unreadable (${error}). Re-capture it with \`${PAGE_KIT_BUILD_SUMMARY_CAPTURE_COMMAND}\`.`,
|
|
99
|
+
detail: { expected_path: PAGE_KIT_BUILD_SUMMARY_REL_PATH, parse_error: error },
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Staleness: if any built page for this campaign is newer than the summary,
|
|
105
|
+
// the summary describes an older build and its verdicts cannot be trusted.
|
|
106
|
+
const summaryMtime = typeof builtPathForPage === "function" ? safeMtimeMs(path) : null;
|
|
107
|
+
if (summaryMtime !== null) {
|
|
108
|
+
const staleSources = activePages
|
|
109
|
+
.map((page) => builtPathForPage(page))
|
|
110
|
+
.map((builtPath) => (builtPath ? safeMtimeMs(builtPath) : null))
|
|
111
|
+
.filter((builtMtime) => builtMtime !== null && builtMtime > summaryMtime + 1);
|
|
112
|
+
if (staleSources.length > 0) {
|
|
113
|
+
warnings.push({
|
|
114
|
+
code: "built_output.build_summary_stale",
|
|
115
|
+
message:
|
|
116
|
+
`Page Kit build summary predates ${staleSources.length} built page(s); the build ran again without re-capturing. ` +
|
|
117
|
+
`Re-run \`${PAGE_KIT_BUILD_SUMMARY_CAPTURE_COMMAND}\`.`,
|
|
118
|
+
detail: { newer_pages: staleSources.length },
|
|
119
|
+
});
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const campaignPages = summary.pages.filter((page) => page && page.campaignSlug === publicRouteSlug);
|
|
125
|
+
if (campaignPages.length === 0) {
|
|
126
|
+
warnings.push({
|
|
127
|
+
code: "built_output.build_summary_no_pages",
|
|
128
|
+
message:
|
|
129
|
+
`Page Kit build summary has no pages for campaign slug "${publicRouteSlug}". ` +
|
|
130
|
+
`The capture may have run against a different campaign or before this campaign's source existed; re-run \`${PAGE_KIT_BUILD_SUMMARY_CAPTURE_COMMAND}\`.`,
|
|
131
|
+
detail: { slug: publicRouteSlug, summary_slugs: [...new Set(summary.pages.map((p) => p?.campaignSlug).filter(Boolean))] },
|
|
132
|
+
});
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let failed = 0;
|
|
137
|
+
let flagged = 0;
|
|
138
|
+
for (const page of campaignPages) {
|
|
139
|
+
const pageWarnings = Array.isArray(page.warnings) ? page.warnings : [];
|
|
140
|
+
const pageErrors = Array.isArray(page.errors) ? page.errors : [];
|
|
141
|
+
|
|
142
|
+
if (page.status === "error" || pageErrors.length > 0) {
|
|
143
|
+
failed += 1;
|
|
144
|
+
issueTarget.push({
|
|
145
|
+
code: "built_output.build_page_error",
|
|
146
|
+
message:
|
|
147
|
+
`Page Kit failed to build "${page.inputFile}"${page.url ? ` (${page.url})` : ""}: ` +
|
|
148
|
+
`${pageErrors.map((e) => e.message || e.code).join("; ") || "status=error"}.`,
|
|
149
|
+
detail: { input_file: page.inputFile, url: page.url ?? null, errors: pageErrors },
|
|
150
|
+
});
|
|
151
|
+
// Fall through: a page that fails to render can still carry shape
|
|
152
|
+
// warnings (e.g. NESTED_NO_PERMALINK) that must survive the error.
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const warning of pageWarnings) {
|
|
156
|
+
flagged += 1;
|
|
157
|
+
const escalated = ESCALATED_WARNING_CODES.has(warning.code);
|
|
158
|
+
const target = escalated ? issueTarget : warnings;
|
|
159
|
+
target.push({
|
|
160
|
+
code: escalated ? "built_output.build_page_warning_escalated" : "built_output.build_page_warning",
|
|
161
|
+
message:
|
|
162
|
+
`Page Kit flagged "${page.inputFile}"${page.url ? ` (${page.url})` : ""}: [${warning.code}] ${warning.message}`,
|
|
163
|
+
detail: { input_file: page.inputFile, url: page.url ?? null, warning_code: warning.code },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (failed === 0 && flagged === 0) {
|
|
169
|
+
ready.push(
|
|
170
|
+
`Page Kit build summary verified for "${publicRouteSlug}": ${campaignPages.length} page(s) built, no build errors or shape warnings`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { normalizePublicRouteSlug } from "./route-identity.mjs";
|
|
4
|
+
|
|
5
|
+
export const PAGE_KIT_CAMPAIGNS_REL_PATH = "_data/campaigns.json";
|
|
6
|
+
|
|
7
|
+
// Public doctor/next/verdict evidence must never inherit arbitrary merchant
|
|
8
|
+
// configuration from campaigns.json. The raw entry is validation-local; this
|
|
9
|
+
// fixed projection is the only loader metadata safe to retain in artifacts.
|
|
10
|
+
export function projectPageKitCampaignLoad(load) {
|
|
11
|
+
return {
|
|
12
|
+
status: typeof load?.status === "string" ? load.status : "target_repo_missing",
|
|
13
|
+
public_route_slug: normalizePublicRouteSlug(load?.public_route_slug),
|
|
14
|
+
target_path: typeof load?.target_path === "string" && load.target_path
|
|
15
|
+
? load.target_path
|
|
16
|
+
: PAGE_KIT_CAMPAIGNS_REL_PATH,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadPageKitCampaignEntry({ targetRepo, publicRouteSlug }) {
|
|
21
|
+
const public_route_slug = normalizePublicRouteSlug(publicRouteSlug);
|
|
22
|
+
const base = {
|
|
23
|
+
public_route_slug,
|
|
24
|
+
target_path: PAGE_KIT_CAMPAIGNS_REL_PATH,
|
|
25
|
+
entry: null,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (typeof targetRepo !== "string" || !targetRepo.trim() || !existsSync(targetRepo)) {
|
|
29
|
+
return { ...base, status: "target_repo_missing" };
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
if (!statSync(targetRepo).isDirectory()) return { ...base, status: "target_repo_missing" };
|
|
33
|
+
} catch {
|
|
34
|
+
return { ...base, status: "target_repo_missing" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const campaignsPath = join(targetRepo, PAGE_KIT_CAMPAIGNS_REL_PATH);
|
|
38
|
+
if (!existsSync(campaignsPath)) return { ...base, status: "file_missing" };
|
|
39
|
+
|
|
40
|
+
let campaigns;
|
|
41
|
+
try {
|
|
42
|
+
campaigns = JSON.parse(readFileSync(campaignsPath, "utf8"));
|
|
43
|
+
} catch {
|
|
44
|
+
return { ...base, status: "invalid_json" };
|
|
45
|
+
}
|
|
46
|
+
if (!campaigns || typeof campaigns !== "object" || Array.isArray(campaigns)) {
|
|
47
|
+
return { ...base, status: "root_not_object" };
|
|
48
|
+
}
|
|
49
|
+
if (!Object.hasOwn(campaigns, public_route_slug)) {
|
|
50
|
+
return { ...base, status: "entry_missing" };
|
|
51
|
+
}
|
|
52
|
+
const entry = campaigns[public_route_slug];
|
|
53
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
54
|
+
return { ...base, status: "entry_not_object" };
|
|
55
|
+
}
|
|
56
|
+
return { ...base, status: "ok", entry };
|
|
57
|
+
}
|