Docs/Skills and code/skill winning-video-teardown
.claude/skills/winning-video-teardown/scripts/aggregate.js
What the file says about itself
Counts what the teardowns have in common. Writes aggregate.json and aggregate.md into the run directory. node aggregate.js --run <runDir>
#!/usr/bin/env node
// Counts what the teardowns have in common. Writes aggregate.json and aggregate.md into the run directory.
// node aggregate.js --run <runDir>
const fs = require('fs');
const path = require('path');
const run = path.resolve(process.argv[process.argv.indexOf('--run') + 1] || '');
if (!fs.existsSync(path.join(run, 'manifest.json'))) { console.error('usage: node aggregate.js --run <runDir>'); process.exit(1); }
const manifest = JSON.parse(fs.readFileSync(path.join(run, 'manifest.json'), 'utf8'));
const taxonomy = fs.readFileSync(path.join(__dirname, '..', 'references', 'taxonomy.md'), 'utf8');
const names = Object.fromEntries([...taxonomy.matchAll(/\*\*([SPT]-[A-Z0-9-]+)\*\* - ([^.]+)\./g)].map((m) => [m[1], m[2].trim()]));
const toSec = (t) => { if (t == null) return null; const p = String(t).replace(/[\[\]]/g, '').split(':').map(Number); return p.some(Number.isNaN) ? null : p.reduce((s, v) => s * 60 + v, 0); };
const items = [];
for (const c of manifest.cases) {
const f = c.output;
if (!fs.existsSync(f)) { console.warn(`missing analysis for ${c.cid}`); continue; }
items.push({ c, a: JSON.parse(fs.readFileSync(f, 'utf8')) });
}
const tally = (pick) => {
const t = {};
for (const { c, a } of items) for (const m of pick(a)) {
const e = (t[m.id] ??= { videos: new Set(), groups: {}, examples: [] });
if (!e.videos.has(c.cid)) { e.videos.add(c.cid); e.groups[c.group] = (e.groups[c.group] || 0) + 1; }
e.examples.push({ cid: c.cid, title: c.title, how: m.how || m.how_it_works || '', effect: m.effect_on_viewer || '', evidence: (m.evidence || [])[0] || null });
}
return Object.entries(t).map(([id, e]) => ({ id, name: names[id] || id, n: e.videos.size, groups: e.groups, videos: [...e.videos], examples: e.examples })).sort((x, y) => y.n - x.n);
};
const scriptMechs = tally((a) => [...(a.script?.mechanisms || []), ...(a.script?.hook?.moves || [])]);
const titleMechs = tally((a) => a.packaging?.title?.mechanisms || []);
const thumbMechs = tally((a) => (a.packaging?.thumbnail?.seen ? a.packaging.thumbnail.mechanisms || [] : []));
const loopStats = items.map(({ c, a }) => {
const loops = a.script?.loops || [];
const opened = loops.map((l) => toSec(l.opened_at)).filter((x) => x != null);
return { cid: c.cid, n: loops.length, closed: loops.filter((l) => l.closed_at && l.closed_at !== 'null').length, first_open_sec: opened.length ? Math.min(...opened) : null };
});
const mean = (xs) => (xs.length ? xs.reduce((s, v) => s + v, 0) / xs.length : null);
const delivery = { delivered: {}, clickbait: {} };
for (const { a } of items) {
const d = a.packaging?.promise_delivery || {};
delivery.delivered[d.delivered] = (delivery.delivered[d.delivered] || 0) + 1;
delivery.clickbait[d.clickbait_risk] = (delivery.clickbait[d.clickbait_risk] || 0) + 1;
}
const promiseTimes = items.map(({ a }) => a.script?.hook?.time_to_promise_sec).filter((x) => typeof x === 'number');
const newMechs = items.flatMap(({ c, a }) => (a.new_mechanisms || []).map((m) => ({ cid: c.cid, ...m })));
// ---- stakes / flow / forced continuation (fields added 2026-10-24; only videos analysed under the new schema count) ----
const LINK_KEYS = ['THEREFORE', 'BUT', 'AND_THEN', 'NEW_TOPIC'];
const junctionStats = items.map(({ c, a }) => {
const js = a.script?.junctions || [];
const t = Object.fromEntries(LINK_KEYS.map((k) => [k, 0]));
for (const j of js) if (j.link in t) t[j.link]++;
return { cid: c.cid, n: js.length, ...t };
}).filter((j) => j.n);
const junctions = junctionStats.length ? {
videos: junctionStats.length,
totals: Object.fromEntries(LINK_KEYS.map((k) => [k, junctionStats.reduce((s, j) => s + j[k], 0)])),
mean_share: Object.fromEntries(LINK_KEYS.map((k) => [k, mean(junctionStats.map((j) => j[k] / j.n))])),
videos_with_no_and_then: junctionStats.filter((j) => j.AND_THEN === 0).length,
per_video: junctionStats,
} : null;
const withHook = items.filter(({ a }) => typeof a.script?.hook?.first_payoff_sec === 'number');
const hookNew = withHook.length ? {
videos: withHook.length,
first_payoff_sec: { mean: mean(withHook.map(({ a }) => a.script.hook.first_payoff_sec)), max: Math.max(...withHook.map(({ a }) => a.script.hook.first_payoff_sec)) },
figures_in_first_60s_mean: mean(withHook.map(({ a }) => a.script.hook.figures_in_first_60s ?? 0)),
destination_hint_present: withHook.filter(({ a }) => a.script.hook.destination_hint?.present).length,
videos_with_extra_promise: withHook.filter(({ a }) => (a.script.hook.extra_promises || []).length).length,
extra_promises_paid_share: (() => { const all = withHook.flatMap(({ a }) => a.script.hook.extra_promises || []); return all.length ? all.filter((p) => p.paid_at && p.paid_at !== 'never').length / all.length : null; })(),
} : null;
const withNum = items.filter(({ a }) => Array.isArray(a.script?.numbers?.hero_figures));
const anchoring = {};
for (const { a } of withNum) for (const h of a.script.numbers.hero_figures) anchoring[h.anchored_by] = (anchoring[h.anchored_by] || 0) + 1;
const numbersNew = withNum.length ? {
videos: withNum.length, anchored_by: anchoring,
naked_example_videos: withNum.filter(({ a }) => (a.script.numbers.naked_examples || []).length).length,
densest_30s_mean: mean(withNum.map(({ a }) => a.script.numbers.densest_30s?.figures).filter((x) => typeof x === 'number')),
} : null;
const agg = {
n: items.length,
junctions, hook_escalation: hookNew, numbers: numbersNew,
script_mechanisms: scriptMechs.map(({ examples, ...r }) => r),
title_mechanisms: titleMechs.map(({ examples, ...r }) => r),
thumbnail_mechanisms: thumbMechs.map(({ examples, ...r }) => r),
loops: { mean_opened: mean(loopStats.map((l) => l.n)), mean_closed_share: mean(loopStats.filter((l) => l.n).map((l) => l.closed / l.n)), mean_first_open_sec: mean(loopStats.map((l) => l.first_open_sec).filter((x) => x != null)), per_video: loopStats },
promise_delivery: delivery,
time_to_promise_sec: { mean: mean(promiseTimes), min: Math.min(...promiseTimes), max: Math.max(...promiseTimes) },
new_mechanisms: newMechs,
};
fs.writeFileSync(path.join(run, 'aggregate.json'), JSON.stringify(agg, null, 2), 'utf8');
const groups = [...new Set(manifest.cases.map((c) => c.group))];
const table = (rows) => ['| Mechanism | Videos | ' + groups.join(' | ') + ' | Which |', '|---|---|' + groups.map(() => '---|').join('') + '---|',
...rows.map((r) => `| **${r.id}** ${r.name} | ${r.n}/${items.length} | ${groups.map((g) => r.groups[g] || 0).join(' | ')} | ${r.videos.join(', ')} |`)].join('\n');
const L = [`# Aggregate across ${items.length} teardowns`, '',
`Counts are the number of videos in which an analyst found the mechanism and quoted the moment. They count presence, not quality, and ${items.length} videos is a small sample.`, '',
'## Script mechanisms', '', table(scriptMechs), '', '## Title / packaging mechanisms', '', table(titleMechs), '', '## Thumbnail mechanisms', '', thumbMechs.length ? table(thumbMechs) : '(no thumbnails analysed)', '',
'## Loops and promise', '',
`- Open loops per video: mean ${agg.loops.mean_opened?.toFixed(1)}; share closed by the end: ${((agg.loops.mean_closed_share ?? 0) * 100).toFixed(0)}%; first loop opens at about ${agg.loops.mean_first_open_sec?.toFixed(0)} s on average.`,
`- Time to the promise: mean ${agg.time_to_promise_sec.mean?.toFixed(0)} s (range ${agg.time_to_promise_sec.min} to ${agg.time_to_promise_sec.max}).`,
`- Promise delivered: ${JSON.stringify(delivery.delivered)}. Clickbait risk: ${JSON.stringify(delivery.clickbait)}.`, '',
'## Flow, opening and figures (analysed under the 2026-10-24 schema)', '',
...(junctions ? [
`- Junctions (${junctions.videos} videos): ${LINK_KEYS.map((k) => `${k} ${junctions.totals[k]} (${((junctions.mean_share[k] ?? 0) * 100).toFixed(0)}% of a video on average)`).join(', ')}. Videos with no AND_THEN junction at all: ${junctions.videos_with_no_and_then}/${junctions.videos}.`,
] : ['- Junctions: no video was analysed under the new schema.']),
...(hookNew ? [
`- Opening (${hookNew.videos} videos): first concrete payoff at ${hookNew.first_payoff_sec.mean?.toFixed(0)} s on average (latest ${hookNew.first_payoff_sec.max} s); ${hookNew.figures_in_first_60s_mean?.toFixed(1)} figures in the first 60 s on average; destination hint present in ${hookNew.destination_hint_present}; an extra promise seeded in ${hookNew.videos_with_extra_promise}${hookNew.extra_promises_paid_share == null ? '' : `, ${(hookNew.extra_promises_paid_share * 100).toFixed(0)}% of them paid`}.`,
] : []),
...(numbersNew ? [
`- Hero figures (${numbersNew.videos} videos) by how they were framed: ${JSON.stringify(numbersNew.anchored_by)}. Videos with at least one naked figure: ${numbersNew.naked_example_videos}/${numbersNew.videos}. Densest 30 s: ${numbersNew.densest_30s_mean?.toFixed(1)} figures on average.`,
] : []), '',
'## Mechanisms the analysts proposed that the taxonomy lacks', '',
...(newMechs.length ? newMechs.map((m) => `- **${m.name}** (${m.cid}): ${m.definition}`) : ['(none)']), ''];
fs.writeFileSync(path.join(run, 'aggregate.md'), L.join('\n'), 'utf8');
console.log(`Wrote aggregate.md and aggregate.json (${items.length} analyses)`);
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.