@nextcommerce/campaigns-os 1.47.0 → 1.48.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 +194 -0
- package/README.md +30 -3
- package/agents/claude/CLAUDE.md +2 -0
- package/agents/codex/AGENTS.md +1 -0
- package/agents/copilot/copilot-instructions.md +1 -0
- package/agents/cursor/campaigns-os.mdc +1 -1
- package/compatibility.json +1 -1
- package/contracts/commerce-surface-catalog.json +17 -17
- package/contracts/release-ledger.json +465 -0
- package/contracts/supported-surface.json +1 -1
- package/contracts/template-brand-contract.shared-commerce.v0.json +1 -1
- package/docs/build-packet.md +1 -1
- package/docs/campaign-build-brief.md +25 -28
- package/docs/local-setup.md +7 -2
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/qa-and-test-orders.md +49 -2
- package/docs/runtime-readiness.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 +9 -4
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +4 -3
- package/skills/next-campaigns-os/SKILL.md +3 -3
- package/skills/next-campaigns-os-setup/SKILL.md +3 -3
- package/skills/next-campaigns-polish/SKILL.md +4 -4
- package/skills/next-campaigns-qa/SKILL.md +3 -3
- package/skills.json +10 -10
- package/src/cli.mjs +6 -3
- package/src/content-residue.mjs +18 -90
- package/src/doctor/checks.mjs +21 -29
- package/src/doctor/inspect.mjs +13 -3
- package/src/doctor/next-step.mjs +4 -0
- package/src/gate-actions.mjs +8 -0
- package/src/local-preview-policy.mjs +92 -0
- package/src/page-kit-sdk-version.mjs +8 -1
- package/src/polish-node.mjs +26 -2
- package/src/progress-node.mjs +5 -1
- package/src/qa-binding-evidence.mjs +1 -1
- package/src/qa-browser.mjs +62 -13
- package/src/qa-node.mjs +33 -4
- package/src/readback.mjs +19 -10
- package/src/source-prep.mjs +1 -1
|
@@ -96,6 +96,32 @@ recapture. A hosted target (`netlify`, `cloudflare-pages`, …) is unaffected:
|
|
|
96
96
|
its build stage renders production as before and `page-kit parity` refuses the
|
|
97
97
|
packet (`local_proof.parity.not_local_serve`).
|
|
98
98
|
|
|
99
|
+
### Missing evidence carried forward on the local preview
|
|
100
|
+
|
|
101
|
+
On a `local-serve` packet served from a loopback host (`localhost`,
|
|
102
|
+
`127.0.0.1`, `[::1]`, for both `deploy.preview_url` and `--base-url`), some
|
|
103
|
+
missing evidence is carried forward as a warning instead of blocking the
|
|
104
|
+
loop. The campaign must still prove its commerce there: store and campaign
|
|
105
|
+
binding, routes, SDK loading, prices and a typed-card order. Only these
|
|
106
|
+
checks are carried forward:
|
|
107
|
+
|
|
108
|
+
| Check | When |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `polish.evidence_missing`, `polish.report_missing` | Polish was never recorded for this build. |
|
|
111
|
+
| `polish.hidden_eager_media.no_capturable_routes` | Every mapped page is template stock (`skip_reason`), so polish capture has no design route to capture. |
|
|
112
|
+
| `polish.hidden_eager_media.capture_malformed` | Only when no page-load capture was recorded at all. |
|
|
113
|
+
| Template-residue severity | With `theme_gate.nothing_generatable`, the starter template is the design, so residue findings are warnings rather than blockers. |
|
|
114
|
+
|
|
115
|
+
A carried-forward gate has status `carried_forward`. Doctor reports it as a
|
|
116
|
+
warning starting "Carried forward on the local preview"; `next` moves past
|
|
117
|
+
polish to deploy and QA; QA records it as a `warn` row, so the verdict is at
|
|
118
|
+
best `ready_with_exceptions`. The evidence is reported as missing, never as
|
|
119
|
+
passed. Any other check keeps its meaning. A hosted preview or production
|
|
120
|
+
packet, and a `local-serve` packet served from any other host, gets the strict
|
|
121
|
+
gates. `record polish` and the waiver commands also keep them strict. Progress
|
|
122
|
+
snapshots record a carried-forward gate as `not_applicable`, because their
|
|
123
|
+
schema has no carried-forward state.
|
|
124
|
+
|
|
99
125
|
## Resolve
|
|
100
126
|
|
|
101
127
|
Use resolve before a full run:
|
|
@@ -482,7 +508,15 @@ the three checks and each `evidence.checks[]` entry carries `kind`
|
|
|
482
508
|
(`family_shell` or `sdk_wiring`).
|
|
483
509
|
The upsell and checkout bundle price checks count the SDK's
|
|
484
510
|
`[data-next-bundle-display*='price']` alongside the contract's price rows; a
|
|
485
|
-
hidden, zero-size or empty bundle-display node does not count.
|
|
511
|
+
hidden, zero-size or empty bundle-display node does not count. A checkout that
|
|
512
|
+
shows no price because QA opened it directly, with an empty SDK cart and no
|
|
513
|
+
package selection of its own, is `skipped` rather than failed:
|
|
514
|
+
`pricing.checkout_price_visible` records `cart_count` and
|
|
515
|
+
`checkout_selection_surface` and says so. That checkout's cart is filled on
|
|
516
|
+
an earlier page, for example by a landing link carrying `forcePackageId`, and
|
|
517
|
+
the test order enters it from there. A checkout with its own package
|
|
518
|
+
selection, or a filled cart, still fails when no price shows. If the cart
|
|
519
|
+
probe itself fails, the row stays failed and records `empty_cart_probe_error`.
|
|
486
520
|
Promoted template families must also have
|
|
487
521
|
`contracts/template-brand-contract.<family>.v0.json`; QA emits a blocker if the
|
|
488
522
|
selected family is missing its brand/residue/pricing contract instead of
|
|
@@ -1749,6 +1783,17 @@ shared real inbox rather than a synthetic one). When neither is set, the runner
|
|
|
1749
1783
|
falls back to a single stable synthetic address — still one reused customer, but
|
|
1750
1784
|
not deliverable.
|
|
1751
1785
|
|
|
1786
|
+
Reusing one customer has one cost. The platform refuses an order whose customer,
|
|
1787
|
+
items and total match one it accepted or is still processing in the last 30
|
|
1788
|
+
minutes ("Duplicate order detected, order not created"). A successful test order
|
|
1789
|
+
does not hold that window, so the paths of one run do not collide. Two QA runs
|
|
1790
|
+
against the same campaign at once do, and so can a rerun soon after an attempt
|
|
1791
|
+
that died mid-submit. QA reports it on the path's `browser-test-order` row as
|
|
1792
|
+
`order create rejected: HTTP 400: Duplicate order detected …` followed by
|
|
1793
|
+
`duplicate_order` and the remedy: re-run with a different `--test-email-prefix`
|
|
1794
|
+
(or `--test-email`), or wait. The shipping address is not part of the match, so
|
|
1795
|
+
changing `--test-address1` does not help.
|
|
1796
|
+
|
|
1752
1797
|
The browser driver intentionally behaves like a user:
|
|
1753
1798
|
|
|
1754
1799
|
- package selection uses rendered `[data-next-package-id]` controls when
|
|
@@ -1919,7 +1964,9 @@ ASCII whitespace and compared case-insensitively as the browser does; classic
|
|
|
1919
1964
|
`built_output.script_syntax` in [the Build Packet doc](build-packet.md).
|
|
1920
1965
|
|
|
1921
1966
|
External executable scripts other than the recognized jsDelivr Campaign Cart
|
|
1922
|
-
loader
|
|
1967
|
+
loader or index are inspected only on the page's origin. Three Campaign Cart
|
|
1968
|
+
paths count as the SDK and are never fetched: `dist/loader.js` (the one the
|
|
1969
|
+
starter templates load), `dist/index.js` and `public/loader.js`. Each page admits at most
|
|
1923
1970
|
6 such references; each run fetches at most 24 distinct URLs (deduplicated),
|
|
1924
1971
|
256 KiB per response and 6 MiB aggregate, 5 seconds per request including body
|
|
1925
1972
|
read (at most 30 seconds of sequential config requests per page). Redirects,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
How a checkout of this repository at one commit becomes a usable installed runtime, and how a consumer decides whether a prepared one is still trustworthy. Everything below is generated from `contracts/runtime-recipe.campaigns-os-node-v1.json`, which is the only authority for these values.
|
|
10
10
|
|
|
11
|
-
Recipe kind `campaigns-os-node-v1`, revision `1.0.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.
|
|
11
|
+
Recipe kind `campaigns-os-node-v1`, revision `1.0.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.48.0`.
|
|
12
12
|
|
|
13
13
|
## What this is
|
|
14
14
|
|
package/docs/skills-revision.md
CHANGED
|
@@ -16,7 +16,7 @@ that the copy on disk moved.
|
|
|
16
16
|
`skills.json` carries one top-level field:
|
|
17
17
|
|
|
18
18
|
```json
|
|
19
|
-
"bundle_revision": "1.
|
|
19
|
+
"bundle_revision": "1.48.0+skills.1"
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
The spelling is `<package version>+skills.<n>`:
|
|
@@ -25,7 +25,7 @@ The spelling is `<package version>+skills.<n>`:
|
|
|
25
25
|
skills ship with (`check-skill-versions.mjs` fails if the two disagree);
|
|
26
26
|
- `<n>` is a plain counter, not a semver component. It says "this is the *n*th
|
|
27
27
|
skill-text revision published against that package version" and it **resets
|
|
28
|
-
with the prefix**. `1.
|
|
28
|
+
with the prefix**. `1.48.0+skills.1` is therefore ahead of `1.40.0+skills.7`.
|
|
29
29
|
|
|
30
30
|
It is one identity for the bundle as a whole, on purpose. Per-skill versions
|
|
31
31
|
still exist and still gate per-skill changes, but an agent that loaded one skill
|
|
@@ -37,7 +37,7 @@ The first body line of every bundled `SKILL.md`, immediately after the
|
|
|
37
37
|
frontmatter, is exactly:
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
Bundle revision: 1.
|
|
40
|
+
Bundle revision: 1.48.0+skills.1
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
followed by a short paragraph telling the agent to run the check below at the
|
|
@@ -48,7 +48,7 @@ text the agent is actually reading, not from a file it would have to go and open
|
|
|
48
48
|
## The check
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
51
|
+
npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
The value is compared against the bundle revision of the **CLI the command runs
|
|
@@ -90,20 +90,20 @@ reports the choice as `skills.scope` (`requested`, `installed_platforms`, or
|
|
|
90
90
|
"revision_check": "match",
|
|
91
91
|
"skills_revision": {
|
|
92
92
|
"status": "match",
|
|
93
|
-
"requested": "1.
|
|
93
|
+
"requested": "1.48.0+skills.1",
|
|
94
94
|
"spelling": "bundle",
|
|
95
|
-
"on_disk": "1.
|
|
95
|
+
"on_disk": "1.48.0+skills.1",
|
|
96
96
|
"on_disk_skill": null,
|
|
97
|
-
"message": "match (1.
|
|
97
|
+
"message": "match (1.48.0+skills.1)"
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
The text view prints one named line, as a header above the rest of the status:
|
|
102
102
|
|
|
103
103
|
```
|
|
104
|
-
Skills revision: match (1.
|
|
105
|
-
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.
|
|
106
|
-
Skills revision: unchecked (on disk 1.
|
|
104
|
+
Skills revision: match (1.48.0+skills.1)
|
|
105
|
+
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.48.0+skills.1 — start a fresh session
|
|
106
|
+
Skills revision: unchecked (on disk 1.48.0+skills.1)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
`unchecked` is the state when the flag is absent. It is not an error — an
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: campaign-lifecycle-orientation
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.22
|
|
4
4
|
description: Orient a reader to the Campaigns OS lifecycle artifacts a run has already emitted, without advancing any stage or changing any state.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: campaign-readback-classification
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.22
|
|
4
4
|
description: Classify a selected campaign from the readback projection's v2 fields and write a read-only handoff without turning diagnosis into permission.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: campaign-run-evidence
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.22
|
|
4
4
|
description: Interpret existing Campaigns OS doctor, QA and proof-depth evidence without claiming more proof than the artifacts contain.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -65,7 +65,12 @@ Saved-Map QA may publish it under the existing consent and flag controls; a
|
|
|
65
65
|
publication failure does not erase the local one. Local-spec packet verdicts
|
|
66
66
|
stay local even with `--post-verdict`, and `qa publish` refuses those packets.
|
|
67
67
|
The readback projects `.campaign-runtime/qa-verdict.json` when
|
|
68
|
-
that sidecar has been copied into the campaign repository.
|
|
68
|
+
that sidecar has been copied into the campaign repository. The sidecar is a
|
|
69
|
+
committable projection: it always empties `test_orders`, `entry_urls`,
|
|
70
|
+
`page_urls` and `tested_urls`, so an empty `test_orders` there says nothing
|
|
71
|
+
about ordering. Its typed-card proof is the `browser-test-order:<path>`
|
|
72
|
+
assertions, each with a `browser-order-total-parity:<path>` row beside it; the
|
|
73
|
+
full verdict under `qa-output/` keeps the order records. A markdown QA
|
|
69
74
|
report, a ledger or a gate script is not a verdict and must not be scanned for
|
|
70
75
|
a disposition, a run id or a blocker. Where two JSON verdicts exist, interpret
|
|
71
76
|
the one the projection loaded; do not walk a report looking for a later rerun.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: contribution-intake
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.22
|
|
4
4
|
description: Turn a suggestion about the agent surface into a classified, evidence-checked proposal and, only with attended approval, one issue on this repository's tracker.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-build
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.27
|
|
4
4
|
description: Assemble a NEXT campaign from a doctor-cleared Build Packet, CampaignSpec/API values, prepared HTML/assets, page-kit, and starter-template contracts.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -86,6 +86,7 @@ Build rules:
|
|
|
86
86
|
- Preserve SDK-owned checkout/cart/upsell/receipt/payment/address/totals/submit surfaces.
|
|
87
87
|
- If `doctor` (tier `A`: inspection that writes nothing, plus one read-only live campaign request once the site is built and a public campaign key resolves; `--write` is also tier `A` and writes doctor output; `--built` is tier `B`) reports `derived.scope.mode = "partial"`, build the pages listed in `derived.scope.built_pages` from their prepared source. Keep every out-of-scope route unbuilt by default, including pages marked `template_stock: true`; never publish placeholder presell/landing pages that remain on another host. Materialize a stock page only with explicit per-page operator opt-in (including a required pre-checkout `select` stand-in). For opted-in pages use the locked family's own page for that role with its dependent `_includes/`, `_layouts/`, and assets, wire it from CampaignSpec, and build a required select step first. Do not attest stock screenshots as design source. Built stock pages rejoin preview QA once their HTML exists. Carry remaining `skip_reason` declarations into the report and label the preview route/visual-testable rather than full-funnel launch-ready.
|
|
88
88
|
- For `landing` and `presell` pages, prefer the prepared source HTML when `source_html.pages[].path` points at a real standalone page. Preserve the design/content through a passthrough page-kit layout, inject the SDK loader/config as needed, and repoint CTAs into the CampaignSpec flow. Treat `source_html.pages[].path` and `context.page_map[].source_path` as source provenance. Treat `source_html.pages[].page_kit`, `context.page_map[].page_kit`, and `context.page_map[].output_path` as the Page Kit target file, route, CPK `page_type`, and frontmatter projection.
|
|
89
|
+
- Reproduce the source design's own proof and urgency elements as designed: reviews and testimonials, ratings, "Verified Purchase" labels, recent-purchase popups, stock counters, countdowns and guarantees. They are the merchant's content and the merchant's responsibility; Campaigns OS does not review them, so do not remove, soften or flag them. The starter templates shipping without some of these elements is not a reason to drop the source's.
|
|
89
90
|
- Prepared source HTML means page-kit-ready markup, not a wholesale Liquid rewrite. Standalone HTML mockups that are meant to stay whole (their `source_screenshot` proof is of the full document) keep their document wrappers: the source-html manifest records `wrapper_policy: preserve_document_wrappers` (or `--wrapper-policy preserve_document_wrappers` on `start` / `prepare-build`), and doctor reports the wrappers as a warning. Otherwise, standalone AI/exported HTML should keep page-owned body markup, remove document wrappers, add YAML frontmatter, move shared CSS/assets into the campaign structure, and use Liquid helpers only where page-kit needs campaign-rooted links/assets/includes.
|
|
90
91
|
- For `checkout`, `upsell`, `downsell`, and `receipt` pages, treat the selected starter-template commerce surface as the SDK contract reference: preserve required `data-next-*` controls, hidden fields, payment/address/totals/submit wiring, and `next_dont_touch` regions. The surrounding HTML wrapper, page composition, imagery, copy hierarchy, and brand layer are campaign/source-owned. Do not carry starter visual chrome forward when prepared source design should own that surface. QA checks what the checkout does, not family class names: do not add family shell classes (`.checkout-wrapper`, `.checkout-layout__left/right`) to the campaign's own grid or swap source-owned field markup for family includes to satisfy QA. Keep the checkout working instead: a `<form data-next-checkout="form">`, the contact and shipping fields bound with `data-next-checkout-field` inside it, and a visible cart total. A required field bound only on a `type="hidden"` input, a disabled control, a read-only input or read-only textarea, or a control with `aria-disabled="true"` does not count as bound: QA reports it in `fields_bound.missing`.
|
|
91
92
|
- Read `context.theme` and `.campaign-runtime/theme/theme-report.json` when present. If a fresh `brand-theme.css` artifact exists, copy it into the campaign asset tree and load it after `next-core.css` on checkout, upsell, downsell, and receipt pages. If policy is `inspect_only`, either run `campaigns-os theme generate` (tier `B`: writes the theme artifacts and doctor output under the target; `--force` is tier `C`) or record an explicit skipped reason before applying a new brand layer.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-os
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.42
|
|
4
4
|
description: Coordinate Campaigns OS lifecycle workflows from CampaignSpec, Build Packet, starter-template contracts, stage reports, deploy evidence, and QA proof depth.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-os-setup
|
|
3
|
-
version: 2.0.
|
|
3
|
+
version: 2.0.25
|
|
4
4
|
description: Bootstrap or prepare a target page-kit campaign repo from a doctor-cleared Campaigns OS Build Packet before full build wiring. Formerly installed as next-campaigns-setup; renamed 2026-08 to stop colliding with the published NextCommerceCo/skills scaffolder of that name.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-polish
|
|
3
|
-
version: 1.1.
|
|
3
|
+
version: 1.1.26
|
|
4
4
|
description: Run the visual/runtime polish pass after build and before QA for a Campaigns OS campaign.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -56,7 +56,7 @@ Responsibilities:
|
|
|
56
56
|
- Compare prepared source design against built campaign pages.
|
|
57
57
|
- Scan the prepared source assets for brand marks such as `logo*.png`, `logo*.svg`, and obvious header/logo images before leaving starter-template logos in place.
|
|
58
58
|
- Scan **visible** rendered text inside the family's content/commerce surfaces (the selectors enumerated in `contracts/template-brand-contract.<family>.v0.json`) for placeholder/residue copy the build should have replaced — the *literal starter defaults*: lorem-ipsum, the unmodified starter headings (`Product Name` / `Package Title` / `Your headline`), `[VERIFY …]` author notes, `TODO` markers. Match the literal starter strings, not any authored copy that merely contains those words, and skip `<script>` / `<style>` / JSON-LD / `data-*` attributes. Treat a surviving literal starter default on a content/commerce surface as a polish blocker — replace it from the prepared source / CampaignSpec; do not draft substitute copy (if the design's authored copy is genuinely missing, flag it rather than invent it). This is *copy* residue, complementary to the computed-style / asset residue gated by `next-campaigns-qa` + the brand contract — not a duplicate of it.
|
|
59
|
-
- Flag template *defaults* left where they disagree with the prepared design — e.g. the same benefit icon repeated across a grid *when the design uses distinct icons*, or a guarantee badge/term that disagrees with the design — as polish defects, not just logos. Judge against the prepared source, not taste: copy or imagery the source/CampaignSpec does not supply (e.g. a placeholder testimonial name/quote/role) is residue; "looks generic" on its own is not.
|
|
59
|
+
- Flag template *defaults* left where they disagree with the prepared design — e.g. the same benefit icon repeated across a grid *when the design uses distinct icons*, or a guarantee badge/term that disagrees with the design — as polish defects, not just logos. Judge against the prepared source, not taste: copy or imagery the source/CampaignSpec does not supply (e.g. a placeholder testimonial name/quote/role) is residue; "looks generic" on its own is not. Copy the source does supply, including its proof and urgency elements (reviews, "Verified Purchase" labels, recent-purchase popups, stock counters, countdowns, guarantees), is the merchant's content: keep it as designed and do not record it as an issue or an unconfirmed claim.
|
|
60
60
|
- **Brand-bleed (cloned-source de-brand) pass.** When a campaign is cloned from a proven sibling, the sibling's brand defaults ride along. Inspect the built pages and assets for residual cross-brand bleed and clear it before recording: (1) a residual promo/sale banner or coupon code/copy from the source campaign (including a baked-in *fake* code); (2) a prior-campaign / sibling favicon left in place; (3) scaffold or non-design fonts the design did not specify (e.g. starter `Plus Jakarta`); (4) hardcoded non-token colors — any brand color literal that should be a token, such as next-core's `#C670FE` "Most Popular" pill. Clear each from the prepared source / CampaignSpec and brand theme (tokens, not literals); flag — do not invent — anything the design genuinely doesn't supply. Treat surviving bleed as a polish blocker. This complements the favicon/logo and copy-residue checks above; it is the cross-brand contamination angle, not a duplicate.
|
|
61
61
|
- Read the assembly report decisions before polishing. Do not reintroduce source-HTML elements that build intentionally dropped because CampaignSpec/API did not support them, such as unavailable payment methods.
|
|
62
62
|
- **Remove or rename a contract-listed payment-chrome asset; never edit one in place.** The assets named in `contracts/template-brand-contract.<family>.v0.json` under `default_residue.payment_chrome.assets` are keyed by QA on the *referenced basename*, not on their contents. Stripping the unsupported marks from inside a shared file such as `upsell-payment-logos.svg` leaves the reference in place, so QA reports residue for an asset that no longer carries any — and on 2026-09-06 the repair loop's remedy for that report deleted a cards-only trust strip that was correct. Delete the asset, or write a new one under a new name and repoint the reference. QA downgrades an edited-in-place asset to `manual_review` rather than a blocker, but that is a safety net for a mistake, not the supported way to do this.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-qa
|
|
3
|
-
version: 1.3.
|
|
3
|
+
version: 1.3.26
|
|
4
4
|
description: Run spec-aware QA from a saved Map or local-spec Build Packet and tested campaign URL after build, polish, and deploy/local evidence exist, including Playwright typed-card test-order proof.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.48.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.48.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
package/skills.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "campaigns-os-skills",
|
|
3
3
|
"description": "Skills bundled with Campaigns OS. `skills.sh` installs these into the shared agent skill directories (~/.claude/skills, ~/.codex/skills), so each one is a versioned package: bump the version whenever a package changes. `bundle_revision` identifies the bundle as a whole — `<package version>+skills.<n>`, where the prefix is this package's version and `<n>` counts the skill-text revisions published against it. It advances whenever ANY bundled skill changes, every SKILL.md states it on its first body line, and `campaigns-os tooling status --skills-revision <value>` compares the value an agent read from a skill against the bundle on disk. See docs/skills-revision.md.",
|
|
4
|
-
"bundle_revision": "1.
|
|
4
|
+
"bundle_revision": "1.48.0+skills.1",
|
|
5
5
|
"homepage": "https://github.com/NextCommerceCo/campaigns-os",
|
|
6
6
|
"retired_skills": [
|
|
7
7
|
{
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"id": "next-campaigns-os",
|
|
18
18
|
"name": "Campaigns OS Lifecycle",
|
|
19
|
-
"version": "1.0.
|
|
19
|
+
"version": "1.0.42",
|
|
20
20
|
"path": "skills/next-campaigns-os/SKILL.md",
|
|
21
21
|
"domain": "campaigns",
|
|
22
22
|
"description": "Coordinate Campaigns OS lifecycle workflows from CampaignSpec, Build Packet, starter-template contracts, stage reports, deploy evidence, and QA proof depth."
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"id": "next-campaigns-os-setup",
|
|
26
26
|
"name": "Campaigns OS Setup",
|
|
27
|
-
"version": "2.0.
|
|
27
|
+
"version": "2.0.25",
|
|
28
28
|
"path": "skills/next-campaigns-os-setup/SKILL.md",
|
|
29
29
|
"domain": "campaigns",
|
|
30
30
|
"description": "Bootstrap or prepare a target page-kit campaign repo from a doctor-cleared Campaigns OS Build Packet before full build wiring. Formerly next-campaigns-setup; renamed to release that name to the published NextCommerceCo/skills scaffolder."
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
{
|
|
33
33
|
"id": "next-campaigns-build",
|
|
34
34
|
"name": "Campaign Build",
|
|
35
|
-
"version": "1.0.
|
|
35
|
+
"version": "1.0.27",
|
|
36
36
|
"path": "skills/next-campaigns-build/SKILL.md",
|
|
37
37
|
"domain": "campaigns",
|
|
38
38
|
"description": "Assemble a NEXT campaign from a doctor-cleared Build Packet, CampaignSpec/API values, prepared HTML/assets, page-kit, and starter-template contracts."
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
{
|
|
41
41
|
"id": "next-campaigns-polish",
|
|
42
42
|
"name": "Campaign Polish",
|
|
43
|
-
"version": "1.1.
|
|
43
|
+
"version": "1.1.26",
|
|
44
44
|
"path": "skills/next-campaigns-polish/SKILL.md",
|
|
45
45
|
"domain": "campaigns",
|
|
46
46
|
"description": "Run the visual/runtime polish pass after build and before QA for a Campaigns OS campaign."
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
{
|
|
49
49
|
"id": "next-campaigns-qa",
|
|
50
50
|
"name": "Campaign QA",
|
|
51
|
-
"version": "1.3.
|
|
51
|
+
"version": "1.3.26",
|
|
52
52
|
"path": "skills/next-campaigns-qa/SKILL.md",
|
|
53
53
|
"domain": "campaigns",
|
|
54
54
|
"description": "Run spec-aware QA from a saved Map or local-spec Build Packet and tested campaign URL after build, polish, and deploy/local evidence exist, including Playwright typed-card test-order proof."
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
{
|
|
57
57
|
"id": "campaign-lifecycle-orientation",
|
|
58
58
|
"name": "Campaign Lifecycle Orientation",
|
|
59
|
-
"version": "1.0.
|
|
59
|
+
"version": "1.0.22",
|
|
60
60
|
"path": "skills/campaign-lifecycle-orientation/SKILL.md",
|
|
61
61
|
"domain": "campaigns",
|
|
62
62
|
"description": "Orient a reader to the Campaigns OS lifecycle artifacts a run has already emitted, without advancing their state: the shared vocabulary, doctor as a gate, the stage record, and the store-theme/Page Kit two-worlds trap."
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
{
|
|
65
65
|
"id": "campaign-run-evidence",
|
|
66
66
|
"name": "Campaign Run Evidence",
|
|
67
|
-
"version": "1.0.
|
|
67
|
+
"version": "1.0.22",
|
|
68
68
|
"path": "skills/campaign-run-evidence/SKILL.md",
|
|
69
69
|
"domain": "campaigns",
|
|
70
70
|
"description": "Interpret existing doctor, QA verdict and proof-depth evidence without claiming more proof than the artifacts contain, and keep funnel proof apart from merchant launch readiness."
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
{
|
|
73
73
|
"id": "campaign-readback-classification",
|
|
74
74
|
"name": "Campaign Readback Classification",
|
|
75
|
-
"version": "1.0.
|
|
75
|
+
"version": "1.0.22",
|
|
76
76
|
"path": "skills/campaign-readback-classification/SKILL.md",
|
|
77
77
|
"domain": "campaigns",
|
|
78
78
|
"description": "Classify a selected campaign from the campaigns-os-readback/v2 fields (artifacts, staleness.stale_keys, clean, doctor, divergences, skip_cascades) and write a read-only handoff without turning diagnosis into permission."
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
{
|
|
81
81
|
"id": "contribution-intake",
|
|
82
82
|
"name": "Contribution Intake",
|
|
83
|
-
"version": "1.0.
|
|
83
|
+
"version": "1.0.22",
|
|
84
84
|
"path": "skills/contribution-intake/SKILL.md",
|
|
85
85
|
"domain": "campaigns",
|
|
86
86
|
"description": "Turn a suggestion about the agent surface into a classified, evidence-checked and redacted proposal, filed on this repository tracker only with attended approval."
|
package/src/cli.mjs
CHANGED
|
@@ -296,15 +296,18 @@ Usage:
|
|
|
296
296
|
campaigns-os start (--spec <json> | --map-id <id>) --source <html-dir> --target <page-kit-dir> --template-family <family>
|
|
297
297
|
[--brief <yaml|json>] [--proxy-base <url>] [--cached-spec] [--theme-policy <inspect_only|auto|off>]
|
|
298
298
|
[--wrapper-policy <strip_document_wrappers|preserve_document_wrappers|not_required|unknown>] [--design-manifest <path>]
|
|
299
|
-
[--allow-uncertified-template "<reason>"] [--order-path-depth <off|common|full>]
|
|
299
|
+
[--allow-uncertified-template "<reason>"] [--order-path-depth <off|common|full>]
|
|
300
|
+
[--deploy-target <target>] [--preview-url <url>] [--production-url <url>] [--no-run-session] [--force] # --force overwrites an assembly report that carries stage evidence (destructive; prints the cleared stage keys) and regenerates a stale Design Source Package an earlier intake synthesized and nobody changed
|
|
300
301
|
campaigns-os prepare-build (--spec <json> | --map-id <id>) --source <html-dir> --target <page-kit-dir> --template-family <family>
|
|
301
302
|
[--brief <yaml|json>] [--proxy-base <url>] [--cached-spec] [--theme-policy <inspect_only|auto|off>]
|
|
302
303
|
[--wrapper-policy <strip_document_wrappers|preserve_document_wrappers|not_required|unknown>] [--design-manifest <path>]
|
|
303
|
-
[--allow-uncertified-template "<reason>"] [--order-path-depth <off|common|full>]
|
|
304
|
+
[--allow-uncertified-template "<reason>"] [--order-path-depth <off|common|full>]
|
|
305
|
+
[--deploy-target <target>] [--preview-url <url>] [--production-url <url>] [--no-run-session] [--force]
|
|
304
306
|
campaigns-os build (--spec <json> | --map-id <id>) --source <html-dir> --target <page-kit-dir> --template-family <family>
|
|
305
307
|
[--brief <yaml|json>] [--proxy-base <url>] [--cached-spec] [--theme-policy <inspect_only|auto|off>]
|
|
306
308
|
[--wrapper-policy <strip_document_wrappers|preserve_document_wrappers|not_required|unknown>] [--design-manifest <path>]
|
|
307
|
-
[--allow-uncertified-template "<reason>"] [--order-path-depth <off|common|full>]
|
|
309
|
+
[--allow-uncertified-template "<reason>"] [--order-path-depth <off|common|full>]
|
|
310
|
+
[--deploy-target <target>] [--preview-url <url>] [--production-url <url>] [--no-run-session] [--force] # intake alias for prepare-build + doctor
|
|
308
311
|
campaigns-os doctor --packet <campaign-runtime.build.json> [--context <json>] [--report <json>] [--strip-paths] [--write] [--no-write] [--doctor-out <path>] [--proxy-base <url>] [--no-live-refs] [--json] # inspection by default; --doctor-out requires --write; --no-write wins. When the packet's built _site/<route>/ exists and a public Campaigns API key resolves (packet, its local CampaignSpec, or the declared campaign-key env var), doctor makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check each built page's shipping and package refs against the live campaign; --proxy-base overrides the canonical proxy (https, or a loopback host over http). --no-live-refs skips the read and records not_run with reason disabled. No key, no built page, or a failed read records derived.live_campaign_refs as not_run with its reason. Only doctor and qa run make this read; other commands that run doctor record not_read
|
|
309
312
|
campaigns-os doctor --built <page-kit-target-repo> --family <family> [--slug <slug>] [--base-url <url>] [--emit-packet [path]] [--json] # L7: doctor a built _site/ with no Build Packet
|
|
310
313
|
campaigns-os bundle check --packet <campaign-runtime.build.json> [--require-qa] [--json] # validate the canonical migration/readback JSON bundle; never substitutes markdown
|
package/src/content-residue.mjs
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
// Rendered-output content-residue scan + proof-attestation gate.
|
|
2
2
|
//
|
|
3
3
|
// Scans BUILT campaign output (_site HTML), not frontmatter: layout- or
|
|
4
|
-
// script-rendered
|
|
5
|
-
//
|
|
4
|
+
// script-rendered chrome only exists after the build, which is why
|
|
5
|
+
// frontmatter-level checks missed the hardcoded starter countdown chrome.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// in this public package.
|
|
7
|
+
// What it looks for is template residue, never the merchant's own copy: the
|
|
8
|
+
// public starter-template demo strings, bracket-style demo stubs, and the
|
|
9
|
+
// literal needs-merchant-input marker. Proof and urgency content the merchant
|
|
10
|
+
// supplies (reviews, ratings, "Verified Purchase" labels, stock counters,
|
|
11
|
+
// countdowns) is the merchant's responsibility and is not scanned. No
|
|
12
|
+
// merchant-specific fingerprints are carried in this public package.
|
|
14
13
|
//
|
|
15
|
-
// Posture (
|
|
16
|
-
// - hard: the literal needs-merchant-input marker, and
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
14
|
+
// Posture (two tiers):
|
|
15
|
+
// - hard: the literal needs-merchant-input marker, and starter countdown
|
|
16
|
+
// chrome on a brief-backed build whose brief does not verify the offer
|
|
17
|
+
// urgency — blockers (collect-inputs).
|
|
18
|
+
// - review: demo/placeholder residue — warnings; a hit means an unreplaced
|
|
19
|
+
// demo slot or a stale template.
|
|
21
20
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
22
21
|
import { join, relative } from "node:path";
|
|
23
22
|
|
|
@@ -55,63 +54,6 @@ export const DEMO_RESIDUE_TERMS = Object.freeze([
|
|
|
55
54
|
"Sell-Out Risk: High",
|
|
56
55
|
]);
|
|
57
56
|
|
|
58
|
-
// Generic anti-pattern classes (review tier). Each id is stable so the
|
|
59
|
-
// attestation/review UX can key on it.
|
|
60
|
-
export const CONTENT_ANTI_PATTERNS = Object.freeze([
|
|
61
|
-
{
|
|
62
|
-
id: "invented_counts",
|
|
63
|
-
antiPattern: 1,
|
|
64
|
-
rule: "Counts/ratings/percent-recommend claims require a real, brief-sourced basis.",
|
|
65
|
-
// Count branch requires a proof-scaled number (comma groups, 4+ digits, or
|
|
66
|
-
// a trailing +) so ordinary commerce quantities ("Choose 3 pairs and
|
|
67
|
-
// save") never enter the review queue; "pairs"/"sold" dropped as nouns
|
|
68
|
-
// for the same reason. Small invented counts are a knowingly accepted gap.
|
|
69
|
-
regex: /\b(?:\d{1,3}(?:,\d{3})+|\d{4,}|\d+\+)\s+(?:reviews?|ratings?|customers?|users?|famil(?:y|ies)|wearers?|people)\b|\b(?:4\.[5-9]|5\.0)\s*(?:\/\s*5|stars?)|\b(?:9[0-9]|100)%\b[^<]{0,60}\b(?:recommend|reported|said|would)\b/i,
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: "verified_buyer_chrome",
|
|
73
|
-
antiPattern: 2,
|
|
74
|
-
rule: "'Verified' labels must resolve to a real approved review source.",
|
|
75
|
-
regex: /Verified\s+(?:Buyer|Customer|Purchase)|What\s+(?:Our\s+)?Customers\s+(?:Think|Say)|Real\s+(?:People|Customers)[^<]{0,20}Real\s+(?:Results|Relief)|5[- ]Star\s+Review/i,
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: "byline_persona",
|
|
79
|
-
antiPattern: 3,
|
|
80
|
-
rule: "Advertorial identities must be real, brief-supplied, and authorized.",
|
|
81
|
-
regex: /Mom\s+of\s+Two|Consumer\s+Report|Review\s+Team|Wellness\s+Educator|Licensed\s+(?:Physiotherapist|Professional)/i,
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
id: "borrowed_authority",
|
|
85
|
-
antiPattern: 4,
|
|
86
|
-
rule: "Expert/clinician/institution references require name, credential, permission, and source.",
|
|
87
|
-
regex: /\bDr\.\s+[A-Z]|\bM\.?D\.?\b|doctor[- ]recommended|clinically\s+(?:recognized|recommended)|expert\s+(?:says|recommends)/i,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
id: "press_marquee",
|
|
91
|
-
antiPattern: 8,
|
|
92
|
-
rule: "Press mentions require a brief-supplied working URL for the exact merchant and product.",
|
|
93
|
-
regex: /As\s+Seen\s+(?:On|In)|Featured\s+(?:On|In)/i,
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
id: "science_theater",
|
|
97
|
-
antiPattern: 9,
|
|
98
|
-
rule: "Study/clinical/certification claims require citation metadata and approved wording.",
|
|
99
|
-
regex: /peer[- ]reviewed|science[- ]backed|backed\s+by\s+science|stud(?:y|ies)\s+(?:show|prove|confirm)|researchers\s+found|clinically\s+(?:proven|shown|tested)|NASA[- ]developed/i,
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: "scarcity_theater",
|
|
103
|
-
antiPattern: 10,
|
|
104
|
-
rule: "Urgency renders only from a real, approved promotion window or live inventory source.",
|
|
105
|
-
regex: /ENDS\s+AT\s+MIDNIGHT|Offer\s+Expires|Deal\s+Ending|Only\s+\d+\s+(?:Units\s+)?Left|Stock\s+(?:Levels?\s+)?Low|\d+%\s+Sold|Sell[- ]?Out\s+Risk|supplies\s+are\s+limited/i,
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
id: "fake_comparison",
|
|
109
|
-
antiPattern: 11,
|
|
110
|
-
rule: "Tested-N/showdown framing requires a brief-supplied comparison matrix and test record.",
|
|
111
|
-
regex: /(?:we\s+)?tested\s+\d+\s+(?:contenders|products|devices|gloves|combinations|brands)|only\s+one\s+(?:survived|worked|stood)|Competitor\s+[12]\b/i,
|
|
112
|
-
},
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
57
|
const ENTITIES = new Map([
|
|
116
58
|
["&", "&"], ["<", "<"], [">", ">"], [""", '"'],
|
|
117
59
|
["'", "'"], ["'", "'"], [" ", " "], ["’", "’"], ["—", "—"],
|
|
@@ -168,13 +110,12 @@ function excerptAt(html, index, span = 80) {
|
|
|
168
110
|
}
|
|
169
111
|
|
|
170
112
|
// Pure scan of one rendered HTML document. Returns { hard: [], review: [] };
|
|
171
|
-
// each finding: { id, tier, rule?, excerpt }.
|
|
172
|
-
// view (attributes count, comments
|
|
173
|
-
//
|
|
174
|
-
//
|
|
113
|
+
// each finding: { id, tier, rule?, excerpt }. The hard checks and the demo
|
|
114
|
+
// terms run on the markup view (attributes count, comments and script/style
|
|
115
|
+
// bodies do not), so attribute residue is still caught; bracket stubs run on
|
|
116
|
+
// the same view with tags stripped, so CSS attribute selectors never match.
|
|
175
117
|
export function scanRenderedHtml(html, { urgencyVerified = false } = {}) {
|
|
176
118
|
const markup = markupView(typeof html === "string" ? html : "");
|
|
177
|
-
const text = visibleText(typeof html === "string" ? html : "");
|
|
178
119
|
const hard = [];
|
|
179
120
|
const review = [];
|
|
180
121
|
|
|
@@ -205,19 +146,6 @@ export function scanRenderedHtml(html, { urgencyVerified = false } = {}) {
|
|
|
205
146
|
}
|
|
206
147
|
}
|
|
207
148
|
|
|
208
|
-
for (const pattern of CONTENT_ANTI_PATTERNS) {
|
|
209
|
-
if (pattern.id === "scarcity_theater" && urgencyVerified) continue;
|
|
210
|
-
const match = pattern.regex.exec(text);
|
|
211
|
-
if (match) {
|
|
212
|
-
review.push({
|
|
213
|
-
id: pattern.id,
|
|
214
|
-
tier: "review",
|
|
215
|
-
antiPattern: pattern.antiPattern,
|
|
216
|
-
rule: pattern.rule,
|
|
217
|
-
excerpt: excerptAt(text, match.index),
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
149
|
return { hard, review };
|
|
222
150
|
}
|
|
223
151
|
|