| name | requests-from-meetings |
| description | Parse meeting recordings into precise feature-request documents under ./requests-from-meetings. Use when the user invokes /requests-from-meetings, asks to parse a meeting or meeting backlog, asks what was requested or shown in a meeting, or mentions meeting recordings, transcripts, or feature requests from meetings. |
Requests from meetings
We record every feature request presented in meetings with the utmost precision — so that months later, someone who wasn't there can understand exactly what was asked, by whom, while looking at what. The output lives in ./requests-from-meetings, one folder per meeting, each with a beautifully readable index.html.
Requests are records, not commitments. Nothing in these documents implies we will build what was asked. Shaping work from requests is a separate, future skill. Every document states this plainly.
The requests-from-meetings/ data folder — the meeting records, assets/ with its manifest, and stakeholders.md — is committed project content, scaffolded by the workflow's install and owned by the project. The machinery is not: the verifier and the shared stylesheet are generated by the workflow package into gitignored paths, so reading or verifying from a fresh clone means running the project's install first.
Per-user config
Where the recordings live is a fact about a machine, never about the project, so it has no committed configuration. Each user's path lives in requests-from-meetings/config.local.json — personal and gitignored, beside the data folder:
{ "meetingsFolder": "/Users/<user>/Dropbox/meeting-records" }
If the file is missing, ask the user for their meetings folder before doing anything else, then write it. Every worktree needs its own copy — the verifier reads it, and without it the quote gate cannot run. The path never becomes a committed project fact.
Invocation
- No arguments → diff the user's meetings folder against the parsed manifests (see Discovering meetings) and answer with
/goal copy the user can paste to parse everything pending. Do not start parsing. The goal copy must: list the pending meetings by date and title, instruct loading this skill for the entire doctrine, mandate one PR per meeting with lanes run in parallel, and define done as "every listed meeting parsed, merged, and linked from the rollup; a fresh no-arg /requests-from-meetings reports nothing pending." Stay under /goal's 4,000-character limit.
- With a specific meeting (or instructions) → parse that meeting following the doctrine below.
Discovering meetings
The Gemini notes define meetings. In the user's folder, a file matching <Title> - <YYYY_MM_DD> <time> - Notes by Gemini*.md is a meeting; its recordings share the same <Title> - <date> <time> prefix (... - Recording.mp4, ... - Recording 2.mp4, …), and each part's Whisper transcript sits beside its recording under the same name with a .vtt extension. Other files in the folder (spreadsheets, PDFs) are shared documents referenced across meetings.
A meeting's repo folder is requests-from-meetings/YYYY-MM-DD-title-slug/ (e.g. 2026-07-27-software-review). A meeting is parsed when that folder's meeting.json records the notes file with a matching sha256 and "status": "parsed". Everything else is pending.
The doctrine
Wide net, labeled entries
Capture three kinds of entries, each labeled with its explicitness:
- Explicit request — someone asked for something in so many words.
- Observed pain point — friction that surfaced (a workaround, a complaint, a manual step nobody would choose) without being voiced as a request.
- Current workflow to replicate — a demonstrated way of working that clearly implies what the product would need to support. When we shape solutions later, the demonstrated workflow is often more precise than the voiced ask.
Every entry is tied to timestamps on the meeting clock. Record who asked, in what context, and what was on screen. Precision beats brevity; completeness beats tidiness — when in doubt, record it.
A label has to be provable from the quotes the entry carries. Explicit request needs a quote in which someone voices the ask — not an entry title phrased as one. Current workflow to replicate needs demonstrated or described practice of today, in the indicative; a subjunctive or conditional ("if we had", "it could", "it would be" — se tivesse, poderia, ia ser in Portuguese) is someone imagining a future, and imagining is not a workflow. What passes neither test is an observed pain point, or context inside another entry.
Two transcripts
Every meeting is read through two transcripts, and each answers a question the other cannot.
Whisper carries the words. Every recording part is transcribed with whisper-cli (whisper.cpp) using ggml-large-v3, in the meeting's own language (-l takes the ISO 639-1 code), and the VTT it writes is the verbatim source for every quote from that part. The toolchain is pinned by the workflow package and checked by its doctor subcommand, so a transcript comes out the same on every machine — a missing binary or model surfaces there, not mid-parse:
ffmpeg -hide_banner -loglevel error -i "<part>.mp4" -ar 16000 -ac 1 "<part>.wav"
whisper-cli -m ~/.cache/whisper-cpp/ggml-large-v3.bin -l <language> -ovtt -of "<part>" -f "<part>.wav"
The model is pinned: ggml-large-v3.bin has to hash to 64d182b440b98d5203c4f9bd541544d84c605196c4f7b845dfa11fb23594d1e2, and a decode by anything else is not this transcript. The VTT lands beside its recording in the meetings folder as <part>.vtt, and meeting.json records it by filename and sha256 — like the recordings themselves, transcripts are never committed. A part's cue times are native to that part's clock: cue 00:01:06 is sixty-six seconds into that file, whatever the meeting timeline calls that moment.
A degenerate decode never ships. On long audio, large-v3's default decode sometimes falls into a loop and emits one cue hundreds of times, destroying everything after it — and the wreckage reads as a plausible transcript to anyone skimming the top. Decode with the defaults, then look: more than five identical cues in a row is the tell, and verify.py refuses the whole meeting when it finds one. The remedy is to re-decode that part behind voice-activity detection:
whisper-cli -m ~/.cache/whisper-cpp/ggml-large-v3.bin -l <language> -mc 0 --vad \
-vm ~/.cache/whisper-cpp/ggml-silero-v5.1.2.bin -ovtt -of "<part>" -f "<part>.wav"
The silero model is pinned too, at 29940d98d42b91fbd05ce489f3ecf7c72f0a42f027e4875919a28fb4c04ea2cf, and meeting.json records which decode shipped for each part so a reader knows what produced the words.
The Gemini notes carry the people and the clock. They are what makes a meeting a meeting, and they are the only source that says who spoke — Whisper hears words, not speakers. A quote's wording is Whisper's; its attribution comes from laying the Whisper text over the notes' speaker turns at the same moment, and verify.py holds a cited speaker to having a turn there. The notes also fix the timeline that every entry, quote, caption, and frame filename on the page is written in.
A part the notes never reached still gets read. Its Whisper transcript gives the words, but nothing gives the names, so attribution has to be argued from the conversation itself — who is driving the shared screen, who is asked and who answers, whose sentence the notes pick up when they finally start — and Reading this record says exactly what the argument rested on. A part that sits before the meeting clock has no timeline time to cite, so its quotes and its frames name the part and the time inside it: Part 1 · 00:01:06.
Language
Documents are written in English prose. Preserve verbatim quotes in the meetings' source language wherever the speaker's exact wording matters — which is most requests — followed by an English translation (a meeting already in English needs no translation line). Never paraphrase away the original ask.
Machine transcription mangles the domain's vocabulary — initialisms, product terms, loanwords from another language — into near-homophones (an initialism like CCP coming back as KCPI, an English term in a non-English meeting coming back as its phonetic spelling). Reproduce the mangling in the verbatim quote and resolve the intended term in the translation with square brackets; the Reading this record note states the convention once, so no quote has to explain itself, and names the mangles the meeting actually produced — or says plainly that it produced none worth flagging, which is an answer and not a gap.
A verbatim never welds two stretches of speech together silently. In the notes, backchannel splits a single sentence across turns and sometimes across a timestamp heading; in a Whisper transcript the join that matters is a pause, because a cue break inside unbroken speech is the decoder segmenting rather than the speaker stopping. Mark either kind of join with …, or […] when the elision is long.
The English translation renders the joined sentence whole: the verbatim carries the fidelity, the translation carries the meaning, and a trailing … in a translation marks only a sentence the speaker never finished. The Reading this record note states this.
Verify every quote mechanically
The check is the verifier the workflow package generates at .claude/skills/requests-from-meetings/scripts/verify.py — gitignored like every generated file and materialized by the workflow's sync, so a missing script means the install has not run in this checkout. It is versioned with the package together with the manifest schema it reads. Run it on the finished document, and a clean run is the ship gate:
python3 .claude/skills/requests-from-meetings/scripts/verify.py 2026-08-05-software-review # or --all
It re-hashes every transcript against meeting.json, then walks every p.verbatim, splits it on … and […], and holds each fragment to the rules below. Each quote is checked against whichever transcript covers the moment it cites: a recording part with a Whisper transcript answers for its own quotes, and everything else answers to the notes. Exit 0 is clean, 1 is failures, 2 is a setup problem (no config, a missing transcript, a transcript whose sha256 has drifted, or a Whisper decode that looped). Paste its summary line into the PR body.
A verbatim fragment lies within one speaker turn. Any join — across a backchannel (Sim., certo?), across the same speaker's own interrupted turns, across a block heading — is marked with …, or […] when the elision is long. Never silently stitch: what stands between two … has to be one person's words, in one turn, exactly as the transcript has them.
That rule is why the script builds the notes as a stream of turns and never as one flat blob. Flatten it and every stitch becomes a substring, so the check passes while the document ships a sentence nobody said in one breath — the exact failure this gate exists to stop, and one that has shipped wherever the check was flattened.
In a Whisper transcript the unit is a run of unbroken speech. Cues that follow one another with no audible gap between them are one stretch, and a fragment may cross them freely; a pause between cues is a boundary and needs its marker exactly as a turn boundary does. The script builds each part's VTT as runs for the same reason it builds the notes as turns.
The rest of what the script holds you to:
- The cite is where the quote starts. In the notes, a claimed timestamp resolves to the block whose window contains it (header time ≤ claimed < next header); the first fragment must sit in that block, and each later fragment must run forward from the one before it. In a Whisper transcript it resolves to a cue instead, and the cite has to land within a minute of it — part offsets are calibrated to a second or two, and a minute is the same forgiveness a block window gives. Off-by-one-block cites read as perfectly plausible and are caught here.
- A cite in part time names its part.
Part 1 · 00:01:06 cites a part that sits before the meeting clock. Every other cite is meeting time, and naming a part that is on the clock fails: the page keeps one clock.
- A speaker is a speaker the notes heard. A quote lifted from a Whisper transcript is credited to someone the notes have speaking within a minute of that moment. Where the notes never reached, nothing mechanical stands behind the name and the record's own account of the attribution is what carries it.
- Casing and quote characters are the transcript's. Do not sentence-case a fragment that starts mid-sentence. Normalization folds HTML entities, markdown backslash escapes (
300\., p\*\*\*\*), typographic quotes and dashes, and whitespace — on both sides, and nothing else.
- No brackets inside a verbatim except
[…] and [unclear]. A mangled term stays mangled where it was spoken and is resolved in brackets in the translation.
- Every quote carries a translation.
The script also warns — without failing the run — about multi-word source-language spans (the lang-attributed ones, lang="pt-BR" in a Portuguese series) in prose that match no single turn. Quote them properly or reword them.
Audit before shipping
verify.py proves that every quote the record ships is real. It cannot see what the record missed, what its prose claims, or what a caption asserts about pixels. A finished record gets a full-precision audit over exactly that gap, and it is not shippable until the audit's findings are fixed in the same PR. A first pass with a clean verifier, every gate green and the whole document read end to end still routinely carries findings of exactly these kinds: entries the wide net missed, an entry whose closing exchange records an outcome a later exchange had already superseded, cross-meeting citations whose targets say something else, an initialism expanded past its evidence, a figure caption its own frame contradicts.
Five passes, each on a layer no script reaches:
- Blind completeness sweep. A reader with fresh context builds its own timestamped inventory of all three entry kinds straight from the transcripts, before opening the finished record, then diffs both ways: entries the record misses, and sentences the record asserts that neither transcript carries — chronology reversed, a hedge hardened into a cause, an outcome a later exchange superseded, a label the quotes do not prove. Building the inventory first is the whole point. A reader who starts from the record only confirms it, and confirmation finds nothing.
- Sibling-claim verification. Every sentence that cites another meeting's entry is read against what that entry actually says. The anchor resolving is necessary and nowhere near sufficient: the speaker, the number, and the direction of the claim all have to match the target. Citations ship whose targets name a different speaker, a different horizon, or the opposite of the citing sentence — this pass is what catches them.
- Calibration re-derivation. Re-derive the timeline offset from the recording's pixels, independently of the note already in
meeting.json, and extract a counterfactual frame showing the rival offset failing. Arithmetic proposes and pixels decide — but a confirming frame is only evidence if the alternative was actually disproven.
- Ink re-render. Draw every figure's ink onto its own frame with PIL and read the composites as images, as Frames that ship requires. Draw
.ink-ring as a rounded rectangle — border-radius: 8px in assets/style.css is what the page renders — because an ellipse composite makes a precisely-placed ring look like it clips the corners of what it encloses, and false-flags it.
- The shipped-evidence rule. The record asserts only what a transcript, a shipped frame, or a shipped asset carries. A fact read off a frame deliberately not shipped — cropped for privacy — is not citable: resolve to what the shipped evidence supports, the way a record leaves an on-screen initialism unexpanded when the meeting never says what it stands for. A withheld frame's contents may inform a hedge in
stakeholders.md; the meeting page cites only evidence a reader can open.
Two layers of this audit are already mechanical, and verify.py runs them: the entry count agreeing across the header meta, the section heading, meeting.json, and the rollup, and every relative link and in-document anchor resolving. It cannot judge the five above — spend the audit there.
Stakeholders
requests-from-meetings/stakeholders.md describes everyone who appears in any meeting. Before finalizing a meeting document, every speaker in its notes must have an entry there. When the notes surface an unknown speaker — or an unresolved reference that matters (a nickname, an unnamed "her assistant" who owns a workflow) — ask the user about them before finalizing (during a goal, use questionnaire questions), then add what you learned to stakeholders.md. Record uncertainty honestly ("as far as we know…").
A speaker label is not proof of who spoke. Diarization gives one label per microphone, so when people walk into a participant's room mid-call, everything any of them says inherits that participant's name. Watch for the tell — the labelled speaker being referred to in the third person by whoever answers them — and, when a document is built on such notes, state the conflation and the moment it starts in the Reading this record note, keep quote attributions exactly as the notes have them, and qualify an entry's "who" only where the conversation itself gives evidence.
Parsing a meeting
1. Transcribe every part
Run every recording part through Whisper as Two transcripts sets out, check each decode for the loop tell, and record the VTTs in meeting.json. This comes first because everything downstream reads from these files — the inventory, the timeline, and every quote the document ships.
2. Transcript pass
Read both transcripts end to end, the notes for who is speaking and the VTTs for what they said. Build a timestamped inventory of every candidate entry (all three kinds), with the surrounding conversational context and the exact quotes worth preserving. The notes have ### **HH:MM:SS** blocks (~1/minute) and bold speaker names; a VTT has cue windows a second or two wide and no names at all, so walk them side by side and carry the names across.
3. Map the recording timeline
The notes' timeline runs continuously across recording parts, in filename order: … - Recording.mp4 first, then … - Recording 2.mp4, 3, … Probe each part's duration:
ffprobe -v error -show_entries format=duration -of csv=p=0 "<part>.mp4"
Cumulative durations are a hypothesis, not the answer. Never assume the notes' clock starts at Recording.mp4: a meeting whose recording was stopped and restarted in its first minutes leaves an orphan first part the notes never cover, and summing durations then puts every frame late by that part's length — consistently enough to look right and be wrong.
Anchor on the notes' own session markers instead. Gemini writes a session-ended marker at each break, in the account's locale — A sessão foi encerrada após HH:MM:SS (and A transcrição foi encerrada após …) in Portuguese, "The session ended after …" in English — and that time is the end of a recording part, to the second. Match each marker to the part whose duration lands on it, then work backwards: a part's offset is its end marker minus its duration; the notes resume a few seconds into the next part. If the durations only line up when a part is left out, that part sits outside the notes' clock — record its offset as negative and say so in meeting.json.
Anchors lie in wait, too. A session-end marker can exactly equal the wrong part's duration by coincidence, making a wrong mapping arithmetically perfect and self-consistent. Arithmetic proposes; pixels decide.
The Whisper cues give a second, independent alignment. Take a distinctive sentence the notes place at a known block, find the same words in a part's VTT, and the difference between the two times is that part's offset, to the second. Where the markers and the cues agree, the mapping is settled; where they disagree, the mapping is wrong and the pixels say which way.
A part outside the notes' clock is still part of the meeting, and its Whisper transcript covers it whether the notes did or not. Read that transcript, watch the part — sample frames across it with ffmpeg — and record what it says and shows. Never write that a part "contains no requests" without looking: a 78-second part dismissed that way can have a shared workbook on screen the whole time, and a part written off as silent can hold the only statement of the rule behind one of its meeting's headline requests.
Then confirm against pixels, twice:
- Content: extract a frame at a moment the notes make visually unambiguous ("agora vou abrir o calendário…", a URL being dictated) and read it.
- A clock on the shared screen: a Windows taskbar or macOS menu bar in the share gives wall-clock minutes. Crop it tiny and upscale (
crop=90:30:1355:815,scale=iw*6:ih*6:flags=neighbor) and read it at the first and last second of each part — that alone settles whether two parts are contiguous or overlapping.
Beware the block spacing: Gemini's headers are nominally one a minute but can run two minutes apart, and a block's dialogue spreads across the whole gap. A quote near the end of a block happened near the next header's time, so locate frames by content and treat block timestamps as the start of the window that contains a quote — say so in the document rather than implying second-level precision.
Record the final offsets in meeting.json, and give every meeting that has video a calibration note — under that exact key — naming the method and the points it was confirmed at. Some meetings have no video — the document then works from the notes alone, with no Whisper transcript to hold it, and says so.
4. Find what the screen showed
Two complementary passes:
Scene-change detection over each part (~60× realtime) lists every moment the shared content changed — tab switches, app navigation, document scrolls:
ffmpeg -hide_banner -loglevel error -i "<part>.mp4" \
-vf "select='gt(scene,0.2)',metadata=print:file=scenes.txt" -fps_mode vfr -f null -
Transcript-guided extraction: for each request-bearing passage, extract frames at its timestamps — and around deictic words (aqui, isso, esse aqui, ó, "here", "this one") pull frames within ±2–4 s, because the words alone don't say what the cursor was pointing at:
ffmpeg -hide_banner -loglevel error -ss <seconds-within-part> -i "<part>.mp4" -frames:v 1 -q:v 2 out.jpg
Read extracted frames as images to resolve what was actually shown. Discipline matters: read in small batches, skip near-duplicates, and target reading well under ~150 frames per meeting so your context stays healthy. Meeting videos are often letterboxed (screen share left, camera tiles right) — when the share region is stable, crop for legibility (e.g. -vf "crop=1440:1080:0:0"), but verify the region per meeting.
5. Frames that ship
Embed a frame in the document only when it genuinely helps understand an entry — the screen being discussed, the spreadsheet cell being pointed at. Name shipped frames by timeline position: frames/HH-MM-SS.jpg inside the meeting folder, and frames/part-N-HH-MM-SS.jpg for a frame from a part that sits before the meeting clock, the same way its quotes are cited. Crop below the browser chrome — tab strip, address bar, bookmarks bar — before shipping: the chrome carries the presenter's bookmarks and live document URLs, which have no business in the permanent record. The one exception is a frame whose address bar is the evidence (a caption pointing at a query parameter); crop that frame below the bookmarks bar instead and say in the caption why the URL is there. Annotate with the docs visual language — accent #c2410c, ~2.5px strokes, circles/boxes/arrows with small label chips — drawn as absolutely-positioned SVG/CSS overlays in the HTML, never burned into the image.
Verify the ink by rendering it, not by re-reading it. Before shipping, draw every figure's ink coordinates — rings, chips, badges — onto its own frame with a small PIL script and read the composites as images: badge n has to land on what the caption's "(n)" names, and each ring has to enclose exactly what the caption claims, nothing more. .ink-badge is centre-anchored (margin: -10px 0 0 -10px), so its left/top is the dot's centre; rings and chips are corner-anchored. A caption can be perfectly true while every pointer sits one row off, and proofreading the HTML cannot catch it — only the rendered composite can.
6. Shared documents
Files shown or referenced in meetings that live at the folder root (spreadsheets, PDFs) are copied to requests-from-meetings/assets/ with kebab-case names, recorded in assets/manifest.json (original, file, sha256, firstSeen). The manifest's schema is a contract owned and versioned by the workflow package together with the verifier that reads it — never extend or reshape it locally; its contents are project data. Copy any documents that aren't there yet; reference them from entries. Videos are never committed.
The document (index.html)
Self-contained and beautiful: links only the shared stylesheet ../assets/style.css, no external fonts/scripts/CDNs, system font stack. The stylesheet is generated by the workflow package and gitignored — committed pages load it by relative path, its class vocabulary evolves append-only so old pages keep rendering across upgrades, and a fresh clone renders the pages after the install materializes it. Structure:
-
Header — meeting title, date, duration, participants (from stakeholders.md), source files, and the standing disclaimer: records of what was asked, not commitments to build.
-
Overview — the meeting's narrative arc in a few paragraphs: what happened, what was demoed, what themes emerged.
-
Reading this record — a <section class="section"> under that exact heading, sitting between the overview and the entries. Its checklist is fixed, and every document answers all five points, including the ones whose answer is "none":
- The verbatim is machine-transcribed and mangles the domain's vocabulary — name the mangles this meeting produced, or say plainly that it produced none worth flagging, and state that a mangled term stays as spoken in the verbatim and is resolved in brackets in the translation.
- What
… and […] mark inside a verbatim.
- That block timestamps are the start of a window rather than second-precision, and whether this document interpolates any timestamps of its own.
- Whether the notes conflate speakers and from which moment — or an explicit "no conflation observed".
- For a recording with parts the notes never cover, what those parts hold — and, where the document quotes them, how each quote's speaker was established, since no speaker scaffold reaches there.
Left to invention, each document writes some of this note and drops the rest, and a reader cannot tell a missing item from a non-existent problem. Write one short paragraph per point, each opening with its claim in bold — and the checklist above is the list, not whatever an earlier document happened to answer.
-
Entries — the heart. Each entry: a slugged id, its label (explicit request / observed pain point / current workflow to replicate), a clear title, who asked, timestamps, the story in English prose, verbatim quotes with translations, annotated frames with captions, and links to shared assets and related entries (same meeting or cross-meeting). A reference to another entry is a link, always: <a href="#other-entry-id"> inside the meeting, a relative URL like ../2026-08-03-software-review/index.html#other-entry-id across meetings. Prose that says "as in the calendar entry above" without one leaves the reader to search.
Markup
Every meeting page uses these exact shapes — they are what assets/style.css styles. Study the stylesheet before diverging from them.
<div class="page">
<header class="doc-header">
<p class="eyebrow"><a href="../index.html">Requests from meetings</a> · Software Review</p>
<h1>Software Review — July 27, 2026</h1>
<dl class="meta">
<div><dt>Date</dt><dd>Monday, July 27, 2026</dd></div>
<div><dt>Duration</dt><dd>39:28</dd></div>
<div><dt>Participants</dt><dd>Ana Lima · Bruno Costa</dd></div>
<div><dt>Entries</dt><dd>12</dd></div>
<div><dt>Source</dt><dd>Software Review - 2026_07_27 10_17 PDT (4 recording parts)</dd></div>
</dl>
<p class="disclaimer"><strong>These are records, not commitments.</strong> …</p>
</header>
<section class="section">
<h2>Overview</h2>
<div class="prose"><p>…</p></div>
</section>
<section class="section">
<h2>Reading this record</h2>
<div class="prose">
<p><strong>The machine transcription mangles the domain's vocabulary.</strong> …</p>
</div>
</section>
<section class="section">
<h2>Entries <span class="section-count">· 12</span></h2>
<div class="entries">
<article class="entry" id="slugged-entry-id">
<div class="entry-head">
<span class="label label-request">Explicit request</span>
<h3><a href="#slugged-entry-id">Entry title</a></h3>
<p class="entry-meta">
<span class="who">Ana Lima</span>
<a class="timestamp" href="#slugged-entry-id">00:05:12</a>
<span>Settings → Companies, creating the second company</span>
</p>
</div>
<div class="prose"><p>The story in English.</p></div>
<blockquote class="quote">
<p class="quote-source"><span class="who">Ana Lima</span> <span class="timestamp">00:05:18</span></p>
<p class="verbatim" lang="pt-BR">“…”</p>
<p class="translation" lang="en">“…”</p>
</blockquote>
<figure class="figure">
<div class="frame">
<img src="frames/00-00-40.jpg" alt="What the frame shows." />
<div class="ink">
<svg viewBox="0 0 1441 610" aria-hidden="true">
<line x1="430" y1="196" x2="486" y2="232" />
<polygon points="496,240 476,236 484,224" />
</svg>
<div class="ink-ring" style="left: 32.8%; top: 39%; width: 5%; height: 43%"></div>
<span class="ink-chip" style="left: 20%; top: 28%">Read down this column first</span>
<span class="ink-badge" style="left: 84.5%; top: 24.5%">1</span>
</div>
</div>
<figcaption>
<span class="timestamp">00:00:40</span>
<span>Caption in English. (1) What the badge marks.</span>
<a class="full-size" href="frames/00-00-40.jpg">Open full size</a>
</figcaption>
</figure>
<ul class="assets">
<li><a href="../assets/2026-planning-calendar.xlsx"><span class="ext">XLSX</span> 2026 Planning Calendar</a></li>
</ul>
</article>
</div>
</section>
<footer class="doc-footer">…</footer>
</div>
The three label variants are label-request (explicit request), label-pain (observed pain point), and label-workflow (current workflow to replicate). The ink overlay's SVG viewBox matches the image's pixel dimensions; rings, chips, and badges are percentage-positioned so they stay aligned at every width.
A fourth label exists that this skill never writes: label-released, an <a class="label label-released"> stamped after an entry's kind label, linking the GitHub release that shipped the ask. That stamp belongs to the release skill, added at /release time when a release is judged against the unbadged entries — a meeting-parsing PR never adds one, however finished the feature looks on screen.
meeting.json
{
"title": "Software Review",
"date": "2026-07-27",
"sourcePrefix": "Software Review - 2026_07_27 10_17 PDT",
"status": "parsed",
"parsedAt": "2026-08-07",
"sources": [
{ "file": "….md", "role": "transcript", "sha256": "…" },
{ "file": "… Recording.mp4", "role": "recording", "bytes": 10670309, "durationSeconds": 80.25, "timelineOffsetSeconds": 0, "calibration": "Session-end marker matched this part's duration; confirmed on the taskbar clock at 00:00:04 and 00:01:18." },
{ "file": "… Recording.vtt", "role": "whisper", "transcribes": "… Recording.mp4", "model": "ggml-large-v3", "modelSha256": "64d182b4…3594d1e2", "language": "pt", "decode": "default", "sha256": "…" },
{ "file": "… CALENDAR.xlsx", "role": "document", "copiedTo": "assets/2026-planning-calendar.xlsx" }
],
"speakers": ["Ana Lima", "…"],
"entryCount": 12
}
The notes' sha256 is what makes the pending diff exact — and every transcript's sha256 is what verify.py re-checks before it trusts a single quote. A whisper source names the recording it transcribes, the model that produced it, and which decode shipped: default, or vad for a part whose first decode looped. Recordings are listed with size and duration, never hashed or committed, and every recording of a meeting that has video carries its calibration note.
Rollup
requests-from-meetings/index.html links every parsed meeting (date, title, entry count, one-line summary) plus stakeholders.md. Each meeting PR adds its entry. It must read well with zero meetings parsed. Rebase conflicts there resolve keep-both.
Shipping
One PR per meeting, built in an isolated worktree provisioned by the workflow's provisioning, skipping the seed — the gates need the provisioned environment a plain git worktree add does not give you, and a content lane has no use for a seeded database. Branch worktree/meeting-YYYY-MM-DD; PR title like "Record feature requests from the 2026-07-27 Software Review meeting", body written for an external reader, with the verifier's summary line in it. Gates: pnpm run lint, pnpm run tsc, pnpm run test:unit, a clean verifier run, and a clean audit with its findings fixed (see Audit before shipping). The Definition of Done's product-surface criteria do not apply — this is repository content, not a product surface, exactly the doctrine's documentation-only carve-out.
Where lessons go
Project-empirical lessons about this skill land in workflow-content/requests-from-meetings.md through a pull request on the project — never by editing this file, which is regenerated on every upgrade. A lesson that turns out to be true of every project travels as an issue on the workflow package instead.