Six-skill presentation system: ingest talks into a rhetoric vault, run interactive clarification, generate a speaker profile, create presentations that match your documented patterns, produce the deck illustrations + thumbnail visual layer, and publish talk pages to a Jekyll shownotes site. Includes a 102-entry Presentation Patterns taxonomy (91 observable, 11 unobservable go-live items) for scoring, brainstorming, and go-live preparation.
86
92%
Does it follow best practices?
Impact
86%
1.24xAverage score across 26 eval scenarios
Advisory
Suggest reviewing before use
Process steps in order. Do not skip ahead. This skill writes a
markdown file into the Jekyll shownotes site's _talks/ collection
where a custom parser plugin (_plugins/markdown_parser.rb in the
target site) extracts structured fields by pattern matching on the
body. The format is strict — small mistakes silently flatten
content or break conditional rendering.
Reference files in this skill:
references/parser-contract.md —
extraction grammar per extracted_* field;
references/template-conditionals.md —
how talk.html renders each field;
references/common-mistakes.md —
13 failure modes with the right way.
Default target: ~/Projects/shownotes (deployed at
https://speaking.jbaru.ch).
Everything the shownotes page needs is already in the talk's artifacts by the time this skill runs. Do not ask the user to re-supply anything that is derivable. Read automatically:
From outline.yaml — the talk's presentation spec. Load via
the schema script's JSON emit mode:
python3 skills/presentation-creator/scripts/outline_schema.py \
--emit-json <path-to-outline.yaml>Script contract:
outline.yaml as a single positional argumentOutline modelFAIL: ... — abort and surface the failureParse the JSON, never re-parse YAML by hand. Map fields directly:
| Shownotes field | Source in outline.yaml |
|---|---|
| Title (body H1) | talk.title |
| Filename slug | talk.slug — the only source. Never invent, derive from venue, or rephrase |
| Conference | talk.venue |
| Date | talk.delivery_date (ISO YYYY-MM-DD) |
| Speakers | talk.speakers[] — single → `"by {{ site.speaker.display_name |
| Abstract seed | talk.thesis |
From the talk directory:
resources.json (produced by extract-resources.py in Phase 6
Step 6.0) — becomes the ## Resources section. If the file is
missing OR has no approved: true items, omit the section_talks/ page — check {talk_slug}.md, then a legacy
{YYYY-MM-DD}-{talk_slug}.md. The filename that exists is
{talk_page_stem} (Step 2)Ask the user EXACTLY one question — the slides PDF embed URL
(Google Drive https://drive.google.com/file/d/.../preview form).
That's the only value not in any file — the speaker just uploaded
the deck. Don't ask about Video; URLs arrive post-recording, and
omitting the line is what fires the "Video Coming Soon" badge
(Step 5). If the user volunteers a video URL in the same turn,
capture it.
Ask follow-ups ONLY on ambiguity:
Skill(skill: "presentation-creator") to repair, then resumetalk.thesis empty → ask the speaker for a one-paragraph
abstract (Step 4 rules apply)talk.delivery_date missing → confirm whether the talk has
happened (pre-talk publish is fine; the date only feeds the body's
**Date:** line in Step 4, not the filename)_talks/ page (either {talk_slug}.md or a legacy
{YYYY-MM-DD}-{talk_slug}.md) exists AND speaker didn't flag
this as an update → ask before overwriting (Step 7)Proceed immediately to Step 2.
The talk slug {talk_slug} is outline.yaml::talk.slug — the spec
is the only source. Already kebab-case validated by outline_schema.py.
Never invent it, never derive it from the venue or title, never
rephrase.
For a NEW talk, the filename is always {talk_slug}.md (e.g.,
geecon-2026-absolutely-right.md). No date prefix. If the URL needs a
date, encode it in talk.slug, never as a filename prefix.
{talk_page_stem} is the published page's filename without .md —
the value Steps 5–9 use for every file path, thumbnail path, preview
URL, branch name, and live-URL check:
{talk_slug}.md →
{talk_page_stem} = {talk_slug}; full path
~/Projects/shownotes/_talks/{talk_slug}.md.{YYYY-MM-DD}-{talk_slug}.md → {talk_page_stem} = that existing
date-prefixed stem. Keep the filename unchanged. Never rename a
published talk.Proceed immediately to Step 3.
Minimum (and maximum) frontmatter:
---
layout: talk
---The Jekyll plugin auto-inserts this if absent; write it explicitly
for portability. Do NOT add title:, video:, slides:,
conference:, date:, description:, abstract:, or
thumbnail_url: — every one of these is either extracted from the
body, ignored by the templates, or duplicates content (see
references/common-mistakes.md
entries 1c and 5 for the failure modes).
Proceed immediately to Step 4.
Body structure is a strict sequence the parser expects. Every value
in {braces} below is derived from outline.yaml or
resources.json per Step 1 — not asked from the user (except the
slides URL, which is the one question from Step 1).
# {talk.title}
**Conference:** {talk.venue}
**Date:** {talk.delivery_date}
**Slides:** [View Slides]({slides_url_from_step_1})
**Video:** [View Video]({video_url}) # ← omit this line entirely if no video yet
A presentation at {talk.venue} in
{Month YYYY} in
{City, Country} by
{{ site.speaker.display_name | default: site.speaker.name }}
## Abstract
{talk.thesis, lightly adapted into one flowing paragraph. No
sub-headings, no lists, no code blocks. See
[references/parser-contract.md](references/parser-contract.md) for
the exact capture-and-flatten mechanic.}
## Resources
- [{title}]({url}) # one bullet per approved resources.json entry
- [{title}]({url})
### Optional Sub-Sections
Sub-sections under Resources DO render correctly — Resources
captures everything after `## Resources` until end-of-file.Authoring rules (full grammar in references/parser-contract.md):
**FieldName:** value, one per line. URLs
wrapped as [text](url) — bare URLs don't populate the
extracted_* fields. Date in ISO YYYY-MM-DD**Field:** TBD fires the wrong state
(covered in Step 5)A presentation at
(the parser anchor) and runs to the next ## headingmarkdownify, so
any sub-headings / lists / code blocks inside flatten to literal
textmarkdownify — sub-headings,
lists, and formatting all renderProceed immediately to Step 5.
talk.html's conditional rendering keys off truthiness of
extracted_slides and extracted_video. Any non-empty value flips
the badge to "Available" and the embed include tries to load the
value as a URL — so placeholder strings always produce the wrong
badge plus a broken iframe. The only correct way to express "not
yet available" is to omit the field line entirely.
Common placeholder shapes and what they break are enumerated in references/common-mistakes.md entries 1 (Video) and 1b (Slides). The fix in every case: omit the line; add it when the real URL lands.
Updating a published file when the URL lands:
_talks/{talk_page_stem}.md (read-then-edit
per Step 7's preservation rule)**Slides:** or **Video:** line in real markdown-link
form, placed inside the field block (between **Date:** and the
blank line before the A presentation at... paragraph)The badge and embed automatically fill in on the next build.
Never commit <!-- TODO --> HTML comments — inline comments after
a link line are captured by the parser's value group and pollute
extracted_* values. Tracking for incomplete work belongs in a PR
description or an issue, not the committed file.
Proceed immediately to Step 6.
Thumbnails are resolved by filename naming convention — the
site's templates compute the path from the .md filename:
/assets/images/thumbnails/{talk_page_stem}-thumbnail.pngExample (new talk, stem = slug): geecon-2026-absolutely-right.md →
assets/images/thumbnails/geecon-2026-absolutely-right-thumbnail.png.
Drop the thumbnail file (4:3 PNG; the site resizes to 400×300) at
that path. Do NOT set thumbnail_url: in frontmatter — it's
checked only for truthiness as a featured-talks signal and never
read as a URL; see
references/common-mistakes.md
entry 1c and
references/template-conditionals.md
for the three template locations that hard-code the slug-derived
path.
Decide explicitly — do not skip this step. Check whether the
convention-path file already exists in the shownotes repo
(assets/images/thumbnails/{talk_page_stem}-thumbnail.png):
Skill(skill: "illustrations") to
generate the thumbnail (this skill places files, it does not generate
images), then drop the 4:3 PNG at the convention path.onerror fallback) is the
intentional interim, not a silent skip; when the video lands and
Phase 7 runs, generate the thumbnail and drop it at the convention
path.Never fall through this step without either producing the thumbnail or explicitly recording the deferral. Then proceed to Step 7.
Compose the full file content per Steps 3 + 4 + 5 + 6 and write it
to ~/Projects/shownotes/_talks/{talk_page_stem}.md.
If a file at that path already exists, this is an UPDATE — typically the video-add case from Step 5, or a resource refresh. In the update case:
**Video:** line, append a new resource)NEVER overwrite an existing file with a fresh composition unless the user explicitly requests a full rewrite — speakers often hand-edit shownotes post-publish (typo fixes, resource additions) and a re-author wipes those edits.
Proceed immediately to Step 8.
Before pushing, validate the file parses cleanly. The subshell +
pipefail is required — without it, tail's successful exit masks
a failing jekyll build:
cd ~/Projects/shownotes
( set -o pipefail && bundle exec jekyll build 2>&1 | tail -20 ) \
|| { echo "Build failed — fix per Step 4 and re-run"; exit 1; }The || { ...; exit 1; } guard makes the build a hard gate: a
non-zero exit aborts before any of the visual-check commands run.
Only after the build is green, open the rendered page locally:
bundle exec jekyll serve --port 4000 2>&1 &
open "http://localhost:4000/talks/{talk_page_stem}/"Visually confirm: title, conference + date + correct video badge (Available vs Coming Soon), slides embed, single-paragraph abstract, resources list. If a field doesn't render, the parser didn't match — re-check Step 4 rules.
Proceed immediately to Step 9.
Pick the push flow with the content-only gate, then publish. Run
the gate from the speaker-toolkit repo root, pointed at the shownotes
repo — it enumerates every path the push would land on main
(committed-but-unpushed commits plus pending staged, unstaged, and
untracked changes) and reports whether they all touch content paths:
bash skills/shownotes-publisher/scripts/content-only-gate.sh ~/Projects/shownotesjbaruch/coding-policy: ci-safety's Content-Only Direct-Push Carve-Out.main, no upstream, empty push, or can't tell) → take the
branch + PR flow. Never direct-push when the gate does not
return 0.The allowed content prefixes are the named ALLOWED_PREFIXES at the top
of skills/shownotes-publisher/scripts/content-only-gate.sh.
Direct-push flow:
cd ~/Projects/shownotes
git add _talks/{talk_page_stem}.md [assets/images/thumbnails/{talk_page_stem}-thumbnail.png]
git commit -m "Add shownotes: {Talk Title} at {Conference}"
git push origin main
gh run watch --exit-status $(gh run list --workflow=pages-build-deployment --branch=main --limit=1 --json databaseId --jq '.[0].databaseId')
curl -fsI "{site.url}/talks/{talk_page_stem}/" | head -1 # expect: HTTP/2 200Branch + PR flow:
cd ~/Projects/shownotes
git checkout -b shownotes/{talk_page_stem}
git add _talks/{talk_page_stem}.md [assets/images/thumbnails/{talk_page_stem}-thumbnail.png]
git commit -m "Add shownotes: {Talk Title} at {Conference}"
git push -u origin shownotes/{talk_page_stem}
gh pr create --fill
gh pr checks --watch --fail-fast
# After merge, watch the Pages deployment and confirm 200:
gh run watch --exit-status $(gh run list --workflow=pages-build-deployment --branch=main --limit=1 --json databaseId --jq '.[0].databaseId')
curl -fsI "{site.url}/talks/{talk_page_stem}/" | head -1 # expect: HTTP/2 200Either flow requires the CI/deploy watch + HTTP 200 — ci-safety
confirms the publish via both the run conclusion and the 200, PR or not.
If a QR code is needed for the live URL, hand off via
Skill(skill: "presentation-creator") — the QR generation flow
lives in that skill's Phase 6 Step 6.2.
Finish here — the skill is complete.
.github
eval-resources
humor-postmortem-blind-spots
qr-bitly-slug-from-outline
qr-missing-shortener-detection
shownotes-publisher-omit-placeholder
shownotes-publisher-publish-no-date
shownotes-publisher-publish-with-date
shownotes-publisher-update-add-video
video-extraction-diagnostics
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
rules
scripts
skills
illustrations
presentation-creator
references
patterns
build
deliver
prepare
scripts
shownotes-publisher
vault-clarification
vault-ingress
vault-profile
tests