wizz-method 2.20.1 → 2.22.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.
Files changed (116) hide show
  1. package/THIRD-PARTY-NOTICES.md +18 -0
  2. package/package.json +4 -2
  3. package/skills-registry.yaml +41 -0
  4. package/src/bmm-skills/1-analysis/wizz-set-feature/SKILL.md +1 -1
  5. package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +1 -1
  6. package/src/bmm-skills/4-implementation/wizz-create-story/SKILL.md +1 -1
  7. package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +8 -1
  8. package/src/bmm-skills/4-implementation/wizz-quick-dev/SKILL.md +2 -2
  9. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-03-implement.md +2 -0
  10. package/src/bmm-skills/4-implementation/wizz-sprint-status/SKILL.md +1 -1
  11. package/src/core-skills/wizz-party-mode/customize.toml +3 -3
  12. package/src/modules/lowticket/skills/wizz-lowticket-funil/SKILL.md +1 -1
  13. package/src/modules/lowticket/skills/wizz-lowticket-guia/SKILL.md +2 -2
  14. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/SKILL.md +5 -5
  15. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/01-principios.md +1 -1
  16. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/02-diagnostico-funil.md +1 -1
  17. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/03-campanha-bidcap.md +1 -1
  18. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/04-criativos.md +2 -2
  19. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/04a-criativos-ia.md +6 -6
  20. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/04b-criativos-formatos.md +1 -1
  21. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/04c-criativos-roteiro.md +5 -5
  22. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/05-oferta.md +1 -1
  23. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/06-pagina-vendas.md +3 -1
  24. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/06a-instagram.md +2 -2
  25. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/06b-player-vsl.md +1 -1
  26. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/07-funil-upsell.md +1 -1
  27. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/07a-copy-humanizacao.md +1 -1
  28. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/08-pesquisa-publico.md +1 -1
  29. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/09-especialista-coproducao.md +1 -1
  30. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/10-levantar-caixa.md +1 -1
  31. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/11-checklist-roi.md +1 -1
  32. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/12-mineracao-ofertas.md +1 -1
  33. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/12a-mineracao-veredito.md +1 -1
  34. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/12b-fabrica-de-ofertas.md +2 -2
  35. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/12c-anatomia-low-ticket.md +3 -1
  36. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/13-benchmarks-vsl.md +1 -1
  37. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/14-vocabulario.md +1 -1
  38. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/15-como-estender.md +2 -2
  39. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/17-rastreamento.md +1 -1
  40. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/18-copy-desejo-oferta.md +3 -3
  41. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/INDEX.md +14 -12
  42. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/guardrails.md +18 -70
  43. package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/pontos-em-aberto.md +9 -9
  44. package/src/modules/lowticket/skills/wizz-lowticket-minerador/SKILL.md +1 -1
  45. package/src/modules/lowticket/skills/wizz-lowticket-pagina/SKILL.md +1 -1
  46. package/src/modules/lowticket/skills/wizz-lowticket-trafego/SKILL.md +1 -1
  47. package/src/modules/lowticket/skills/wizz-lowticket-trafego/references/reguas.md +2 -0
  48. package/src/modules/wizz/_shared/cerebro.md +1 -1
  49. package/src/modules/wizz/_shared/organizacao.md +2 -2
  50. package/src/modules/wizz/agents/wizz-copy/SKILL.md +1 -0
  51. package/src/modules/wizz/agents/wizz-copy/customize.toml +5 -0
  52. package/src/modules/wizz/agents/wizz-copy/knowledge/playbook.md +2 -1
  53. package/src/modules/wizz/agents/wizz-growth/knowledge/playbook.md +1 -1
  54. package/src/modules/wizz/agents/wizz-social/knowledge/blueprint-roteiro-viral.md +14 -0
  55. package/src/modules/wizz/agents/wizz-social/knowledge/legenda-instagram.md +12 -0
  56. package/src/skills-lib/wizz-cerebro/SKILL.md +18 -5
  57. package/src/skills-lib/wizz-design-needs/SKILL.md +7 -0
  58. package/src/skills-lib/wizz-design-needs/references/design-state.md +34 -2
  59. package/src/skills-lib/wizz-design-needs/references/gap-analysis.md +1 -1
  60. package/src/skills-lib/wizz-design-needs/scripts/design_state.py +305 -0
  61. package/src/skills-lib/wizz-design-needs/scripts/tests/test_design_state.py +143 -0
  62. package/src/skills-lib/wizz-impeccable/references/design-rules.md +18 -0
  63. package/src/skills-lib/wizz-motion-3d-director/references/motion-paths.md +2 -0
  64. package/src/skills-lib/wizz-paid-ads/SKILL.md +2 -1
  65. package/src/skills-lib/wizz-premium-landing-ui-researcher/SKILL.md +6 -2
  66. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/audit-protocol.md +7 -34
  67. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +2 -0
  68. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/design-sources.yaml +252 -0
  69. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mandatory-process.md +1 -1
  70. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mining-rubric.md +123 -0
  71. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +10 -4
  72. package/src/skills-lib/wizz-premium-landing-ui-researcher/references/web-access-and-evidence.md +1 -1
  73. package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/design_sources.py +221 -0
  74. package/src/skills-lib/wizz-premium-landing-ui-researcher/scripts/tests/test_design_sources.py +130 -0
  75. package/src/skills-lib/wizz-router/references/routing-table-flat.md +2 -0
  76. package/src/skills-lib/wizz-taste-skill/references/anti-slop-tells.md +8 -0
  77. package/src/skills-lib/wizz-ui-component-curator/SKILL.md +8 -7
  78. package/src/skills-lib/wizz-video-effects/LICENSE-motionmaxxing.txt +202 -0
  79. package/src/skills-lib/wizz-video-effects/SKILL.md +5 -2
  80. package/src/skills-lib/wizz-video-effects/assets/chrome/collect_layout.js +113 -0
  81. package/src/skills-lib/wizz-video-effects/assets/effects.config.template.json +35 -0
  82. package/src/skills-lib/wizz-video-effects/references/data-contract.md +30 -1
  83. package/src/skills-lib/wizz-video-effects/references/gates.md +3 -1
  84. package/src/skills-lib/wizz-video-effects/scripts/check_chrome.py +376 -0
  85. package/src/skills-lib/wizz-video-effects/scripts/check_dead_frames.py +193 -0
  86. package/src/skills-lib/wizz-video-effects/scripts/config.py +13 -0
  87. package/src/skills-lib/wizz-video-effects/scripts/test_chrome.py +181 -0
  88. package/src/skills-lib/wizz-video-effects/scripts/test_dead_frames.py +171 -0
  89. package/src/skills-lib/wizz-video-styles/SKILL.md +6 -2
  90. package/src/skills-lib/wizz-video-styles/references/motion-laws.md +62 -0
  91. package/src/skills-lib/wizz-video-styles/references/styles/desafio-numero.md +1 -0
  92. package/src/skills-lib/wizz-video-styles/scripts/blind_review.py +159 -0
  93. package/src/skills-lib/wizz-video-styles/scripts/taste.mjs +110 -0
  94. package/src/skills-lib/wizz-video-styles/scripts/test_blind_review.py +82 -0
  95. package/src/skills-lib/wizz-vsl/LICENSE +49 -0
  96. package/src/skills-lib/wizz-vsl/SKILL.md +122 -0
  97. package/src/skills-lib/wizz-vsl/references/assembly-qa.md +123 -0
  98. package/src/skills-lib/wizz-vsl/references/blocks-and-leads.md +149 -0
  99. package/src/skills-lib/wizz-vsl/references/diagnosis.md +95 -0
  100. package/src/skills-lib/wizz-vsl/references/intake.md +200 -0
  101. package/src/skills-lib/wizz-vsl/references/mechanisms.md +138 -0
  102. package/src/skills-lib/wizz-vsl/references/niches.md +40 -0
  103. package/src/skills-lib/wizz-vsl/references/offer.md +91 -0
  104. package/src/skills-lib/wizz-vsl/references/production.md +75 -0
  105. package/tools/hooks/README.md +10 -9
  106. package/tools/hooks/wizz-arrumar.js +1 -0
  107. package/tools/hooks/wizz-artifact-guard.js +31 -5
  108. package/tools/hooks/wizz-cerebro-lib.js +36 -13
  109. package/tools/hooks/wizz-feature-lib.js +4 -1
  110. package/tools/hooks/wizz-feature.js +66 -76
  111. package/tools/hooks/wizz-sprint-archive-lib.js +311 -0
  112. package/tools/installer/core/hooks-settings.js +1 -0
  113. package/tools/installer/core/installer.js +6 -0
  114. package/tools/installer/modules/core-seed.js +72 -0
  115. package/tools/installer/modules/mcp-config.js +3 -0
  116. package/tools/installer/ui.js +16 -26
@@ -27,6 +27,7 @@ Food, money, products, everyday economics. The highest single reach in the set (
27
27
  **Look**
28
28
 
29
29
  - A visible progress device fixed on screen: time icons (1 s / 1 min / 1 h) changing colour per stage, emoji checklist, price tags on each ingredient.
30
+ It is measured for this style, so the chrome lint of `wizz-video-effects` takes `chrome_allow: ["P1", "C3"]` in the timeline (progress and stage counter).
30
31
  - Close hands, real places (market, street), a reaction at the end.
31
32
 
32
33
  **Palette:** Warm natural colours; tags in bright yellow with black outline; red/green icons for stages.
@@ -0,0 +1,159 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Blind A/B review of two or more versions of a piece.
3
+
4
+ python3 blind_review.py prepare OUT_DIR a.mp4 b.mp4 [c.mp4 ...]
5
+ python3 blind_review.py reveal OUT_DIR
6
+
7
+ prepare: links (or copies) each film to OUT_DIR/A.mp4, B.mp4 ... in a random
8
+ order, writes OUT_DIR/index.html (the films side by side at phone size, one
9
+ play button for all, sound per film) and the answer key in
10
+ OUT_DIR/.answer-key.json (mode 600). Running it again on the same folder and
11
+ films keeps the earlier shuffle, so a verdict already given stays valid.
12
+ reveal: prints which letter is which file. Only after the user picked.
13
+
14
+ Do not open the key or compare file sizes before judging: both give the
15
+ films away. Judge the picture silent first, then each soundtrack alone. The
16
+ user's pick goes to taste.mjs add.
17
+
18
+ Idea from motionmaxxing's blind_review.py (Apache-2.0,
19
+ github.com/Tejashmakwana/motionmaxxing); see THIRD-PARTY-NOTICES.md.
20
+
21
+ Exit code: 0 ok, 2 bad usage (fewer than 2 films, missing file, no key).
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import html
27
+ import json
28
+ import os
29
+ import secrets
30
+ import shutil
31
+ import string
32
+ import sys
33
+ from pathlib import Path
34
+
35
+ KEY_NAME = ".answer-key.json"
36
+
37
+ PAGE = """<!doctype html>
38
+ <html lang="en">
39
+ <head>
40
+ <meta charset="utf-8">
41
+ <meta name="viewport" content="width=device-width, initial-scale=1">
42
+ <title>Blind review</title>
43
+ <style>
44
+ :root {{ color-scheme: light dark; --bg: #f6f5f2; --ink: #1b1b1b; }}
45
+ @media (prefers-color-scheme: dark) {{ :root {{ --bg: #141414; --ink: #ececec; }} }}
46
+ body {{ margin: 0; padding: 16px; background: var(--bg); color: var(--ink); font: 16px/1.4 system-ui, sans-serif; }}
47
+ .row {{ display: flex; gap: 16px; flex-wrap: wrap; justify-content: center; }}
48
+ figure {{ margin: 0; text-align: center; }}
49
+ video {{ width: min(42vw, 360px); aspect-ratio: 9 / 16; background: #000; }}
50
+ button {{ font: inherit; padding: 6px 14px; margin: 4px; }}
51
+ p {{ max-width: 720px; margin: 8px auto 16px; text-align: center; }}
52
+ </style>
53
+ </head>
54
+ <body>
55
+ <p>Watch silent first, then each sound alone. Pick before revealing anything.</p>
56
+ <p><button id="play">Play all</button><button id="restart">Restart</button></p>
57
+ <div class="row">
58
+ {figures}
59
+ </div>
60
+ <script>
61
+ const vids = [...document.querySelectorAll("video")];
62
+ document.getElementById("play").onclick = () => {{
63
+ const go = vids[0].paused;
64
+ vids.forEach((v) => (go ? v.play() : v.pause()));
65
+ }};
66
+ document.getElementById("restart").onclick = () => vids.forEach((v) => {{ v.currentTime = 0; }});
67
+ document.querySelectorAll("[data-solo]").forEach((b) => {{
68
+ b.onclick = () => vids.forEach((v) => {{ v.muted = v.id !== b.dataset.solo; }});
69
+ }});
70
+ </script>
71
+ </body>
72
+ </html>
73
+ """
74
+
75
+ FIGURE = """<figure>
76
+ <video id="{letter}" src="{letter}.mp4" muted playsinline preload="auto"></video>
77
+ <figcaption><strong>{letter}</strong> <button data-solo="{letter}">Sound of {letter} only</button></figcaption>
78
+ </figure>"""
79
+
80
+
81
+ def assign(films: list[str]) -> dict[str, str]:
82
+ """Letter -> film, in a random order from the OS random source."""
83
+ pool = list(films)
84
+ order = []
85
+ while pool:
86
+ order.append(pool.pop(secrets.randbelow(len(pool))))
87
+ return dict(zip(string.ascii_uppercase, order))
88
+
89
+
90
+ def _place(src: str, dst: Path) -> None:
91
+ if dst.exists():
92
+ dst.unlink()
93
+ try:
94
+ os.link(src, dst)
95
+ except OSError:
96
+ shutil.copyfile(src, dst)
97
+
98
+
99
+ def prepare(out: Path, films: list[str]) -> int:
100
+ films = [os.path.abspath(f) for f in films]
101
+ if len(films) < 2 or len(set(films)) != len(films):
102
+ print("ERROR: give at least two different films")
103
+ return 2
104
+ missing = [f for f in films if not os.path.isfile(f)]
105
+ if missing:
106
+ print(f"ERROR: not found: {', '.join(missing)}")
107
+ return 2
108
+ if len(films) > len(string.ascii_uppercase):
109
+ print("ERROR: too many films")
110
+ return 2
111
+ out.mkdir(parents=True, exist_ok=True)
112
+ key_file = out / KEY_NAME
113
+ key = None
114
+ if key_file.exists():
115
+ old = json.loads(key_file.read_text(encoding="utf-8"))
116
+ if sorted(old.values()) == sorted(films):
117
+ key = old
118
+ key = key or assign(films)
119
+ for letter, film in key.items():
120
+ _place(film, out / f"{letter}.mp4")
121
+ key_file.write_text(json.dumps(key, indent=1) + "\n", encoding="utf-8")
122
+ os.chmod(key_file, 0o600)
123
+ figures = "\n".join(FIGURE.format(letter=html.escape(letter)) for letter in key)
124
+ (out / "index.html").write_text(PAGE.format(figures=figures), encoding="utf-8")
125
+ print(f"open {out / 'index.html'} ({len(key)} films, letters {', '.join(key)})")
126
+ print(f"after the pick: python3 blind_review.py reveal {out}")
127
+ return 0
128
+
129
+
130
+ def reveal(out: Path) -> int:
131
+ key_file = out / KEY_NAME
132
+ if not key_file.exists():
133
+ print(f"ERROR: no answer key in {out}")
134
+ return 2
135
+ for letter, film in json.loads(key_file.read_text(encoding="utf-8")).items():
136
+ print(f"{letter}: {film}")
137
+ return 0
138
+
139
+
140
+ def main(argv: list[str] | None = None) -> int:
141
+ ap = argparse.ArgumentParser(description="Blind A/B review page for versions of a piece.")
142
+ sub = ap.add_subparsers(dest="cmd", required=True)
143
+ p = sub.add_parser("prepare")
144
+ p.add_argument("out")
145
+ p.add_argument("films", nargs="+")
146
+ r = sub.add_parser("reveal")
147
+ r.add_argument("out")
148
+ args = ap.parse_args(argv)
149
+ try:
150
+ if args.cmd == "prepare":
151
+ return prepare(Path(args.out), args.films)
152
+ return reveal(Path(args.out))
153
+ except (OSError, json.JSONDecodeError) as e:
154
+ print(f"ERROR: {e}")
155
+ return 2
156
+
157
+
158
+ if __name__ == "__main__":
159
+ sys.exit(main())
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ // The user's taste for this project's videos: what they approved and rejected, in their words.
3
+ //
4
+ // node taste.mjs show [--style <key>] [--project <dir>]
5
+ // node taste.mjs add --verdict approved|rejected --piece <slug> --quote "<their words>"
6
+ // --lesson "<narrowest lesson>" [--about "<what they reacted to>"]
7
+ // [--style <key>] [--project <dir>]
8
+ //
9
+ // File: <project>/_wizz-output/video-styles/gosto.md, plain markdown the user can read and
10
+ // edit. Append-only: a new verdict never rewrites an old one; one that contradicts an earlier
11
+ // entry is added as a new entry and the reader trusts the newer date.
12
+ // show --style keeps the entries of that style plus the entries with no style (general taste).
13
+ // Read it before choosing a style (wizz-video-styles) and before deciding effects
14
+ // (wizz-video-effects); append after the user reviews a piece.
15
+ // Idea from motionmaxxing's taste/verdicts.md (Apache-2.0); see THIRD-PARTY-NOTICES.md.
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+
20
+ const FILE_REL = path.join('_wizz-output', 'video-styles', 'gosto.md');
21
+ const VERDICTS = new Set(['approved', 'rejected']);
22
+ const HEADER = [
23
+ '# Video taste',
24
+ '',
25
+ "What the user approved and rejected in this project's videos, in their words. Written by",
26
+ '`taste.mjs add` after each review; read before choosing a style or deciding effects.',
27
+ 'A verdict is about one piece: generalise its lesson only as far as its context goes.',
28
+ '',
29
+ ].join('\n');
30
+
31
+ function fail(message) {
32
+ process.stderr.write(`taste: ${message}\n`);
33
+ process.exit(2);
34
+ }
35
+
36
+ function parseArgs(argv) {
37
+ const [command, ...rest] = argv;
38
+ const options = { command, project: process.cwd() };
39
+ const flags = ['--verdict', '--piece', '--quote', '--lesson', '--about', '--style', '--project'];
40
+ for (let i = 0; i < rest.length; i++) {
41
+ if (!flags.includes(rest[i])) fail(`unknown argument: ${rest[i]}`);
42
+ if (i + 1 >= rest.length) fail(`${rest[i]} needs a value`);
43
+ options[rest[i].slice(2)] = rest[++i];
44
+ }
45
+ return options;
46
+ }
47
+
48
+ // One line, no markdown heading or list marker can start inside a value.
49
+ const oneLine = (value) =>
50
+ String(value ?? '')
51
+ .replaceAll(/\s+/g, ' ')
52
+ .trim();
53
+
54
+ function readEntries(file) {
55
+ let text;
56
+ try {
57
+ text = fs.readFileSync(file, 'utf8');
58
+ } catch (error) {
59
+ if (error.code === 'ENOENT') return null;
60
+ fail(`cannot read ${file}: ${error.message}`);
61
+ }
62
+ return text
63
+ .split(/^(?=## )/m)
64
+ .filter((chunk) => chunk.startsWith('## '))
65
+ .map((chunk) => {
66
+ const [, , , style = ''] = chunk.split('\n')[0].slice(3).split(' · ');
67
+ return { style: style.trim(), text: chunk.trimEnd() };
68
+ });
69
+ }
70
+
71
+ function show(options) {
72
+ const entries = readEntries(path.join(options.project, FILE_REL));
73
+ if (entries === null || entries.length === 0) {
74
+ process.stdout.write(`no verdicts recorded yet (${FILE_REL})\n`);
75
+ return;
76
+ }
77
+ const style = options.style ? oneLine(options.style) : '';
78
+ const kept = style ? entries.filter((e) => e.style === '' || e.style === style) : entries;
79
+ process.stdout.write(`${kept.map((e) => e.text).join('\n\n')}\n`);
80
+ }
81
+
82
+ function add(options) {
83
+ const verdict = oneLine(options.verdict);
84
+ if (!VERDICTS.has(verdict)) fail('--verdict must be approved or rejected');
85
+ const piece = oneLine(options.piece);
86
+ const quote = oneLine(options.quote);
87
+ const lesson = oneLine(options.lesson);
88
+ if (!piece || !quote || !lesson) fail('--piece, --quote and --lesson are required');
89
+ const style = oneLine(options.style);
90
+ const about = oneLine(options.about);
91
+ const date = new Date().toISOString().slice(0, 10);
92
+ const lines = [
93
+ `## ${[date, verdict, piece, style].filter(Boolean).join(' · ')}`,
94
+ '',
95
+ `- Quote: "${quote}"`,
96
+ ...(about ? [`- About: ${about}`] : []),
97
+ `- Lesson: ${lesson}`,
98
+ '',
99
+ ];
100
+ const file = path.join(options.project, FILE_REL);
101
+ fs.mkdirSync(path.dirname(file), { recursive: true });
102
+ if (!fs.existsSync(file)) fs.writeFileSync(file, HEADER);
103
+ fs.appendFileSync(file, `\n${lines.join('\n')}`);
104
+ process.stdout.write(`recorded ${verdict} for ${piece} in ${FILE_REL}\n`);
105
+ }
106
+
107
+ const options = parseArgs(process.argv.slice(2));
108
+ if (options.command === 'show') show(options);
109
+ else if (options.command === 'add') add(options);
110
+ else fail('usage: taste.mjs show|add (see the header of this file)');
@@ -0,0 +1,82 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Tests of blind_review.py: random labels, hidden key, re-run keeps the
3
+ shuffle, reveal, and refusals."""
4
+ import io
5
+ import json
6
+ import os
7
+ import stat
8
+ import tempfile
9
+ import unittest
10
+ from contextlib import redirect_stdout
11
+ from pathlib import Path
12
+
13
+ import blind_review as BR
14
+
15
+
16
+ def _films(tmp: str, n: int) -> list[str]:
17
+ out = []
18
+ for i in range(n):
19
+ p = Path(tmp) / f"version-{i}.mp4"
20
+ p.write_bytes(b"fake mp4 " + bytes([i]))
21
+ out.append(str(p))
22
+ return out
23
+
24
+
25
+ def _run(argv) -> tuple[int, str]:
26
+ buf = io.StringIO()
27
+ with redirect_stdout(buf):
28
+ code = BR.main(argv)
29
+ return code, buf.getvalue()
30
+
31
+
32
+ class TestPrepare(unittest.TestCase):
33
+ def test_page_key_and_labels(self):
34
+ with tempfile.TemporaryDirectory() as tmp:
35
+ films = _films(tmp, 2)
36
+ out = Path(tmp) / "review"
37
+ code, _ = _run(["prepare", str(out), *films])
38
+ self.assertEqual(code, 0)
39
+ key = json.loads((out / BR.KEY_NAME).read_text(encoding="utf-8"))
40
+ self.assertEqual(sorted(key), ["A", "B"])
41
+ self.assertEqual(sorted(key.values()), sorted(os.path.abspath(f) for f in films))
42
+ self.assertEqual(stat.S_IMODE((out / BR.KEY_NAME).stat().st_mode), 0o600)
43
+ page = (out / "index.html").read_text(encoding="utf-8")
44
+ self.assertIn("A.mp4", page)
45
+ self.assertIn("B.mp4", page)
46
+ for f in films: # the page never names the source files
47
+ self.assertNotIn(Path(f).name, page)
48
+ self.assertNotIn("—", page)
49
+
50
+ def test_rerun_keeps_the_shuffle(self):
51
+ with tempfile.TemporaryDirectory() as tmp:
52
+ films = _films(tmp, 3)
53
+ out = Path(tmp) / "review"
54
+ _run(["prepare", str(out), *films])
55
+ first = (out / BR.KEY_NAME).read_text(encoding="utf-8")
56
+ _run(["prepare", str(out), *films])
57
+ self.assertEqual((out / BR.KEY_NAME).read_text(encoding="utf-8"), first)
58
+
59
+ def test_shuffle_is_random(self):
60
+ orders = {tuple(BR.assign(["a", "b", "c", "d"]).values()) for _ in range(40)}
61
+ self.assertGreater(len(orders), 1)
62
+
63
+ def test_reveal_prints_the_key(self):
64
+ with tempfile.TemporaryDirectory() as tmp:
65
+ films = _films(tmp, 2)
66
+ out = Path(tmp) / "review"
67
+ _run(["prepare", str(out), *films])
68
+ code, text = _run(["reveal", str(out)])
69
+ self.assertEqual(code, 0)
70
+ self.assertIn("version-0.mp4", text)
71
+ self.assertIn("version-1.mp4", text)
72
+
73
+ def test_one_film_or_missing_file_is_refused(self):
74
+ with tempfile.TemporaryDirectory() as tmp:
75
+ films = _films(tmp, 1)
76
+ self.assertEqual(_run(["prepare", str(Path(tmp) / "r"), *films])[0], 2)
77
+ self.assertEqual(_run(["prepare", str(Path(tmp) / "r"), films[0], "/nope.mp4"])[0], 2)
78
+ self.assertEqual(_run(["reveal", str(Path(tmp) / "empty")])[0], 2)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ unittest.main()
@@ -0,0 +1,49 @@
1
+ wizz-vsl adapts material from two MIT-licensed skills. Their notices follow.
2
+
3
+ == yuribranco/vsl-skill (https://github.com/yuribranco/vsl-skill), commit fe855c50 ==
4
+
5
+ MIT License
6
+
7
+ Copyright (c) 2026 Yuri Branco
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ == bertrand-do/video-sales-letter (https://github.com/bertrand-do/video-sales-letter), commit f41eec5a ==
28
+
29
+ MIT License
30
+
31
+ Copyright (c) 2026 Bertrand Diouly
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining a copy
34
+ of this software and associated documentation files (the "Software"), to deal
35
+ in the Software without restriction, including without limitation the rights
36
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37
+ copies of the Software, and to permit persons to whom the Software is
38
+ furnished to do so, subject to the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be included in all
41
+ copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49
+ SOFTWARE.
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: wizz-vsl
3
+ description: Writes a video sales letter (VSL) from the expert's real material to a script ready to record, and plans its production. Interviews in 11 blocks, diagnoses awareness and sophistication, has a human pick one problem and one solution mechanism from a 5x5 document, writes backwards (offer, middle blocks, then 5 leads), never invents proof, and closes with a claims ledger, a beat sheet and a production hand-off with motion anchored to the final audio alignment. Reads the low ticket ruler first when the operation is low ticket. Use when someone wants a VSL written from scratch, wants to rebuild one that does not convert, needs the mechanism of an offer for a sales video, or needs lead variations to test. Triggers - faz a VSL do meu produto, roteiro do vídeo de vendas, a lead da VSL tá fraca, a retenção cai no primeiro minuto, refaz minha VSL. Does not write the sales page around the player (wizz-copywriting), short ad creatives (wizz-ad-creative) or Reels (wizz-social-content), and does not edit the video itself.
4
+ ---
5
+
6
+ # wizz-vsl: from the expert's briefing to a VSL ready to record
7
+
8
+ **The lead is the last thing written.** A VSL is derived: from what the expert really knows, from the real state of the market, and from the mechanism chosen. An agent that starts at the lead produces pretty, generic copy with no hypothesis, and when it does not convert nobody knows which piece was wrong. This skill imposes the reverse order: interview, diagnose, lock the mechanism, write the offer, climb up to the lead, then plan production.
9
+
10
+ Everything delivered to the user is in `{communication_language}` (Brazilian Portuguese by default, "você", spoken register). No em dash in anything a third party reads or hears.
11
+
12
+ ## Step 0 · Inputs and the ruler in effect
13
+
14
+ 1. **Resume first.** Look for `{output_folder}/vsl/<slug>/manifest.json`. If it exists, read it and `00-briefing.md`, say in one line where the work stopped, and resume from the first pending phase. Never re-interview someone who already answered.
15
+ 2. **Reuse what the method already produced.** An approved offer from the `wizz-offer-forge` skill (`{output_folder}/offers/OFFER-*.md`) is the source for the offer block and for the solution mechanism: record it in the manifest instead of re-deriving it. Audience research from the `wizz-pesquisa-de-publico-do-piva` skill (`{output_folder}/pesquisa-piva/`) is the source for blocks 2, 3, 8 and 10 of the interview: VoC, awareness levels, lexicon and angles come from there, quoted with file and section. When there is no approved offer, say so and invoke the `wizz-offer-forge` skill before Phase 4; when there is no audience research and the expert has no material, offer to invoke the `wizz-pesquisa-de-publico-do-piva` skill first.
16
+ 3. **Low ticket ruler.** The low ticket base applies only when BOTH are true: the `wizz-lowticket-metodologia` skill is installed in this project, AND the project context or the request itself establishes that the operation is low ticket (price range, cheap product, direct traffic). Do not presume low ticket because the product is a course or there is a campaign running. When both hold, before Phase 2, read its `knowledge/INDEX.md`, then `knowledge/13-benchmarks-vsl.md` and the VSL entries the INDEX names (`04b` §4.7 and §4.7-A, `18` §18.8). That ruler sets the length, the pitch position and the number of leads to test; where it contradicts this skill (the 16 to 20 minute default below), **the ruler wins**, and say so in the reply. In practice: a product that proves itself on screen stays at 90 s to 2 min, an offer that needs to build mechanism and authority goes to 7 to 9 min, below R$60 the ceiling is 7 to 8 min, the pitch enters at about two thirds of the video, and 3 to 8 leads get tested.
17
+
18
+ ## The six phases
19
+
20
+ | # | Phase | Read | Done when |
21
+ |---|---|---|---|
22
+ | 0 | Ingest what already exists | [references/intake.md](references/intake.md) §0 | inventory made, briefing pre-filled with sources |
23
+ | 1 | Interview whoever knows | [references/intake.md](references/intake.md) | `00-briefing.md` closed, or its gaps declared and accepted by the human |
24
+ | 2 | Market diagnosis (Schwartz) | [references/diagnosis.md](references/diagnosis.md) | **Gate 1**: human confirms awareness and sophistication |
25
+ | 3 | Mechanisms, 5 problem x 5 solution | [references/mechanisms.md](references/mechanisms.md) | **Gate 2**: human picks 1 + 1 |
26
+ | 4 | Writing, backwards | [references/offer.md](references/offer.md), then [references/blocks-and-leads.md](references/blocks-and-leads.md) | offer, 3 middle blocks and 5 leads written |
27
+ | 5 | Assembly, claims ledger, QA, test plan | [references/assembly-qa.md](references/assembly-qa.md) | checklist closed |
28
+ | 6 | Production plan and hand-off | [references/production.md](references/production.md) | beat sheet delivered, hand-off stated |
29
+
30
+ The niche changes tone, proof type, price and compliance in every phase: [references/niches.md](references/niches.md).
31
+
32
+ **Compress, never drop a phase.** When the user says "I already know the mechanism, just write it", Phase 1 shrinks to the mandatory blocks, Phase 2 fits in one screen (your reading plus their confirmation is Gate 1), and Phase 3 records the mechanism they already have, with their explicit confirmation of which one is master on each side (Gate 2). A decision already recorded in the manifest or in an approved offer is not asked again.
33
+
34
+ ## The two gates
35
+
36
+ **Gate 1 · diagnosis confirmed.** A wrong awareness level or sophistication stage dooms everything after it. Present your reading with the evidence from the briefing that supports it, and wait for the human to confirm or correct.
37
+
38
+ **Gate 2 · mechanisms approved.** The whole VSL derives from the mechanism; getting it wrong means re-recording the video. Present the 5x5 document; the human picks **one master problem mechanism and one master solution mechanism**, and only then Phase 4 starts.
39
+
40
+ Record both in `manifest.json` and check the manifest before writing any line of the VSL. An empty field means stop and go back to the missing phase. **A gate reopens when the base changes:** if the briefing, the offer or the price changes after Gate 2, clear `gate2_aprovado_em`, say what changed and present again.
41
+
42
+ ## Never present invented proof as real
43
+
44
+ Direct response copy that invents gets refunds, lawsuits and platform bans.
45
+
46
+ - Never invent a testimonial, number, study, student case, credential, date or story and present it as fact. Missing information becomes an explicit marker: `[DEPOIMENTO]`, `[NÚMERO A CONFIRMAR]`, `[A DEFINIR]`, `[LACUNA]`. Missing proof does not block the rest of the script: the marker holds the place.
47
+ - **Traceability.** When the source material (call, questionnaire, the expert's material, the approved offer) already carries the mechanism, the story or the number, it is the master source: cite where it came from (file plus timestamp or line). Your own synthesis never replaces it; it enters at most as a variation marked `🆕 proposta nova (não veio da fonte)`.
48
+ - **The numbers in this skill are not copy.** Benchmarks and percentages in the references (play rate, retention, conversion) are internal calibration ranges, not public facts. Never repeat them inside the VSL, in an ad or to the client as market fact.
49
+
50
+ | Rationalization | Reality |
51
+ |---|---|
52
+ | "It's just an illustrative testimonial" | It goes into the recorded video as written. Use `[DEPOIMENTO]`. |
53
+ | "This number is plausible for the niche" | Plausible is not verifiable. The expert answers or it stays `[NÚMERO A CONFIRMAR]`. |
54
+ | "My metaphor came out better than theirs" | Maybe. Mark it as a new proposal and let the human choose at Gate 2. |
55
+ | "The expert didn't answer, I'll deduce from the niche" | A niche deduction is a hypothesis, not a briefing. Mark `[LACUNA]`. |
56
+
57
+ **Stop when:** you are writing a number nobody gave you, telling a story that was not in the material, writing the offer block without knowing the price, or writing the lead before the offer.
58
+
59
+ ## Default length (outside the low ticket ruler)
60
+
61
+ A front-end VSL runs **16 to 20 minutes**: the four blocks before the pitch add up to 12 to 14 minutes and the pitch takes 4 to 6. When the diagnosis asks for a short VSL (awareness 4 or 5, volume niche), compress proportionally to 6 to 8 minutes with the pitch near the middle; the structure is the same. Treat length as an output of the argument: never pad or cut only to hit a runtime.
62
+
63
+ ## Output
64
+
65
+ In `{output_folder}/vsl/<slug>/` (slug from the product or expert; if neither has a name yet, the theme). Never rename the folder later. If several `vsl/*` folders exist, ask which one.
66
+
67
+ ```
68
+ 00-briefing.md interview answers, each with its source
69
+ 01-diagnostico.md mass desire, awareness, sophistication + evidence
70
+ 02-mecanismos-5x5.md 10 proposals + recommendation (decision document)
71
+ 03-vsl-principal.md the full VSL, in presentation order
72
+ 03-leads/ lead-01-<angulo>.md ... lead-05-<angulo>.md
73
+ 04-qa.md claims ledger, checklist, A/B test plan, target metrics
74
+ 05-beat-sheet.md narration, on-screen line, visual evidence and claim per beat
75
+ manifest.json phase state and both gates
76
+ ```
77
+
78
+ Create `manifest.json` in Phase 0 with what you already know (the rest stays `null`) and update `fases` at each transition:
79
+
80
+ ```json
81
+ {
82
+ "expert": "<nome>",
83
+ "produto": "<nome do produto>",
84
+ "nicho": "financeiro|saude|mudanca-de-vida|profissional|volume",
85
+ "low_ticket": false,
86
+ "regua": "default|lowticket",
87
+ "criado_em": "AAAA-MM-DD",
88
+ "fontes": { "oferta": null, "pesquisa_publico": null },
89
+ "fases": { "intake": "pendente", "diagnostico": "pendente", "mecanismos": "pendente",
90
+ "escrita": "pendente", "qa": "pendente", "producao": "pendente" },
91
+ "gate1_confirmado_em": null, "consciencia": null, "sofisticacao": null,
92
+ "gate2_aprovado_em": null, "aprovado_por": null,
93
+ "mecanismo_problema": null, "mecanismo_solucao": null, "camadas": []
94
+ }
95
+ ```
96
+
97
+ ## Writing order versus presentation order
98
+
99
+ | Written in this order | The viewer sees this order |
100
+ |---|---|
101
+ | 1. Offer (pitch) | 1. Lead |
102
+ | 2. Solution mechanism | 2. Problem mechanism |
103
+ | 3. Problem mechanism | 3. Story |
104
+ | 4. Story | 4. Solution mechanism |
105
+ | 5. **Lead (5 variations)** | 5. Offer (pitch) |
106
+
107
+ Writing the offer first is what makes the lead promise exactly what the offer delivers.
108
+
109
+ ## Hand-off
110
+
111
+ - **Review.** For the mechanism (Phase 3) and the lead, ask a different agent than the one that wrote it to judge each proposal as kill, keep with caveat, or keep. A disagreement between maker and checker goes to the human at Gate 2 with both opinions side by side.
112
+ - **Voice pass.** Invoke the `wizz-humanizer` skill on the assembled script after the claims are right, never before.
113
+ - **Production** is not this skill. The beat sheet goes to the designer area (offer `wizz-maestro` when the user wants the video in the same delivery): the TTS or recorded narration comes first, the `wizz-ctc-align` skill turns the final audio into word timings, `hyperframes` draws each silent scene, and the `wizz-remotion-best-practices` skill assembles the film with audio, captions and motion anchored to those timings. Details and the player QA: [references/production.md](references/production.md).
114
+ - **The page around the player** (no headline above the video, delayed button, no coupon) is copy for the `wizz-copywriting` skill with the rules listed in [references/offer.md](references/offer.md).
115
+
116
+ ## When not to use
117
+
118
+ A standalone ad, an email, a capture page, a post: they have neither the four blocks nor a pitch. Ad creative scripts are `wizz-ad-creative`; Reels and Shorts are `wizz-social-content`; the sales page text is `wizz-copywriting`.
119
+
120
+ ## Credits
121
+
122
+ Method synthesized and adapted from two MIT skills, with permission notices in [LICENSE](LICENSE) and `THIRD-PARTY-NOTICES.md`: `yuribranco/vsl-skill` (interview, gates, 5x5 mechanisms, backwards writing, leads, niches, QA), whose own lineage is the VSL method taught by Lucas Ramos (Veritas Club), Eugene Schwartz's *Breakthrough Advertising*, *Great Leads* (Masterson and Forde) and Alex Hormozi's value equation; and `bertrand-do/video-sales-letter` (claims ledger, script-only audit, beat sheet, word-timed motion, player QA, OTO and downsell). Read the originals: this skill works far better in the hands of someone who studied them.
@@ -0,0 +1,123 @@
1
+ # Phase 5 · Assembly, claims ledger, QA and test plan
2
+
3
+ ## Assembly: from writing order to presentation order
4
+
5
+ You wrote offer → solution → problem → story → leads. Now reassemble in the order the viewer sees:
6
+
7
+ ```
8
+ [GUARDRAIL · LEI] (cabeçalho interno, não vai pro vídeo)
9
+
10
+ LEAD (default: <qual e por quê>)
11
+ MECANISMO DO PROBLEMA
12
+ HISTÓRIA
13
+ MECANISMO DA SOLUÇÃO
14
+ OFERTA / PITCH ← entra na virada que a régua em vigor define
15
+ ```
16
+
17
+ Check the sum against the ruler in effect. Default: at 140 to 150 words per minute, the 12 to 14 minute window before the pitch asks for **1,750 to 2,000 words** in the first four blocks; below that the pitch falls too early, and the fix is deeper blocks, not an earlier pitch. Under the low ticket ruler, the pitch enters at about two thirds of the runtime it sets.
18
+
19
+ At the seams, check the **transitions**: each block ends pulling the next. Cut any recap ("como eu falei lá no começo"): in video it is permission to leave.
20
+
21
+ ### Script markers
22
+
23
+ | Marker | Where |
24
+ |---|---|
25
+ | `[DEPOIMENTO]` | where social proof goes (never write the testimonial) |
26
+ | `[BOTÃO APARECE AQUI]` | step 8 of the pitch, when the price is revealed (once) |
27
+ | `[CRONÔMETRO 60s]` | the urgency bonus, **if it exists** |
28
+ | `[A DEFINIR]` / `[NÚMERO A CONFIRMAR]` | any data the briefing did not bring |
29
+ | `[B-ROLL: …]` / `[TELA: …]` | suggested image or screen; feeds the beat sheet |
30
+
31
+ ### File header
32
+
33
+ ```markdown
34
+ # VSL · <produto> (DRAFT v1)
35
+
36
+ > **Régua:** <default 16-20 min | lowticket: <duração e posição do pitch, com o shard citado>>
37
+ > **Lead default:** <qual> · testar as de `03-leads/`.
38
+ > **Mecanismos aprovados (GATE 2, <data>):** problema = <P_> · solução = <S_>
39
+ > **Diagnóstico:** consciência N / sofisticação M
40
+ > **GUARDRAIL (LEI):** <proibições do Bloco 9>
41
+ > **Voz:** <resumo do Bloco 10>
42
+ > **Fontes:** `00-briefing.md`, <oferta aprovada>, <pesquisa do Piva>
43
+ ```
44
+
45
+ ## Claims ledger
46
+
47
+ Before QA, list every claim the script makes and check each against the **real product and the real terms**, not against an old handoff or a prior script: price, limits, allowances, speed, results, compatibility, scarcity, refund terms. Historical documents explain decisions but may carry obsolete versions.
48
+
49
+ | # | Claim (as spoken) | Type | Source that proves it | Status |
50
+ |---|---|---|---|---|
51
+ | 1 | "acesso por 12 meses" | access | checkout config, `00-briefing.md` 1.2 | ✅ confere |
52
+ | 2 | "resultado na primeira semana" | result | `[LACUNA 5.7]` | ⚠️ marcar `[A CONFIRMAR]` ou reescrever |
53
+
54
+ Separate product behaviour from buyer action: drafting is not sending; finding is not guaranteeing. A guarantee line matches the policy word for word. When a price, date or count changes, remove every stale mention together (script, on-screen lines, captions, page).
55
+
56
+ ## Script-only audit
57
+
58
+ 1. **Narration alone.** Read every spoken line without visuals. Does each beat add meaning?
59
+ 2. **On-screen lines alone.** Read only the text that will appear on screen. Does it form a coherent argument and match the narration?
60
+ 3. **Vague referents.** Resolve "isso", "ele", "outro", "mais uma parte", "o que vem a seguir" when the referent is not clear. Keep natural fragments and questions when their meaning is clear.
61
+ 4. **Proof matches claim.** The demo or proof shown proves the exact claim spoken, not a neighbouring one.
62
+
63
+ Invoke the `wizz-humanizer` skill as the final voice pass, after the claims are right.
64
+
65
+ ## QA checklist: close all before delivering
66
+
67
+ **Structure**
68
+ - [ ] Offer written first, the 15 steps covered (the ones that do not apply recorded as absent, not invented)
69
+ - [ ] Solution mechanism gives *what* + *why*, **not** the *how*
70
+ - [ ] Problem mechanism follows the formula (attempts + why they failed = root cause)
71
+ - [ ] Problem block ends with "existe uma saída"
72
+ - [ ] Story in the right format; no rock-bottom hero journey
73
+ - [ ] Leads with different angles, each with a teaser, in the number the ruler asks for
74
+ - [ ] Pitch position and total runtime match the ruler in effect
75
+
76
+ **Offer**
77
+ - [ ] Triad communicated; a missing layer flagged, never invented
78
+ - [ ] Modules described by benefit
79
+ - [ ] Anchor before price, against a real alternative
80
+ - [ ] Timer bonus, **or** recorded as non-existent and why
81
+ - [ ] Each bonus with its origin story
82
+ - [ ] Express stack with values
83
+ - [ ] Clear guarantee matching the real terms
84
+ - [ ] Emotional close + single CTA
85
+
86
+ **Integrity**
87
+ - [ ] No testimonial, number, study or case presented as real without a source
88
+ - [ ] Every claim in the ledger with a source or a marker
89
+ - [ ] Third-party names only with recorded authorization
90
+ - [ ] Compliance guardrail respected line by line
91
+ - [ ] No promise the offer does not deliver
92
+
93
+ **Voice and form**
94
+ - [ ] Spoken language, read aloud without stumbling
95
+ - [ ] The audience's vocabulary
96
+ - [ ] No AI words
97
+ - [ ] Second person, except where compliance requires third
98
+
99
+ **Page and player** (hand-off to `wizz-copywriting`)
100
+ - [ ] No headline above the video · no form before the VSL · no discount coupon · button delay after the price
101
+
102
+ ## Target metrics (internal calibration, front-end VSL)
103
+
104
+ > Reference ranges **without an auditable public source**: they decide what to optimize first. Never inside the VSL, an ad, or a client conversation as market fact. Under the low ticket ruler, the numbers in `13-benchmarks-vsl.md` replace these.
105
+
106
+ | Metric | Target | If below |
107
+ |---|---|---|
108
+ | Play rate | 50-60% | The problem is the page or the traffic source, not the copy |
109
+ | 1st-minute retention | 60-70% | **The lead is weak.** Swap the lead before anything else |
110
+ | Retention up to the pitch | 85-95% **of those still there at minute 1** | A middle block is losing people: find the drop in the chart |
111
+ | Front-end conversion | 3.8-4% typical, 8% exceptional | Offer, price or promise misaligned |
112
+
113
+ **Mind the denominator:** play rate = plays ÷ **page visits**; 1st-minute retention = reached minute 1 ÷ **plays**; retention to pitch = reached the pitch ÷ **reached minute 1**; conversion = purchases ÷ **page visits**. Read them in that order: optimizing the offer while the lead bleeds is waste.
114
+
115
+ ## Test plan
116
+
117
+ - Run **control + challengers at the same time**: the control is the best current VSL; challengers are the variations under test.
118
+ - Most variations tested are **in the lead**, the biggest lever.
119
+ - Typical split: 90% control / 10% shared among 2 or 3 challenger leads. Never throw all leads in one round: each would get too little volume to decide.
120
+ - **At least 500 to 1,000 unique views per variant** before declaring a winner. Below that it is noise.
121
+ - A winning challenger becomes the new control. Start again.
122
+
123
+ Record in `04-qa.md`: what is tested, against what, which metric decides, and the minimum volume. A test without a decision criterion set beforehand becomes an opinion argument afterwards.