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 111-entry Presentation Patterns taxonomy (81 observable: 62 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns) for scoring, brainstorming, and go-live preparation.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Slidev, presenterm, Marp, and reveal-md author decks as markdown. Nothing in
this toolkit reads markdown as slide evidence, so a talk whose deck is a
slides.md in a git repo was analysed transcript-only — the deck was sitting
right there and no rule could see it.
slide_source: "markdown" records that such a deck exists. It is provenance,
not evidence: it stays out of USABLE_SLIDE_SOURCES, and a talk carrying it
gets the same transcript-only reading as "none". This page is how to stop
that being the end of the story.
Transcript-only analysis is not thinner, it is biased downward. Entries whose
strong_evaluable_from requires static_slides / native_deck /
delivery_video cap at moderate and take half the weight; everything else
they might have gated lands in not_evaluable. One talk re-analysed without
its slides lost nine previously-detected patterns — bookends, breadcrumbs,
context-keeper, live-demo, crawling-code, defy-defaults,
display-of-high-value, greek-chorus, hiccup-words — none of them
disproven, all of them merely ungated. A profile built from a cohort like that
understates the speaker's visual craft to the point of being misleading, and
Dimensions 8 and 13 are unmeasurable for them.
Each renderer is its own optional lane, so a presenterm vault is not reported
as degraded for three tools it will never call. Which lane a deck needs is the
lane field of its own receipt (probe it below); what that lane requires is
LANE_REQUIREMENTS in skills/vault-ingress/scripts/check-runtime.py, and the
checker names the missing commands itself when a lane is unavailable.
"{python_path}" "{speaker_toolkit_root}/skills/vault-ingress/scripts/check-runtime.py" \
--lanes "core,{lane_from_the_receipt}"Never --require-lanes a markdown-deck lane during bootstrap. Like every other
optional lane, an absent renderer degrades that one deck, not the run.
{deck_path} below is the talk's registered deck_source_path when it has one,
and the file you located by hand when it does not — registering it is the last
step of this page. A registered value that is not absolute is vault-root
relative: resolve it against {vault_root} before passing it, because the
renderer resolves a relative path from its own working directory and would
report a missing deck for a locator that is fine.
Read the deck without touching a renderer — this reports the detected flavor, the lane, and what is missing from it:
"{python_path}" "{speaker_toolkit_root}/skills/vault-ingress/scripts/render-markdown-deck.py" \
"{deck_path}" --probeThen render it into the vault's slides directory, where the existing
static_slides path already looks:
"{python_path}" "{speaker_toolkit_root}/skills/vault-ingress/scripts/render-markdown-deck.py" \
"{deck_path}" --output "{vault_root}/slides/{talk}.pdf"Both print one JSON receipt on stdout and exit 0; a deck that cannot be read,
an unavailable lane, and a failed render each exit 1 with a diagnostic on
stderr. Exit 3 is the closed failure boundary, not a verdict — see
entrypoint-failure-contracts.md. Pass --flavor when the deck carries no marker that names its tool, or
carries markers for two. The full receipt contract — every field, the detection
vocabularies, and the per-flavor invocation — is the script's, at the top of
skills/vault-ingress/scripts/render-markdown-deck.py and
skills/vault-ingress/scripts/markdown_deck.py.
Register the render as a normal PDF slide source and requeue the talk in one
repair plan (see bootstrap-and-preflight.md for
the apply-source-repairs.py dry-run/apply pair):
{"schema_version": 1, "repairs": [{
"filename": "spring-rag-jcon.md",
"reason": "Slidev deck rendered to PDF and registered as slide evidence",
"expect": {"slides_local_path": {"$missing": true},
"slide_source": "markdown",
"status": "processed_partial",
"reprocess_reason": {"$missing": true}},
"set": {"slides_local_path": "slides/spring-rag-jcon.pdf",
"slide_source": "pdf",
"status": "needs-reprocessing",
"reprocess_reason": "source_added"}}]}expect covers every field set touches, status and reprocess_reason
included — validate_plan refuses a repair that changes a field it did not
declare, so a plan missing those two is rejected before anything is written.
Use the talk's actual current status, not the one above.
The talk then binds as a normal static_slides artifact. Nothing about the
evidence path is special-cased for a deck that started as markdown.
That repair is a one-way door: slide_source becomes "pdf" because the talk
now genuinely has readable slides, and after it nothing on the record says the
deck was ever markdown or where it lives. Record the deck separately, in its own
collection, so a later render — the deck gained three slides, the speaker
reworked the demo — reads its source instead of asking whoever remembers:
{"schema_version": 1, "mutations": [{
"kind": "record_markdown_deck",
"filename": "spring-rag-jcon.md",
"expect": {"deck_source_path": {"$missing": true}},
"deck_source_path": "/repos/spring-rag/slides.md"}]}expect is the same optimistic precondition every other talk-touching mutation
carries: state what you believe is registered, and {"$missing": true} when
nothing is. A registration that moved under the plan fails the write instead of
being silently overwritten. Re-pointing a deck repo that moved names the old
path there and runs the same mutation again; a second record for one talk is
refused. It does not touch the
talk record — deliberately, because a talk's schema_version is its analysis
generation, and the transcript-only talks this whole page is about are legacy
records that can never advance to a shape gate. Field contract and accepted path
spellings: schemas-db.md.
Nothing reaches the output path until the bounded PDF probe has accepted the render. A renderer that exits 0 over a corrupt file leaves an earlier valid render exactly where it was.
slide_count comes from the rendered page count, and the page count means
what it says: the renderer is invoked so that one page is one authored slide,
never one page per build state. How each tool is invoked to get that is the
script's — see RendererSpec.argv and the module docstring in
skills/vault-ingress/scripts/render-markdown-deck.py.
source_slide_count is the cross-check, never the authority. It is what
segmenting the markdown source finds. When it disagrees with the page count,
slide_count_agrees_with_source is false and the deck uses a construct the
source reader does not model. Record slide_count from the render, and treat
the disagreement as a reason to look at the pages rather than as a number to
reconcile.
One such construct is named outright: a Slidev src: key pulls slides from
another file, so one source slide renders as however many that file holds.
source_structure.imported_files lists them and slide_count_is_a_floor goes
true, which is why a disagreement there is expected rather than alarming.
source_structure.slides[].reveal_markers is progressive-reveal evidence.
It counts the staged-reveal markers the deck's own author wrote; which literals
each tool's markers are is _REVEAL_PATTERNS in
skills/vault-ingress/scripts/markdown_deck.py. A slide with markers declares
ordered cumulative content, which is exactly what progressive-reveal asks
for. Two things it is not:
crawling-code or repetition padding. Build states of one
authored slide are one slide, and the one-page-per-slide export never split
them into separate pages to be miscounted.When reveal_markers_are_a_floor is true, floor_causes names the headmatter
switch that stages content the source never marks. The count is a lower bound in
that case — a slide reporting zero may still build.
CI installs all four renderers at pinned versions and renders a three-slide deck through each on every run, so the one-page-per-slide claim is checked there rather than asserted. What CI cannot check is YOUR deck. To do that:
missing_commands.render-markdown-deck.py <deck> --probe — expect the right flavor and
lane_available: true.render-markdown-deck.py <deck> --output /tmp/deck.pdf — expect exit 0.pdftoppm -png -r 100 /tmp/deck.pdf /tmp/slide and open the pages. Expect
one image per authored slide, each showing the slide's final build state.
Several near-identical images differing by one added line means a per-click
export got through, and the renderer's invocation needs fixing rather than
the count explaining away.page_count against the deck's own slide count in its editor.presenterm needs a terminal. Exporting it by hand from a script or a CI
step fails with Inappropriate ioctl for device (os error 25), and from an
unsized pty with render: screen is too small. Both are handled — see
_run_with_pty in skills/vault-ingress/scripts/render-markdown-deck.py.
Recognize the messages if you export by hand; the renderer here does not hit
them. presenterm also shells out to weasyprint, which is why its lane wants
both.
Browser-backed renderers need a browser. Slidev's PDF export needs
playwright-chromium; Marp and reveal-md drive a Chrome/Chromium or Firefox.
Their absence surfaces as a renderer failure, not a missing command, so the
diagnostic carries the renderer's own output.
.tessl-plugin
rules
skills
illustrations
presentation-creator
references
patterns
build
deliver
prepare
scripts
shownotes-publisher
vault-clarification
vault-ingress
references
scripts
vault-profile