Imported from hutuleac/YT-Stocks-Brain (
skill/yt-stocks-brain/SKILL.md). Install upstream withnpx skills add hutuleac/YT-Stocks-Brain --skill yt-stocks-brain. Copyright stays with the author.
YT Stocks Brain — YouTube Research Brief
Generate a structured, evidence-based, mobile-friendly research brief (HTML + JSON) from any YouTube video. The content of a video can be investing/market commentary, AI/tech workflows, lifestyle/product recommendations, quotes-and-culture, or (most often) a mix — the brief adapts to whichever mix of that is actually in the transcript instead of forcing every video through the same fixed table set.
When to use
- User pastes a YouTube URL and asks to "analyze", "research", "summarize", or "do the HTML for" a video.
- User wants a conviction map, stocks/companies breakdown, workflow extraction, product/supplement recommendations, or notable quotes from a talk/interview/market update/podcast.
Workflow (do this in order)
1. Fetch metadata + transcript with yt-dlp
Use yt-dlp (already installed). Auto-captions usually exist even when manual subs don't.
yt-dlp --skip-download --print "%(id)s|%(title)s|%(channel)s|%(upload_date)s|%(webpage_url)s" "URL"
yt-dlp --skip-download --write-auto-sub --sub-lang en --convert-subs srt --no-warnings "URL" -o "%(id)s.%(ext)s"
If --sub-lang en reports no subtitles, retry the full fetch — auto-captions are delivered under
automatic_captions and usually still write VID.en.srt. If captions truly fail, state the
limitation prominently at the top of the brief.
Fallback — yt-dlp missing, or HTTP 429 (rate-limited, can last hours; cookies and
player_client switches don't help): don't wait or ask, use the API package instead. Metadata
(title/channel/date) still comes from yt-dlp --print if it works, otherwise from the video page.
python3 <skill-folder>/scripts/fetch_transcript_api.py "URL_OR_VIDEO_ID" [lang] # writes VID.<lang>.srt
Then continue with Section 2 unchanged. Needs pip install youtube-transcript-api.
No usable captions at all (none exist, garbled, members-only/non-YouTube media): use the local-whisper skill (~/.claude/skills/local-whisper/SKILL.md) — it writes the same VID.<lang>.srt; continue with Section 2.
Romanian-language videos: fetch Romanian captions (--sub-lang ro) but write the brief in
English — snapshot, themes, claims, glossary, everything the search, Signals and graph read
(changed Oct 2026: Romanian briefs were invisible to English search and their claims couldn't
group with the rest). Keep the speaker's own words in Romanian: theme quote text and HOT_TAKES
take stay verbatim Romanian, without diacritics (ș→s, ț→t, ă→a, î/â→i/a). The video title
stays as published. Set META["region"] = "ro" so the brief lands on the Romania tab, and
add the romania tag only to themes about Romania itself (the leu, local airlines, Cernavoda) —
a Romanian show's Micron or Anthropic story is a global theme and gets global tags only.
2. Clean the transcript with the bundled script
python3 <skill-folder>/scripts/clean_transcript.py VIDEO_ID.en.srt --stats
Writes VIDEO_ID.txt (timestamps/index stripped, duplicate rolling captions collapsed, wrapped to
~900-char paragraphs). Read the full .txt, never the raw .srt.
3. Read for coverage, not just gist — track where every fact will land
Free-form themes have no fixed-table safety net (no guaranteed Conviction Map / Companies / Tech /
Quotes slot), so the real risk isn't missing the gist — it's silently dropping specific numbers,
analogies, and comparisons while compressing a rich transcript into 3-6 cards. Read the whole
cleaned transcript once, and as you read, register every discrete, evidence-carrying fact worth
keeping and roughly which Section 4 theme it belongs to: named numbers/metrics (not just headline
ones), named companies/products (even in passing comparisons), explanatory analogies the speaker
uses (these carry real reasoning, not just color), standalone-quotable lines, opinions the speaker
would be on the hook for (hot takes, contrarian calls, dated predictions — these feed HOT_TAKES),
named sources, dates, or forward-looking claims — and the four things the knowledge graph is
built from: an explicit stance on a named asset (owns / buying / watching / positive /
negative, plus how strongly and over what horizon — feeds per-entity stance), any call with a
number or a date attached (feeds CLAIMS), any stated tie between two named entities
(acquires, partners, supplies, invests in, competes with — feeds RELATIONS), and who said it
when speakers disagree (attribute the bullet to the speaker; a panel disagreement is signal, not
noise). Also register upcoming catalysts the speaker points at (an earnings date, a launch, a
ruling, a rate decision) — those go in CLAIMS with the date as by. For a short, single-topic video a direct read-and-place pass is
enough; past the thresholds below, write the list.
Write an explicit flat inventory list before drafting themes — one line per fact, checked off
against the drafted bullets/quote/watch/names once themes are written — whenever any of
these holds: the cleaned .txt exceeds ~50k characters (wc -c it after Section 2); the video is a
multi-hour or multi-speaker panel; it covers many distinct stories; or the user asked for
exhaustive/"deep" coverage. Don't agonize over the call — a 59-minute single-guest interview at 63k
chars still surfaced facts that a read-and-place pass had dropped. Put the inventory in the
scratchpad directory, not the project.
Either way, a fact that doesn't fit any theme goes to OTHER_NEWS or GLOSSARY — it must land
somewhere, never get dropped for being "minor." If a theme's bullet cap (below) is too tight to
hold everything that belongs there, that's a signal the thread is actually two themes, not a reason
to cut a fact. Section 7's coverage re-scan is the actual guarantee against drops — treat it as
mandatory even when you skip the written inventory.
4. Identify the video's content mix, then find THEMES (not fixed tables)
First, classify what's actually in the transcript — most videos are a mix of:
- Market/investing: positions, conviction, stocks/funds, macro calls
- Tech/AI: workflows, tools, prompts, product/model releases
- Lifestyle/recommendations: products, supplements, workouts, media, travel, habits
- Culture/quotes/geopolitics: opinions, notable quotes, non-investment news
Set META["category"] from this classification: "market" when investing/market content is
present at all (even mixed with other types — this is the default and covers most videos), or
"dev" when the video is dev/systems/knowledge/AI-workflow content with no market angle (e.g. a
career-advice or coding-practice video like "Why Most Devs Stop Improving"), or "life" when the
video is knowledge-sharing and perspective content about life, family, parenting, mindset,
relationships, creativity or business/founder wisdom with no primary investing angle (e.g. a
founders-podcast riff on habits, systems and stories). Tie-break: market if there's an
investable angle worth indexing; otherwise dev for tools/workflows/craft, life for
perspectives and personal frameworks. This one field decides
which index.html tab the brief appears under (Section 6) and which color/badge vocabulary applies
below.
Then read across the WHOLE transcript and group related facts into 3-6 self-contained themes — a theme is a real topic thread the speaker returns to (e.g. "a hedge fund unwind," "the hyperscaler capex debate," "a new harness-design practice," "a supplement stack change"), not a content-type bucket. A theme pulls together whatever applies to it: a stance/conviction if it's an investment view, a company/product mini-profile, a pull-quote, a "not settled yet" watch-flag, a reusable workflow — all in one card. This is the single most important step: it replaces the old pattern of splitting the same facts across a Conviction Map table, a Companies table, a Tech table, and a Quotes section. If a fact would appear in two theme cards, it belongs in whichever one it's most central to — mention it once, not twice.
For each theme, capture:
id(short anchor slug),color,badge,status— see the category-conditional rules below; these three fields carry different meaning depending onMETA["category"].tags— 1-3 sector/topic tags from the fixed list ingenerate.py(TAGS:ai-infra,semis,software,macro-rates,crypto,energy,space,biotech,robotics,geopolitics,policy,consumer,finance,dev-workflow,career,health,family,parenting,mindset,relationships,romania— the one regional tag, see Romanian-language videos). These aggregate across briefs in the graph, so never coin a new one per video — the generator warns on unknown tags; if none fits, pick the nearest and move on.title— a specific, concrete headline (not a category name)lead— one bold sentence giving the point before any detail. The whole line already renders bold via CSS — don't wrap it in**...**, that's redundant (harmless if you do, just pointless).bullets— 3-5 short, concrete, evidence-carrying bullets as the norm; flex to 6-7 rather than cutting a fact when a thread is genuinely dense (e.g. an earnings-call theme with many distinct guided figures) — never a dense paragraph either way (see Section 7). Emphasis for scanning: text fields across the brief (bullets,SNAPSHOT,TAKEAWAYStitles,OTHER_NEWS) support**bold**and*italic*markdown. The renderer also auto-bolds the first$-amount,%, or(TICKER)in each bullet/snapshot/news line for you — don't hand-bold those, you'll just get a redundant nested tag. Reach for manual**bold**only on a bullet that has no such figure but still has one keyword worth anchoring the eye on (a named company, a dated claim, "non-consensus", etc.) — one bold phrase per line, never more; the point is a single scannable anchor, not emphasis for its own sake.HOT_TAKESand themequotetext render markdown too but get no auto-bold — leave those as clean verbatim quotes.quote(optional) — the single best verbatim quote for this theme, or nonewatch(optional) — an explicit "this isn't settled" caveat when the speaker themselves hedges, or a conflict of interest / undisclosed position that colors this theme's claim (e.g. "he sells the compute both sides of this debate run on"). Provenance lives here, next to the claim it affects — there is no separate caveats section. Never audit vocabulary (unverified,uncorroborated) and never tell the reader to go verify; label, don't grade.names(optional) — see the category-conditional rules below; omit entirely (None) when a theme has no company/product/person worth naming, which is common and fine outsidemarket. Each entry is{"name", "blurb", "stance", "conviction", "horizon"}.stanceis one ofOWNS/BUYING-ADDING/WATCHING/POSITIVE VIEW/NEGATIVE VIEW/CASUAL MENTION/UNCERTAIN,convictionisHigh/Medium/Low/None,horizonis short text ("12-18 months","by 2030") orNone. This per-entity stance is what the ticker index and the graph read — the theme'scolor/badgedescribe the thread, not each name in it, and a bullish name can sit inside a red-flag theme. Only label what's explicitly said; a name merely mentioned getsCASUAL MENTIONwithconviction: None.
Category-conditional vocabulary (META["category"], set above):
-
category: "market"(investing/AI-news mix, stocks/funds/macro):coloris a stance signal —green=positive/bullish/confirmed-good,amber=mixed/contested/one-eye-open,gray= speculative/low-confidence,red=negative/bearish/red-flag.badge/statususe conviction language:badgee.g. "High conviction" / "Contested" / "Speculative" / "Confirmed event";statuse.g. "HOLDING — reduced but not exited", "RELEASED July 27, 2026". Per-entitystanceandconviction(fields on eachnamesentry, vocabulary above) are never inferred — label only what's explicitly said. ConvictionHigh= clear thesis + explicit action/holding + repeated emphasis;Medium= clear view + some reasoning, no confirmed action;Low= passing/ speculative. For any publicly-tradeable entity innames, write"Company (TICKER)"(e.g."Nvidia (NVDA)","RSP"for a bare-ticker ETF) — the index's cross-reference view parses this pattern into a per-ticker mention history across every brief. Several tickers sharing one bullet get comma-separated inname, e.g."Apollo, BlackRock, KKR", so each gets its own index entry.namesis the permanent cross-reference index, not a general "notable things" slot. Every entry becomes a row in the By Company/Ticker view spanning every brief in the library, forever. Companies, funds and organizations belong there — and so do investable asset classes/commodities the speaker takes a position on (crypto —"Bitcoin (BTC)","Ethereum (ETH)"— and metals/ETFs —"Gold","Silver","GLD"— are explicit, confirmed exceptions; this was a standing instruction, not a default). Countries, regions, and non-investable product/model names or technologies still do NOT belong there — put those inbullets. Anamesentry of"Australia, Chile, Mexico"creates three country rows in the ticker index;"Natrium, BWRX-300"creates two rows for reactor designs that aren't companies. Use the comma-split deliberately: only when you genuinely want each side indexed separately (a bloc like"JPMorgan (JPM), Goldman Sachs (GS)"is the intended use); otherwise join with/or a word, e.g."Natrium / BWRX-300 class".Use the plain canonical form for a company you've named before, not a decorated variant. The index groups rows by exact string match, so
"Nvidia"and"NVIDIA", or"CoreWeave"and"CoreWeave (comparison)", become two separate rows for the same company instead of one combined history. Default to the bare"Company (TICKER)"form; only append a parenthetical qualifier ("(supply chain)","(comparison)") when the distinction is actually load-bearing for that entry, and prefer folding that nuance into theblurbinstead. When unsure what form a company has used before in this library, a quick check keeps it consistent:python3 -c "import json;d=json.load(open('library.json'));print(sorted({e['display'] for b in d['briefs'] for e in b['entities'] if 'nvidia' in e['display'].lower()}))"(swap the search term). This is a forward-looking hygiene habit, not a mandate to go back and fix older entries — existing variant rows are left as-is unless the user asks for a cleanup pass. Since Oct 2026 every entity string resolves through the repo's identity registrykb/entities.json(scripts/entities.py): case, trailing qualifiers and known tickers merge automatically. A split row that still shows up is fixed by moving the stray variant into the right entity'saliasesthere — never a per-brief workaround. Keys are permanent; editname/ticker/kind, never the key.Never write a name you can only reach by guessing through a garbled caption. Auto-captions mangle proper nouns constantly (ERCOT→"Urkott", FERC→"FK", Cerebras→"Cerrus", Cagney→"Kagny", Warsh→"Worsh", KOSPI→"Cosby"). Resolving an obvious one is fine; guessing at a company you can't identify is not, because
namesand tickers are durable index data and a wrong entry is permanent damage. When you can't identify a company with confidence, describe it by spec in a bullet ("a ~10 MW micro-reactor firm building five-unit pods") and leave it out ofnames,CLAIMSandRELATIONSentirely. -
Any other
category(e.g."dev"— dev/systems/knowledge/AI-workflow content,"life"— see below, or a future category): don't force stock-conviction vocabulary onto content that isn't a stance on an asset.colormarks how settled/confidence-worthy the claim is, not bullish/bearish:green=confirmed- good/validated,amber=mixed/contested/one-eye-open,gray=speculative/opinion/unverified,red=flagged as a real problem or risk (not "this is a bearish stock call" — there's no stock).badgedescribes the character of the claim instead of a conviction level: "Recommendation" / "Structural critique" / "Skill-atrophy warning" / "Self-critique, since resolved" / "Confirmed event".namesand ticker formatting are optional and often irrelevant — use plain names with no(TICKER)suffix, or omit the field, when there's nothing to cross-reference. -
category: "life"uses the same non-market rules asdev(color = how settled, no stock vocabulary;namesoptional, plain names,(TICKER)only for a genuinely investable company a speaker discusses).badgedescribes the kind of insight: "Principle" / "Framework" / "Personal story" / "Recommendation" / "Counterintuitive take" / "Cautionary tale". Prefer thefamily,parenting,mindset,relationships,career,healthtags. Keep the speaker's own examples and analogies — in this category they are the content, not color.
MANDATORY — capture non-investment signal too, wherever it fits best (inside a theme if it's central to one, otherwise in Other Notable News): strong convictions/opinions on any topic, recommendations (products, tools, supplements, workouts, media, travel, safety), notable non-stock news (cybersecurity, energy, space, science, health, policy, culture), and any standout verbatim quotes not already used inside a theme.
MANDATORY — capture references to other sources by name: any other podcast, show, channel,
publication, article, book, or person the speakers cite as where a claim or number came from, or
name as someone they follow/trust/recommend (e.g. "I heard this on the All-In podcast," "Bloomberg
reported," "this is the Joshua Kushner framework"). If the reference is central to a theme (it's
where that theme's key fact or figure came from), fold it into that theme's bullets or quote —
don't create a parallel section for something that already has a home. If several small references
don't anchor to any one theme, group them into a single OTHER_NEWS entry (e.g. "Sources referenced
this episode: ..."). A show naming zero outside sources is common and fine — say so briefly rather
than leaving the reader to wonder if you missed something; never pad this out with vague "as
reported elsewhere" hand-waving when no specific source was named. Vague attributions ("some
people," "you often hear") are opinions to capture in bullets, not references — only capture a
named source or a named person's stated view.
EXCLUDED — sponsor content never goes in the brief. Skip paid sponsorship segments, "this video is sponsored by" reads, host-endorsed sponsor products/tools, and sponsor discount codes/links entirely — do not give them a theme, an OTHER_NEWS entry, a CLAIMS row, a RELATIONS edge, or a GLOSSARY term. If a sponsor segment interrupts an otherwise substantive discussion mid-transcript, skip only the sponsor material and continue capturing the surrounding content normally.
5. Fill in the remaining sections
SNAPSHOT— 5-8 scannable bullets, the one-paragraph read of the whole video (unchanged from before; this is a bulleted list, never one dense paragraph).TAKEAWAYS— 4-6 short, imperative, DISTINCT actions. If a takeaway just restates a theme's bullet verbatim, cut it — takeaways are the "so what do I do" layer, not a recap.tagis a short topic category (1-3 words: "Markets", "Macro", "AI ethics", "Health", "Energy", "Geopolitics", "Crypto", "Robotics", "Space", "Policy", "Careers" — coin another short category when none fit, but keep it a topic label, not an action verb like "Watch" or "Track"; the imperative verb belongs intitleitself). It renders inline, leading the takeaway text on the same line ("Markets: Track RSP and IGV first...") — never a separate line below.CLAIMS— the graph's checkable-predictions layer: one row per call that carries a number or a date,{"who", "claim", "metric", "target", "by", "condition", "entity"}.whois the speaker;claimis one plain sentence;metric/target/byhold the checkable part split out ("China native lithography" / "achieved" / "2030"; "Nvidia NeoClouds" / "50-1,000" / None) — at least one of the three is filled;conditionis the speaker's own "if" orNone;entityis the"Company (TICKER)", asset, or region the call is about, orNone. Include upcoming catalysts the speaker points at (earnings dates, launches, rulings) with the date asby. Writewhoas the speaker's full name ("David Sacks", not"Sacks"; several speakers joined with/; provenance in parens:"Polymarket (cited by Jason Calacanis)") andbyas an ISO date or month ("2026-10-30","2026-12") whenever the speaker gave one — relative spans ("12-18 months") are fine when that's all they said. Both feed per-speaker track records with real due dates inkb/brain.db. A claim that already lives in a bullet or hot take still gets a row here — this section is structure over the same material, so the no-duplicate rule does NOT apply. Numbers stated as history ("we did $400B last quarter") are bullets, not claims; a claim is something the future can prove wrong.RELATIONS— edges between named entities as stated in the video:{"from", "rel", "to", "note"}withrelfrom the fixed setacquires/invests_in/partners_with/supplies/customer_of/competes_with/owns_stake/endorses/criticizes.from/touse the same canonical"Company (TICKER)"string asnamesso the graph can join them. Only stated relationships — a comparison in passing is not an edge.noteis one short clause orNone. Empty is fine when the video names none.HOT_TAKES— 0-6 opinions the speaker personally owns and would be quoted on: hot takes, contrarian/unpopular calls, personal convictions, dismissals ("X is dead", "nobody needs Y"), and predictions carrying a number or a date. Shape:{"take": ..., "cite": "— Speaker", "why": short context}.takeis verbatim or near-verbatim, tightened only for caption noise — never paraphrased into neutral prose, because the value here is the speaker's own phrasing and the risk they took saying it. Onlytakeandciterender on the page —citesits inline right after the take, same line, not on its own line below — andwhyis kept in the data file purely as your own drafting/vetting aid (the exposure test below), never shown to the reader. The test is exposure, not volume: would a reasonable listener disagree, or is the speaker on the hook if it's wrong? A number, date, or ranking makes it a take; "AI is important" does not. Include takes on any subject, not just the investable ones. This section is a lens on material that mostly already lives in the themes, so the Section 4 no-duplicate rule does NOT apply to it — a line can be a theme'squoteand a hot take. Do not invent a separate theme bullet just to host one. If a video is pure reporting with no neck-out opinions, leave it empty and let the placeholder say so.OTHER_NEWS— only items that don't fit inside any theme. If everything folds into a theme, it's fine for this section to end up short or empty — never pad it.GLOSSARY— compact term/definition pairs, tightened to one sentence each.
Any list left empty renders an honest "nothing found" placeholder — never fabricate content to fill a section.
6. Generate outputs
generate.py itself is never edited per video — it takes a per-video data file as an
argument instead. Copy <skill-folder>/scripts/TEMPLATE.py into the working directory (any filename,
e.g. _data.py), fill in META, SNAPSHOT, THEMES, TAKEAWAYS, HOT_TAKES, CLAIMS, RELATIONS, OTHER_NEWS,
GLOSSARY, then:
python3 <skill-folder>/scripts/generate.py <data-file>.py
Outputs (channel-first naming so same-creator videos group together in Finder/ls, with the
upload date giving chronological order within each creator):
<slug-channel>_<date>_<slug-title>.html— standalone, responsive brief, written to the current working directory.<slug-channel>_<date>_<slug-title>.json— same structured data, written toresearch-data/<slug-channel>_<date>_<slug-title>/(created automatically).<slug-channel>_<date>_<slug-title>_data.py— an archived copy of the data file you passed in, written to that sameresearch-data/<slug>/folder, so the brief can be regenerated or hand-edited later without re-deriving it from the transcript.index.html— rebuilt at the working directory root every run: a single searchable page with tabs — All Briefs (chronological, every category), By Channel, By Company / Ticker, Quotes & Takes, Dev & Workflows (category == "dev"only), Life & Perspectives (category == "life"only) and Romania (region == "ro"). The ticker view parses every theme'snamesfield (current schema) orconviction_maptopic (legacy schema) into a cross-reference: click a ticker's group to see every brief that mentioned it, with date, channel, per-entity stance/conviction/horizon, and blurb — this is what turns a growing pile of briefs into an investing-thesis tool instead of just a list of pages. Runpython3 <skill-folder>/scripts/generate.py --reindexto rebuild it standalone (e.g. after manually deleting or renaming a brief).library.json— rebuilt alongsideindex.htmlat the working directory root: a flat machine-readable manifest of every brief plus its tags and extracted ticker/company entities (with stance/conviction/horizon), meant to be fed directly into an external AI/knowledge-graph tool.CLAIMSandRELATIONSlive in each per-brief.jsonunderresearch-data/.
Filename convention: lowercase, non-alphanumerics → single hyphen, diacritics stripped, date as
YYYY-MM-DD (from META["date"]). Example: jordi-visser_2026-08-09_the-ai-crash-is-over.html.
After generating, move the raw transcript and cleaned .txt into that same research-data/<slug>/
folder, and delete the data-file copy from the working directory root (the archived copy in
research-data/ is the one that persists) — the working folder root should only ever gain the
finished .html plus the refreshed index.html.
Fixing a brief after the root data file is gone: edit the archived
research-data/<slug>/<slug>_data.py in place and run generate.py against that path. It
regenerates the .html, rewrites the .json, re-archives the data file over itself, and rebuilds
index.html + library.json — no need to copy anything back to the root. Use this for every
correction pass rather than re-deriving a fresh data file from the transcript.
7. Quality check before saving
- Re-scan the full cleaned transcript against the drafted data lists: every named number, company/
product, analogy, standalone quote, and forward-looking claim you noted in Section 3 is placed in
a theme,
OTHER_NEWS, orGLOSSARY. If you wrote an explicit inventory list, check every line off; anything unchecked (or, without a written list, anything you notice on re-scan that isn't represented) gets added, not dropped for length. Do this against the inventory file itself, line by line — not against your memory of the themes. Recalling what you wrote and believing it complete is how facts get dropped. When you wrote an inventory, run the bundled checker instead of eyeballing it:
It pulls distinctive tokens (numbers-with-units, proper nouns) out of everypython3 <skill-folder>/scripts/check_coverage.py <inventory.md> <slug-substring> --ignore=SpeakerSurname- [ ]line and reports any fact with no trace in the generated brief JSON; exit code 1 means something is unplaced. Point it at the slug, never atlibrary.json— that file is only the manifest and contains no bullets, so checking against it reports nearly every fact as missing. Each flag is a candidate, not a verdict: a paraphrase can land fine and still trip it, and a clean run doesn't prove nothing was watered down. Read every flag before editing. A fact is never dropped for being "minor." If a theme's bullet cap won't hold everything that belongs there, the thread is two themes — that is not a licence to cut. Anything that fits no theme goes toOTHER_NEWSorGLOSSARY, or gets appended to the most closely related existing bullet. - Verify the extracted entities before committing — this catches
namespollution (Section 4) in one command instead of relying on memory:
Every row must be a company/fund/organization. A country, product name or concept in that list means apython3 -c "import json;d=json.load(open('library.json'));b=d['briefs'][0];print(b['html']);print([e['display'] for e in b['entities']])"namesentry needs to move intobullets. - Graph fields are complete and vocabulary-clean: every
namesentry in amarketbrief has astance; everyCLAIMSrow haswhoplus at least one ofmetric/target/by; everyRELATIONSrow uses a verb from the fixed set and canonical entity strings; every theme hastags.generate.pyprints awarning:line for any unknown tag, stance or verb — a clean run has none. - Registry warnings are resolved before committing.
new entity '<key>' ... set its kind→ openkb/entities.json, set that entry'skind(company / fund / crypto / commodity / person / country / government / org / product / group / other); if it's really an existing entity under a new spelling, move the alias onto that entity and delete the new line instead. A ticker conflict warning means a typo in the brief (fix the data file) or a real new listing (updatetickeron the entity). Commitkb/entities.jsontogether with the brief. - A correction pass that edits a claim's
whoorclaimtext re-keys it (claim_idhashes both); ifkb/claim_outcomes.jsonalready has a verdict for it, the orphan warning names the old id — move the verdict to the new id. - No fact appears in two theme cards (see the dedup rule in Section 4).
HOT_TAKESis exempt — it deliberately re-surfaces lines that also live in a theme. HOT_TAKESentries are verbatim/near-verbatim and each one would actually make someone disagree or hold the speaker to it. A bland consensus statement in there means it should be cut.- Every major claim has transcript evidence; ownership/buying intent is never inferred.
- Conflicts of interest sit in the relevant theme's
watch, not in a bullet and not dropped. - Bullets are short and concrete — no dense multi-sentence paragraphs inside a theme.
- HTML well-formed, closes
</html>. - Working folder root only gained the
.html; transcript/JSON live inresearch-data/<slug>/.
HTML/CSS design
Already implemented in generate.py (see the "HTML/CSS DESIGN" comment block near the top of the
file) — cream editorial page, color-coded theme cards with sidecards, mobile-first CSS with no
media queries. generate.py itself is never edited per video (Section 6), so this doesn't need to
be reasoned about while writing a data file — only open that comment block if you're actually
changing the template/CSS.
Weekly memo
When generate.py prints memo due: (no memo yet this ISO week and the last is 6+ days old),
write it in the same session, after the brief is committed. The user wants direction, not
precision: what is emerging, turning, contested, connecting. Never score predictions here.
python3 <skill-folder>/scripts/memo.py context > <scratchpad>/memo_ctx.mdand read all of it. This createsmemos/<ISO-week>.jsonwith the signals snapshot embedded (don't editsignals).- Fill
title,summary(2-3 sentences, the answer),insights(3: each a headline claim, 3-5 evidence bullets with the numbers from the context,evidence= brief.htmlfiles) andwatch(3-5, prioritized, each with why it's worth a closer look). Connect dots across lenses: an insight that explains why several signals move together beats a list of movers. Name contrarian setups (falling attention vs a still-rising thesis), conflicts of interest, and thin evidence (one channel, one brief) plainly. The context'snarrativesare auto-built entity clusters labeled only by member names: give the ones you cite a human name in prose ("private credit / alt managers", "memory + optics supply chain"). python3 <skill-folder>/scripts/memo.py render memos/<ISO-week>.json→memos/<week>.html, index rebuilt with the link on the Signals tab. "What moved since the last memo" is computed from the previous memo's snapshot, not written.- Commit
memos/with the messageAdd weekly memo <ISO-week>: <title>and push.
