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
Follow-up to the QR shortlink work shipped via #79, which enforced the slug-only back-half for newly-created links but left two gaps.
publishing_process.qr_code.{shortener}_domain key
STOPS so the agent asks the user and saves the answer — the domain, or null
for "no custom domain" — so a configured custom domain is never silently
skipped. Absent = never asked; null = decided (default domain), never
re-asked. The MCP path makes the same check.bitly_domain knob in the profile schema (the code and the
clarification flow already used it). rules/qr-generation-rules.md §2 (the
custom domain must be used when configured) and new §7 (the three-state
decision); phase6-publishing and the clarification prompts save an explicit
null.--build previously passed each build-NN description to the image editor
verbatim, auto-appending only safety clauses #1/#2; the mandatory preservation
list (component #3 of Edit Prompt Safety) was never applied, so a step that
erases a dense region left the element in place and the chain emitted visually
identical intermediate stages. The build flow now validates that every erase
step carries an explicit Keep clause and skips the slide with a stderr error
and a non-zero exit when one is missing — instead of silently producing a broken
chain. Build step descriptions must be authored as erase instructions with
Keep clauses (see skills/illustrations/references/builds.md).
narrative.md used to print the full talk.thesis (in practice 3–4 elaborated
paragraphs) and then the chapter argument_beats as prose with *[slide N]*
markers. The two sections stated the same argument at different granularities, so
the breakdown read as the thesis chopped into slide-tagged chunks — a reader saw
the whole argument twice. The narrative is also the only artifact that gives "the
idea + what's on each slide" in plain prose: slides.md is technical generation
input and script.md is the spoken words.
talk.tldr field on the outline schema: a short distillation of
thesis (a couple of paragraphs or a bulleted list), authored by the agent.
narrative.md renders it verbatim under ## TL;DR and never reprints the
elaborated thesis.narrative.md (slides authored) is now a one-line-per-slide walk grouped
by chapter — **N. Title** — synopsis, 1:1 with slides[], with live-demo
interludes inlined at their anchor. The per-slide synopsis prefers
text_overlay, falling back to the slide's visual.narrative.md (Phases 1–2, no slides yet) keeps the chapter +
argument-beat scaffold so the author still reviews the arc before slides exist.tldr field and the partial-vs-full
rendering split.narrative.md (the partial narrative scaffold) can now be generated and
reviewed before any slide exists. Previously
extract-narrative.py called load_outline(), which runs the full Outline
schema — slides[] (min 1), the big_idea singleton, paired callbacks, and
slide-budget math — so the human-readable narrative could not appear until Phase 3,
after slide content development had already begun. The narrative itself is fully
authored by the end of Phase 2, so the author had no readable artifact to approve
at the point the argument was actually being shaped.
PartialOutline model + load_outline_partial() in outline_schema.py
validate talk (+ optional chapters) without the slide-dependent
cross-validators. The full Outline stays the Phase 3+ source-of-truth contract.extract-narrative.py --partial renders from the partial view and emits a
"narrative arc not yet authored" note when chapters are absent.On a deck adapted (trimmed) from another talk, the QR step added a second QR instead of replacing the inherited one, and only targeted the configured slide — leaving stale QRs on earlier slides (e.g. an early shownotes slide). Now every QR-bearing slide is detected and its QR replaced in place.
generate-qr.py: QRs are detected by CONTENT, not size — find_qr_rects
flags a square picture that is both ~2-color and roughly balanced between those
colors, so it catches an inherited QR at any size (the same QR appeared at 1.8"
and 2.8" in the repro deck) while excluding colored diagrams and mostly-one-color
text screenshots. resolve_target_slide_indices targets every QR-bearing slide
in addition to the configured placement.RunDeckOps.bas InsertQR: the macro can't run image libraries, so detection
stays in Python; it now receives each slide's existing-QR geometry and just
removes those exact shapes and places the QR there (same position/size, cleaning
up duplicates). New placements still go bottom-right.preferred_short_path override (removed
from the profile schema). If bit.ly can't set the slug back-half, the create now
fails (degrading to the raw URL) rather than silently keeping a random hash.
Documented in rules/qr-generation-rules.md.qrcode dep) is
won't-fix: the dependency can't be dropped (rebrandly / none / --png-only
paths render locally), and the one-call QR-codes endpoint abandons the managed
bitlink model (custom domain, PATCH-able target, tracking).InsertQR change; untestable in Linux CI by
design. The QR-detection, slide-targeting, and back-half logic IS unit-tested.Step 9 runs skills/shownotes-publisher/scripts/content-only-gate.sh against the
shownotes repo before publishing. When every pending change touches only the
declared content globs, the skill direct-pushes to main; any out-of-glob path,
or an indeterminate state, falls back to branch + PR. This is the Form B
client-side gate that jbaruch/coding-policy: ci-safety's Content-Only
Direct-Push Carve-Out permits where server-side allowlist enforcement is not
expressible on a github.com personal repo (coding-policy#119, shipped in
coding-policy 0.3.52). The carve-out's precondition 1 is satisfied by a new
authority-of-record steering rule, rules/shownotes-content-publish.md, naming
the covered globs, the gate script, and the review the direct-push skips. Fixes #65.
Completes the date-less-slug convention. #66 made the publisher consume
talk.slug verbatim (date-less filename and URL); this drops the date prefix
from how slugs are composed, so the QR back-half and the Phase 1 slug match the
published page instead of pointing at a stale YYYY-MM-DD-prefixed back-half.
rules/qr-generation-rules.md §4: the QR back-half IS talk.slug, composed in
Phase 1 (per the speaker's slug_convention.template) and used VERBATIM — no
invent / rephrase / re-derive / date-prefix. Replaces the old
{YYYY-MM-DD}-{conference-slug}-{talk-short-name} format and removes the
self-contradictory derive-from-delivery-date guidance. §2 example date-less.rules/interaction-rules.md and
skills/presentation-creator/references/phase1-intent.md: the Phase 1
slug-confirmation examples are now date-less (jcon26-robocoders).qr-bitly-slug-from-outline,
qr-missing-shortener-detection): fixtures + criteria updated to a date-less
slug, in a synthetic namespace (froconf26-cache-stampedes) distinct from the
devnexus/robocoders examples used in skill/rule context (no fixture/example
bleeding).generate-qr.py needed no change — it already uses the passed --talk-slug
verbatim as the custom back-half.url.template date variables (URL assembly, configurable
per deployed site — tracked in #17), and legacy date-prefixed filenames already
published (the publisher's never-rename guard) or ingested into the vault.talk.slug from outline.yaml is now the single source of truth for a new
talk's _talks/ filename and live URL: the filename is always {talk_slug}.md,
never {YYYY-MM-DD}-{talk_slug}.md. The old delivery_date-conditional branch
overrode the speaker's chosen slug with a date-prefixed name, so the published
URL diverged from the slides + QR (which point at the bare slug) — it had to be
renamed by hand and the Bitly QR repointed. The downstream {filename_stem}
indirection is replaced by {talk_page_stem} — {talk_slug} for new talks, the
existing date-prefixed stem when updating a legacy page — so the
never-rename-a-published-file guard holds without duplicating legacy talks.
Fixes #66.
Retires the last python-pptx + MCP-PPT-server deck-writing path. Slide structure
was created by stripping the template with strip-template.py (python-pptx) and
then walking the deck through the MCP PPT server (add_slide /
populate_placeholder / add_bullet_points / manage_image / manage_text /
add_shape / optimize_slide_text). Both are gone — BuildDeck creates the
whole deck in the real PowerPoint app, so the engine that ships valid,
Keynote-openable .pptx is now the sole writer for creation as well as edits.
Completes #57: real PowerPoint is the sole .pptx engine.
BuildDeck (in RunDeckOps.bas) — opens a uniquely-named template copy,
deletes the template's demo slides (subsumes strip-template.py), and executes
a flat op sequence: SLIDE / TITLE / SUBTITLE / BODY / BULLET / TEXT
/ IMAGE / SHAPE / BG / FOOTER / OPTIMIZE / TABLE / CELL / CHART
/ CAT / SERIES — full parity with the retired MCP surface, in one module
(VBA has no package manager; the macros share private helpers). When a layout
lacks the requested title/subtitle/body placeholder, BuildDeck preserves the
op's content in a fallback text box rather than dropping it silently.build-deck.sh / build-deck.applescript — wrapper + driver. The
AppleScript reads the ops file as UTF-8 and passes it as one Unicode arg (no
VBA-side decoding); the wrapper validates first, stages locally, then moves the
output into place (sandboxed PowerPoint can't write to a Google Drive folder).validate-deckops.py — deterministic, unit-tested
(tests/test_validate_deckops.py) op-sequence validator (UTF-8): op vocabulary,
arity, int/float fields, BG 0–255, non-negative layout index, and state rules
(ops need a prior SLIDE; CELL needs a TABLE; CAT/SERIES need a CHART;
SERIES needs ≥1 value; a CHART needs ≥1 SERIES so it never ships
PowerPoint's default sample data). BuildDeck raises a clear error on an
out-of-range layout index rather than silently remapping it. The
PowerPoint-driving layer stays manually validated.references/deckops-spec.md — the op-sequence spec (delimiter, fields,
state rules, enum values, build-then-assemble for fragments).strip-template.py and _pptx_repair.py (and test_strip_template.py
strip_template / pptx_repair conftest fixtures) — _pptx_repair.py's
only consumer was strip-template.py.SKILL.md Step 5 and phase5-slides.md from the MCP walk to
emit-ops → validate-deckops.py → build-deck.sh; the MCP tool quick-reference
table is now a deck-op quick-reference. slide-generation-rules.md reconciled to
BuildDeck (not python-pptx, not MCP); the stale _pptx_repair.py / generate-qr.py
Keynote-carve-out example and the obsolete python-pptx code snippets are dropped.Retires generate-qr.py's python-pptx deck write (insert_qr_on_slides +
_remove_existing_qr + prs.save) for an InsertQR VBA macro. generate-qr.py
keeps everything else — URL/shortener resolve, per-slide background-color match
(read-only), target-slide finding, and QR PNG generation — and calls
insert-qr.sh for the write.
InsertQR (in RunDeckOps.bas) + insert-qr.applescript / insert-qr.sh
— places the QR bottom-right (2.0in, 0.3in margin) on the given 1-based slides,
removing any existing corner QR first (idempotent re-runs).generate-qr.py threads the deck through uniquely-named intermediates (one
InsertQR pass per color variant) and moves the result back; the python-pptx
Inches/Emu/RGBColor imports and the QR-insert test are dropped.generate-qr.py
stays cross-platform). Completes #57's deck-writer retirement. Untestable in
Linux CI by design — validate by re-opening in PowerPoint and Keynote.Retires insert-placeholder-slides.py (python-pptx) for a MakePlaceholderSlide
VBA macro driven through the real PowerPoint app.
MakePlaceholderSlide (in RunDeckOps.bas) + make-placeholder-slide.applescript
/ make-placeholder-slide.sh — builds a loud yellow [PLACEHOLDER] slide (title
auto-prefixed, optional subtitle) as a 1-slide deck sized to the base deck.run-deck-ops.sh order string: Mac VBA's
Slide.MoveTo raises E_INVALIDARG, so placeholders are built then assembled at
their target slots via InsertFromFile, rather than inserted-and-moved..pptx writer). macOS + PowerPoint
only; untestable in Linux CI by design — validate by re-opening in PowerPoint
and Keynote.Retires inject-speaker-notes.py (python-pptx) in favor of a SetSpeakerNotes
VBA macro driven through the real PowerPoint app. PowerPoint serializes valid
notes OOXML — including the <p:notesMasterIdLst> element python-pptx omitted —
so the Keynote-compatibility patch the python path carried is no longer needed
(retiring the cause of the breakage, not a safety net).
SetSpeakerNotes (in RunDeckOps.bas) + inject-notes.applescript /
inject-notes.sh — sets per-slide notes via PowerPoint, writes a COPY.notes-to-packed.py — deterministic JSON→wire-format packer, unit-tested
(tests/test_notes_to_packed.py); the VBA layer stays manually validated.phase5-slides.md rewired: notes inject via inject-notes.sh after
the illustrations apply pass and before the final apply-backgrounds.sh write..pptx writer). macOS + PowerPoint
only; untestable in Linux CI by design — validate by re-opening in PowerPoint
and Keynote.Step 6 (Thumbnail) was opt-out: it stated the page "renders fine without one"
(the onerror placeholder fallback), framed production as a vague conditional
hand-off to the illustrations skill, and ended "Proceed immediately to Step 7"
with no gate — so agents always skipped it and the talk card fell back to the
placeholder SVG. Step 6 is now an explicit decision: check the convention-path
file (assets/images/thumbnails/{filename_stem}-thumbnail.png); if absent,
either produce it via Skill(illustrations) when a source image is available,
or explicitly record it as deferred to Phase 7 (pre-talk publish with no
slides/video). Never a silent fall-through. Fixes #58.
Adds a non-corrupting way to make structural edits (delete / reorder /
cross-deck import) to an existing .pptx, driven by the real PowerPoint app
instead of python-pptx, and makes it the SOLE structural-edit path. Prompted by
a concrete failure: trimming a 128-slide, 51 MB illustrated deck with
python-pptx / clipboard paste flattened every slide whose full-bleed art is a
per-slide background fill — the output dropped to 6.2 MB with all backgrounds
gone (picture shapes survived, per-slide <p:bg> fills did not). The
InsertFromFile path recovered the same cut to 24 MB with backgrounds intact.
delete-slides.py / reorder-slides.py (and their tests +
conftest fixtures) — python-pptx slide-delete / reorder strips per-slide
background fills, so it is no longer offered for any deck. All structural
edits route through RunDeckOps. _pptx_repair.py stays (used by
strip-template.py). phase5-slides.md, SKILL.md, and the README script
tree updated to match. Tracked in #57.rules/deck-editing-rules.md) — drive real PowerPoint
for all structural edits; documents the Mac PowerPoint VBA landmines and how
each is handled.RunDeckOps.bas — reusable VBA macro that rebuilds a deck via
Slides.InsertFromFile (keep-source-formatting Reuse Slides) in a target
order, with cross-deck import, global text replace, and a COPY-only save.
Guards against the filename-collision trap and self-cleans on failure.run-deck-ops.applescript + run-deck-ops.sh — driver and wrapper; the
wrapper stages locally then moves into place (sandboxed PowerPoint can't
create files in a Google Drive File-Provider folder).MakeBgImageSlide (+ make-bg-slide.applescript / make-bg-slide.sh) —
turn a generated illustration into a slide whose image is the BACKGROUND FILL
(so the layout's halftone-dot overlay covers it, matching the other comic
slides) by cloning a template slide, swapping its background, and retitling —
a top-pasted picture would sit above the overlay. Produces a 1-slide deck to
import via run-deck-ops.sh.ApplyBackgrounds (+ apply-backgrounds.applescript / apply-backgrounds.sh) —
the creation-time counterpart: set FULL-slide illustration backgrounds in bulk
via Slide.Background.Fill.UserPicture, run as the final write of the build.
apply-illustrations-to-deck.py no longer inserts FULL-slide picture shapes —
it records each FULL slide in a backgrounds manifest (--backgrounds-out) and
applies only scrim + title; IMG+TXT keeps its left-column picture shape. Begins
retiring python-pptx as a deck writer for creation (Phase B of #57). Phase 5
reorders so the VBA background pass runs after speaker-note injection.rules/deck-editing-rules.md gains alwaysApply
frontmatter and sheds rationale prose; references/deck-editing-setup.md drops
the pause-and-wait flow for continue-immediately; the wrappers emit actionable
validation errors; and the deterministic manifest→spec step is extracted to a
unit-tested backgrounds-manifest-to-spec.py (the VBA core stays CI-untestable
by design).tile.json steering updated..pptx engine) is tracked in #57 with a phased plan.Reworked the Phase 2 illustration-strategy flow and the model roster behind it,
prompted by two reported failures: the SKILL.md Step 2 model-freshness check
effectively never ran (prose-only with a "proceed silently if everything is
represented" escape hatch, so an agent left no trace and skipped it), and a
refresh asked to update the model list dropped the nano-banana-* entries —
because "nano-banana" is Google's codename for the Gemini image line (Nano
Banana Pro = Gemini 3 Pro Image), and a bare string list carries nothing tying
the codename to the canonical id.
skills/illustrations/scripts/model_registry.py) — the
bare COMPARE_MODELS list became a structured registry: canonical id, vendor
family, aliases, and per-model cost/speed/quality tiers + edit support. The
redundant nano-banana-pro-preview entry folded into
gemini-3-pro-image-preview as an alias. resolve_model_id() maps any baked
codename to the canonical API id before dispatch. COMPARE_MODELS is now
derived from the registry for backward compatibility.model_registry.py --check-freshness emits
last_reviewed / age_days / stale / roster JSON from a date heuristic
(REGISTRY_LAST_REVIEWED + 90-day max age). SKILL.md Step 2 runs it first and
reports the verdict in one line — no silent skip. WebSearch + registry
reconciliation fires only when stale; for an existing outline the agent also
checks the baked model against the roster.model_registry.py --shortlist <priorities> before any render.
build-editability hard-excludes Imagen (no edit endpoint); cost/speed/quality
are soft rankings.generate-illustrations.py --style-explore reads a
candidates.json (styles × shortlist × formats; schema in
references/style-explore-candidates-schema.md) and renders into a structured
style-explore/<style>/<format>/<model>.<ext> tree with an index.md contact
sheet, so the speaker picks style and model together from rendered output.shortlist_models(extra_models=...) / --shortlist --add '<json>' without a
table edit. Persistent additions land in the registry through the Step 2
refresh.references/strategy.md (priorities → format →
shortlist → style proposals → exploration render → continuity), updated
generation.md, the SKILL.md Key Files table, and presentation-creator's
Decision #11. Updated the two illustrations-freshness-* eval criteria to the
precheck contract and added illustrations-priority-model-shortlist. New tests
cover alias resolution, shortlist ranking + injection, the freshness date math,
and the style-explore helpers.illustrations-mode-routing eval criteria
count steps without the freshness step (off by one vs the committed 7-step
SKILL.md). The README "6 mode-routed steps" comment is corrected here; the
mode-routing criteria renumber is left for a dedicated pass.A sixth skill, shownotes-publisher, writes talk pages into a
Jekyll-based shownotes site (~/Projects/shownotes, published at
https://speaking.jbaru.ch). The site uses a custom markdown parser
(_plugins/markdown_parser.rb) that extracts structured fields by
pattern-matching on the body — abstract under ## Abstract,
field-block lines like **Conference:** value + **Video:** [text](url),
presentation-context paragraph starting with "A presentation at",
resources under ## Resources. The format is strict; small mistakes
silently flatten content (e.g., multi-paragraph abstracts become one
paragraph because the parser joins all lines with spaces before
markdownify).
The skill encodes the contract end-to-end:
SKILL.md — 9-step workflow from outline.yaml gather through
publish, with the field-block grammar, the "Video Coming Soon"
pattern, thumbnail conventions, and the update-don't-rewrite rulereferences/parser-contract.md — line-by-line spec of what
each extracted_* field captures (title, conference, date,
slides, video, abstract, resources, presentation_context) and howreferences/template-conditionals.md — what talk.html does
with each extracted field, including the truthiness trap on
extracted_video (any non-empty string triggers "Video Available"
— **Video:** TBD fires the wrong badge)references/common-mistakes.md — 13 documented failure modes
(entries 1, 1b, 1c, 2–11) with what visually happens and the right
way (e.g., abstract sub-headings flatten; bare-URL Slides/Video
doesn't extract; resource before abstract folds abstract into
resources)Motivating incident. This skill was authored after the
KotlinConf 2026 talk file shipped on jbaruch/shownotes commit
83ac8d9 with placeholder-URL Slides/Video lines:
**Slides:** [View Slides](#) <!-- TODO -->
**Video:** [Watch Video](#) <!-- TODO -->Both fields fired the wrong badges and rendered broken embeds; the
inline HTML comments were pulled into the captured field values by
the parser's ^\*\*Slides:\*\*\s*(.+)$ value-capture group. The
incident motivates entries 1b and 11 in references/common-mistakes.md.
The key behaviors the skill enforces:
{% if page.extracted_video %} is what flips the "Video Coming
Soon" badge to "Video Available". Adding **Video:** TBD (or any
placeholder) makes extracted_video truthy and fires the wrong
badge plus a broken embed## Abstract with a single space, collapses
whitespace, then passes the result to markdownify. Sub-headings,
lists, code blocks, and tables inside the abstract render as
flattened prose\[([^\]]+)\]\(([^)]+)\). Bare URLs survive in the
field value but break the embed include's URL-pattern matchingFour eval scenarios ship with the skill, all under evals/:
shownotes-publisher-publish-with-date — first-time publish, the
delivery date is set, filename uses the dated conventionshownotes-publisher-publish-no-date — pre-talk publish where the
delivery date is absent, filename and Date field both adaptshownotes-publisher-update-add-video — adds a video URL to an
existing file, exercises the read-then-edit preservation ruleshownotes-publisher-omit-placeholder — negative case; the user
asks for a "video coming soon" UX cue, the skill must omit the
**Video:** line entirely rather than emit a placeholder URLThe skill is invocable directly (Skill(skill: "shownotes-publisher"))
or after the presentation-creator skill finishes Phase 6 publishing
when the speaker says "now publish to shownotes". Tile size: six
skills, tile.json and README updated accordingly.
The presentation-creator skill moves from two hand-authored markdown
files (presentation-spec.md for talk metadata, presentation-outline.md
for the outline) to a single schema-validated outline.yaml. The four
derived artifacts (narrative.md, script.md, slides.md,
rhetorical-review.md) generate deterministically from it.
What changed:
New scripts/outline_schema.py — pydantic v2 source of truth.
talk: block (title, slug kebab-case-validated, speakers, duration,
audience, mode, venue, slide_budget, pacing_wpm, architecture from
closed enum, thesis, shownotes_url_base, commercial_intent,
profanity_register, must_include, must_avoid, catalog_reference,
delivery_count, delivery_date). chapters[] with target_min,
cuttable, accent, argument_beats for narrative.md. slides[]
with format (FULL/IMG+TXT/EXCEPTION/TITLE/DEMO), visual,
text_overlay, image_prompt, builds, screenplay-form script with
speaker attribution, applied_patterns against the 77-pattern closed
enum discovered from references/patterns/, callbacks ledger,
big_idea singleton, thesis preview/payoff. interludes[] for live
demos between slides (anchored by after_slide). style_anchor:
block for illustration-strategy talks.
Four new extractor scripts:
extract-narrative.py → chapter walker, proseextract-script.py → screenplay form, slides + interludes
interleaved by anchorextract-slides.py → per-slide build sheetcheck-rhetorical.py → structural gap-check over the closed
pattern taxonomy (PUNCH coverage, big-idea singleton, thesis
ordering, sparkline elements when applicable, master-story
threading, callback ledger, inoculation count, progressive-list
contiguity, duration accounting)Existing scripts rewritten to consume outline.yaml:
guardrail-check.py — profile-aware checks (slide budget, Act 1,
branding, profanity, data attribution, closing, cut lines); the
structural taxonomy now belongs to check-rhetorical.pyextract-resources.py — walks slides[]/interludes[] via
outline_schema; image prompts deliberately excludedgenerate-talk-timings.py — walks chapters[]; no markdown
parsingSkill prose rewritten end-to-end: SKILL.md (workflow table, all
phase steps, late-entry checklist, artifact table),
phase1-intent.md (talk metadata → talk: block),
phase3-content.md (full rewrite teaching the YAML schema),
phase4-guardrails.md (two-script split documented),
phase5-slides.md (slides.md is the build sheet; {slug}.md for
presenterm decks), phase6-publishing.md and
phase7-post-event.md (file refs updated).
Why it matters: the markdown outline format required regex
parsing for every downstream consumer (guardrail-check, extract-
resources, generate-talk-timings, the agent itself), and every
change to the format risked breaking parsers in unrelated scripts.
Schema validation + four single-responsibility extractors collapses
that parsing surface into one pydantic model and four deterministic
walkers — per rules/script-delegation.md's deterministic-vs-
reasoning split.
All numeric scenario-N evals renamed to descriptive kebab-case
(e.g., scenario-20 → qr-missing-shortener-detection).
eval-resources/ subdirectories renamed to match. Fixtures that
referenced presentation-outline.md or presentation-spec.md
converted to outline.yaml (QR scenarios, thumbnail evals, CFP,
illustrations-mode-routing, freshness evals, pattern-strategy-4-tier,
illustrated-outline evals, progressive-reveal-builds). Criteria
ported from markdown-bullet assertions to YAML field assertions.
Test suite: 289 / 5 skipped (+60 net).
tessl eval run from CI per updated plugin-evals policyjbaruch/coding-policy 0.3.20's rules/plugin-evals.md (Persistence
section) is explicit: do not add a tessl eval run step to tile-repo
CI, and do not add a scheduled/recurring workflow that re-runs the
suite as a persistence mechanism. The Tessl-publish layer
(tesslio/patch-version-publish@v1) owns persistence execution and
runs the eval suite automatically — any explicit step on top is
duplicate cost producing the same numbers a maintainer would already
see at publish time, and a parallel cadence can mask a publish-layer
eval failure with a parallel pass.
Two deletions:
publish-tile.yml — removed the explicit Run eval suite before publish step (tessl eval run .). The eval suite still runs (via
the publish action's internal execution); only the duplicate CI
step is gone.evals-scheduled.yml — deleted entirely. The weekly cron was a
recurring-persistence workflow of exactly the kind the rule
prohibits.Steady-state effect: every publish run drops tessl eval run . from
the CI step list; the publish action still gates on eval regressions
because it runs the suite itself. The scheduled weekly run is gone.
Local tessl eval run . for scenario authoring/debugging remains
permitted under the rule's authoring carve-out.
tessl skill review to changed-skills looppublish-tile.yml previously ran one static tessl skill review step per
skill on every push to main (5 invocations per merge). After
jbaruch/coding-policy 0.3.20 codified the changed-skills-loop pattern
in rules/context-artifacts.md, those static steps became a policy
violation — and a real cost: tessl skill review is LLM-backed, so
re-reviewing unchanged content burns Tessl credits while reproducing the
prior rubric output.
This release replaces the 5 static steps with one uses: of the
reference composite action shipped at
jbaruch/coding-policy/.github/actions/skill-review, pinned to SHA
2a9df6575e153ce0d98900fdae26384c06df478f. The action:
github.event.before..HEAD -- skills/ to identify changed skillsworkflow_dispatch or initial
push (no usable base)actions/checkout@v4 gains fetch-depth: 0 per the composite action's
documented requirement (it needs the prior-push commit reachable).
Steady-state effect: PRs that don't touch skills/ cost zero skill-review
invocations at merge; PRs that touch one skill cost one. Multi-skill PRs
scale linearly with what they actually changed.
Audited the 34-scenario eval suite against jbaruch/coding-policy: plugin-evals
(No Bleeding, Lift Not Attainment) and the user-stated rules in working
memory (test outcomes not implementation details; no agent-written
reimplementations of skill-provided scripts).
scenario-2 (duplicates
scenario-11 slide-source coverage), scenario-23 (overlaps
scenario-22+scenario-19), scenario-27 (generic python-pptx
placeholder work), structured-talk-outline-with-typed-place
(overlaps scenario-14).clarification-interactive-session, pattern-strategy-4-tier,
scenario-12, scenario-13, scenario-16, scenario-21,
scenario-22, scenario-24, scenario-26. Removed criterion-mirror
text from task bodies (Notes-on-Verification answer-key blocks,
enum literals, threshold values, verb-action directives like "do
NOT flag X"). The bleeding-strip pass left criteria.json files
untouched in every case — fixes are at the task per the rule.
Subsequent reviewer-driven commits in this PR did edit four
criteria.json files (rebalancing three sums to 100 and
reframing scenario-13's wide-angle criterion as outcome-based);
those are documented in their own entries below.scenario-0
bleeding cleanup ("(should be skipped)" annotations) plus removed
the build_tracker.py script-from-scratch requirement from
scenario-1 (vault-ingress ships Step 1 logic, not a separate
script).scenario-14 reviewed and reclassified to KEEP — audit had a
false positive; its criteria check tile-prescribed structural
tokens that the task does not pre-state.scenario-18
(OOXML element presence, python-pptx output mechanics), scenario-19
(QR image properties, qrcode-library output; subsumed by scenario-21
full orchestration + scenario-20 negative case), scenario-24
(thumbnail planning; subsumed by scenario-26 thumbnail revision
which carries richer decisional content via speaker feedback).tessl eval run . on the de-bled set and inspecting per-scenario
lift (with-context − baseline). Cut anything ≤3 lift or with a
structural mismatch:
clarification-interactive-session (−71 lift) — vault-clarification
is interactive (uses AskUserQuestion for multi-turn flow); the
with-context agent correctly refuses to operate one-shot and
scores 0, while the baseline fabricates answers and scores 71.
Negative lift signals an eval-framework mismatch, not a fixable
scenario problem.scenario-8 (Co-Presented Talk Adaptation, 0 lift) — both
variants score 100/100; criteria measure universal competence.guardrail-check-format (Guardrail Audit, 0 lift) — both
variants 100/100; same problem.scenario-22 (Extract Resources, 2 lift) — baseline 98, ceiling
effect; tile contribution drowned in universal-competence scoring.scenario-7 (PowerPoint Deck Build Plan, 2 lift) — baseline 98.scenario-25 (Post-Event Video Publishing, 3 lift) — baseline 97.Suite goes from 34 to 21 scenarios. Average lift across the remaining suite is substantially higher.
Skill coverage after pruning. jbaruch/coding-policy: plugin-evals
requires every skill with decisional logic to ship eval cases. After
this PR, all five skills retain at least one eval case in the suite:
scenario-12 (Humor Post-Mortem
and Blind Spot Debrief), which tests vault-clarification's
one-shot-evaluable decisional surface: recency-adapted questioning,
per-beat humor grading, blind-spot probing grounded in analysis
observations, structured-output capture. The interactive
multi-turn AskUserQuestion flow that
clarification-interactive-session previously attempted to cover
is architecturally outside the eval framework's reach (the
with-context agent correctly refuses to operate one-shot, producing
the −71-lift signal that drove the retirement); this is an
eval-framework limitation, not a coverage gap the eval suite is
meant to close. The skill's
decisional surface that can be one-shot-evaluated is covered.Reviewer-driven criteria edits. Cross-family policy review on this
PR surfaced two criteria.json-side issues that were not in the
original bleeding-strip scope:
weighted_checklist max_score sums of 95 instead
of 100, violating the eval-authoring weighting contract:
scenario-1 bumped "No-sources talk flagged as unprocessable"
10 → 15 (the high-decisional behavior the tile teaches);
scenario-20 bumped "Agent distinguishes missing config from
opt-out" 10 → 15 (the unique tile insight); scenario-21 bumped
"Command uses --shownotes-url (not --short-url)" 10 → 15 (the
tile-prescribed arg choice). All 21 surviving scenarios now sum to
exactly 100.scenario-13's "Wide-angle detection" criterion previously prescribed
a numeric ratio threshold ("ratio above 5:1 or 10:1 triggers a
warning"). After de-bleeding stripped the task's hand-fed ratio
interpretation, the criterion's threshold-direction was exposed as
ambiguous (case_clean at 50/45 = 1.11:1 is even lower than
case_wide_angle's 1.33:1, so any pure ratio threshold either
false-flags clean or misses wide-angle). The criterion is now
outcome-based: it grades that the agent flags case_wide_angle
as wide-angle without false-flagging case_clean, using whatever
signal the agent derives from extraction metadata. No specific
numeric threshold is prescribed.tessl.json floats its dependencies to "latest" because tessl update
rewrites the manifest in-place at runtime and .tessl/tiles/ is
gitignored — pinning produces silent drift between commit history and
the running install. jbaruch/coding-policy: dependency-management
permits this only when three preconditions are met. This release adds
all three:
rules/tessl-version-floating.md
documenting the carve-out, naming tessl.json as the single covered
manifest, and explaining why pin/lock semantics break in this shape.
Registered under tile.json → steering.scripts/check-tessl-pins.sh that walks
every covered manifest and fails if any dependency uses a specifier
other than "latest" — rejecting literal pins, version ranges, tags,
and anything else per the carve-out's "rejecting only literal pins
lets a non-literal pinned/ranged value slip through" warning..github/workflows/tests.yml runs the check ahead
of the test suite on every push and PR. CI failure blocks merge.The second tessl.json dependency (tessl-labs/tessl-skill-eval-scenarios)
also moves to "latest" — the carve-out applies to the manifest as a
whole, mixed pin/float within a covered manifest is not allowed.
New Step 2 in the illustrations skill runs before Strategy comparison or
deck Generation touches images. It uses WebSearch to identify current
flagship image-generation models from the major vendors (Google's Gemini
image + Imagen, OpenAI's gpt-image-*, and any other vendor with a
publicly accessible image API) and surfaces gaps against the script's
COMPARE_MODELS constant and — for Generation mode — the outline's baked
**Model:** choice plus its selection date.
If newer flagships exist, the step proposes updating COMPARE_MODELS
(Strategy) or re-running --compare against an updated list (Generation)
before continuing. The motivation is the months-long gap between when a
model was picked for a talk and when illustrations are actually generated
— a window in which a vendor often ships a meaningfully better flagship
(the recent gpt-image-2 release being the precipitating example).
Step numbers in SKILL.md and the four reference files shift accordingly:
Strategy → Step 3, Generation → Step 4, Builds → Step 5, Apply → Step 6,
Thumbnail → Step 7.
generate-illustrations.py is no longer Gemini-only. The script now
dispatches by model-name prefix to three vendor families:
gemini-* and nano-banana-* → Google generateContent (existing path)imagen-* → Google :predict endpoint with format-derived aspect
ratio (new — FULL → 16:9, IMG+TXT → 3:4, the closest of Imagen's
supported 1:1 / 9:16 / 16:9 / 3:4 / 4:3 set to the IMG+TXT 2:3 anchor)gpt-image-* → OpenAI /images/generations for fresh images and
/images/edits (multipart) for the --edit, --build, and --fix
workflows; size is format-derived (FULL → 2048x1152 true 16:9,
IMG+TXT → 1024x1536 true 2:3) (new)API-key resolution gains an openai slot. secrets.json now reads both
gemini.api_key and openai.api_key; either may also come from the
GEMINI_API_KEY / OPENAI_API_KEY environment variables. The script
only demands the key(s) needed by the models a given run will actually
hit — Gemini-only outlines don't require an OpenAI key, and vice versa.
Missing-key errors are per-vendor and include the right signup link
(aistudio.google.com/app/apikey for Google, platform.openai.com/api-keys
for OpenAI).
COMPARE_MODELS is refreshed to current flagships across vendors:
gemini-3-pro-image-preview, gemini-3.1-flash-image-preview,
nano-banana-pro-preview, imagen-4.0-ultra-generate-001, and
gpt-image-2. The older gemini-2.0-flash-preview-image-generation and
imagen-3.0-generate-002 entries are dropped — they were superseded by
the flagships above (and the Imagen-3 entry was effectively broken
anyway, since generateContent doesn't accept Imagen models).
Imagen models have no public edit endpoint, so --edit, --build, and
--fix against an Imagen-family outline return an actionable error
directing the speaker to a Gemini or OpenAI model for editing workflows.
The outline parser also gained + and - tolerance in the Format and
STYLE ANCHOR regex ([\w+-]+ replaces \w+) so the documented IMG+TXT
token is parsed correctly — previously it produced no match and the slide
silently fell back to the first available anchor and the FULL sizing
default. Safe-zone precedence is now applied uniformly:
apply-illustrations-to-deck.py treats Safe zone: presence as the
FULL/title-overlay signal regardless of the Format: token, so the
generator mirrors that — when Safe zone is present, the slide is
treated as FULL for anchor selection, vendor sizing, AND the directive
itself (via a new effective_slide_format() helper threaded through
every run_* caller).
New tests cover model-family classification across vendors, multi-vendor
key resolution (secrets.json, env-var fallbacks, partial config, malformed
JSON warning), the OpenAI multipart body structure, final_build_dest
extension preservation, the empty-build-steps parse path, the format
sizing table, and the IMG+TXT outline regex fix.
illustrations skill from presentation-creatorThe visual layer (deck illustration strategy, generation, build chains, and
YouTube thumbnails) moves from presentation-creator into a new illustrations
skill. presentation-creator now delegates at three points: Phase 2 Decision
#11 (style strategy), Phase 5 Step 5.1b (illustration generation + build
generation + apply-to-deck), and Phase 7 Step 7.1 (thumbnail).
skills/illustrations/ with mode-routed SKILL.md (strategy /
generation / thumbnail) and four references: strategy.md, generation.md,
builds.md, thumbnails.md. Existing title-placement.md moved here too.generate-illustrations.py, apply-illustrations-to-deck.py,
generate-thumbnail.py, suggest-scrim-color.py. Tests updated to point
at the new location; all 188 existing tests still pass.apply-illustrations-to-deck.py now handles Format: IMG+TXT slides as a
first-class layout (image left ~60%, title + body right column), in addition
to the existing FULL + Safe-zone path. New IMGTXT_* geometry constants;
six new tests cover format parsing, picture repositioning, title repositioning,
and column-width consistency.Skill(skill: "illustrations") rather than carrying inline workflow.tile.json adds the new skill entry. README updates skill count from four
to five and rewrites the architecture diagram.template_layoutsscripts/pptx-extraction.py now extracts the master slide-layout
catalog ({index, master_index, name, placeholders} per layout) and
emits it under a top-level template_layouts key. Previously the
script emitted only per_slide_visual and global_design, so each
vault-profile regen silently carried forward the prior profile's
hand-curated layouts without ever refreshing them from the source
.pptx.
The master_index field disambiguates layouts that share a name
across different slide masters — PowerPoint allows reuse of layout
names like "Title and Content" across masters, so name alone is
unsafe as a merge key. Placeholder extraction catches AttributeError
specifically (rather than a bare Exception catch-all) and writes a
diagnostic to stderr with master index + layout name + placeholder
context when a malformed placeholder is skipped.
skills/vault-profile/SKILL.md Step 3 documents the merge contract:
the script is the source of truth for layout existence (index,
master_index, name, placeholders), while the speaker-curated
use_for field is preserved across regenerations by matching the
(master_index, name) pair.
skills/vault-profile/references/speaker-profile-schema.md adds an
inline note to the template_layouts example explaining the curation
contract.
tests/test_pptx_extraction.py adds 6 regression tests covering the
new extract_template_layouts function: emitted-key assertion,
default-count baseline, per-entry schema, sequential global indices,
placeholder schema (idx/type), and known layout-name presence.
Five patterns observed across the vault corpus but not present in the canonical Ford/McCullough/Schutta or Reynolds/Duarte sources have been formalized into the taxonomy:
patterns/deliver/delayed-self-introduction.md — open with a hook
before introducing the speaker; the bio answers a question the
audience has already implicitly asked. Vault dimensions 2, 11.patterns/build/three-part-close.md — closing structure of three
separate slides (recap, CTA, thanks) rather than a single combined
closing slide. Vault dimensions 2, 10.patterns/build/progressive-reveal.md — single complex base image
annotated cumulatively across multiple slides, with a payoff slide
that resolves the buildup. Vault dimensions 4, 7.patterns/deliver/anti-sell.md — speaker downplays own product or
employer at moments where the audience expects a pitch, buying
credibility for substantive claims later. Vault dimensions 11, 6.patterns/build/meme-as-argument.md — internet memes used as
argumentative devices rather than decoration; relies on shared
cultural reference to compress claims. Vault dimensions 4, 7, 12.Taxonomy size: 97 → 102 entries (72 → 77 patterns; antipatterns unchanged at 25). Observable count: 86 → 91. Build phase: 34 → 37 patterns; Deliver phase: 19 → 21 patterns.
Index, summary stats, README structure tree, and tile.json summary +
description updated to reflect new counts.
Third source ingested alongside Ford/McCullough/Schutta (2013) and Reynolds (2012): Nancy Duarte, Resonate: Present Visual Stories that Transform Audiences (Wiley, 2010).
patterns/build/sparkline.md — persuasion-specific narrative arc
with two named turning points (Call to Adventure, Call to Action)
and a "new bliss" close; vault dimensions 2, 5, 9patterns/build/call-to-adventure.md — first sparkline turning
point: dramatize the "what is" / "what could be" gap and reveal
the Big Idea; vault dimensions 1, 2, 9patterns/build/call-to-action.md — second sparkline turning
point: specific, immediately-executable asks differentiated by
audience action-temperament type (Doer / Supplier / Influencer /
Innovator); vault dimensions 4, 6, 9patterns/build/new-bliss.md — vivid future-state vision after
the Call to Action; ensures the talk ends on a higher emotional
plane than it started; vault dimensions 5, 6, 9patterns/build/star-moment.md — "Something They'll Always
Remember": planted dramatic peak in five sub-types (memorable
dramatization / repeatable sound bite / evocative visual /
emotive storytelling / shocking statistic); vault dimensions 3,
5, 13patterns/build/inoculation.md — preemptively voice the
audience's strongest objection (steel-manned) and address it
inside the talk; vault dimensions 4, 9patterns/build/master-story.md — single anecdote woven
recursively through the talk, each return deepening rather than
repeating; vault dimensions 2, 5, 7mentor.md ← Adopting the Stance — Planning Implications
(six-dimensional audience research, move-from/move-to matrix,
resistance map, reward proportionality)the-big-why.md ← The Big Idea — Statement Format (three
required components: unique POV + explicit stakes + complete
sentence)vacation-photos.md ← Numerical Narrative — Making Numbers
Land (Scale / Compare / Context techniques)peer-review.md ← Screening with Critics — Beyond Copyediting
(3× duration external critic session; six dysfunctional review
patterns to avoid)crucible.md ← Murder Your Darlings — The Pre-Delivery Cut
Pass (convergent-thinking filter pass after divergent
generation)sparkline.md ← The Three Contrast Types — Engine of the
Middle (content / emotional / delivery contrast as the
persuasive-middle oscillation engine)## Related Reading Duarte citations.patterns/_index.md — catalog tables, phase lookup, vault-dim
mapping, summary stats, and sources updated. Total taxonomy entries
now 97 (72 patterns + 25 antipatterns); 86 observable.The speaker's slide-design-spec.md lives in their vault at
~/.claude/rhetoric-knowledge-vault/slide-design-spec.md (not in
this repo — it's per-speaker generated data). Two new reference
sections added to the vault file:
The presentation-creator skill in this repo references those
sections via phase5-slides.md (General Design Principles).
generate-thumbnail.py --portrait-style "<anchor>" — new flag
enables a two-pass pipeline for decks with an Illustration Style
Anchor (Phase 2 output). The script first pre-stylizes the speaker
photo into the anchor's medium (sepia tech-manual, watercolor, ink,
etc.) via a Gemini image-edit call, then runs the normal composition
step using the stylized portrait as input. Fixes the palette-mismatch
problem on illustrated decks that neither --aesthetic photo nor
--aesthetic comic_book could solve. Independent of --aesthetic;
they compose. Phase 7 Step 7.1 now passes the anchor through
automatically when presentation-outline.md has a ## STYLE ANCHOR
block. Fixes #31.Second source ingested alongside Ford/McCullough/Schutta (2013): Garr Reynolds, Presentation Zen (2nd ed., 2012, New Riders).
patterns/prepare/opening-punch.md — Reynolds's PUNCH framework
(Personal / Unexpected / Novel / Challenging / Humorous) for
opening hooks; vault dimensions 1, 4patterns/deliver/screen-blackout.md — deliberate B-key blackout
or planned black slides as attention-redirection device; vault
dimensions 12, 13breathing-room.md ← Hara Hachi Bu (90–95% finish-line discipline)concurrent-creation.md ← Plan Analog Before Going Digitalthe-big-why.md ← The Elevator Test (30–45 sec core-message check)## Related Reading Reynolds citations
(slideuments, bullet-riddled-corpse, floodmarks, borrowed-shoes,
cookie-cutter, ant-fonts, narrative-arc, triad, crucible,
concurrent-creation, vacation-photos, cave-painting, takahashi,
bunker, bookends, coda, breathing-room).patterns/_index.md — catalog tables, phase lookup, vault-dim
mapping, summary stats updated; sources section now lists Reynolds
alongside Ford et al.test_stylize_portrait_* × 4, test_compose_thumbnail_* × 2).Talk timer, Keynote compatibility, shownotes destination — New delivery timer artifact, documented Keynote gotchas for slide generation, and machine-readable shownotes publishing destination.
generate-talk-timings.py — new script parses ## Pacing Summary table
from the outline into MM:SS Chapter plain-text format for timemytalk.app.
Supports --qa flag for Q&A chapters, sub-minute resolution, and automatic
subdivision of acts exceeding 5 min using ## Section headersslide-generation-rules.md: use rectangles not connectors for
decorative lines, never create-then-remove shapes in the same authoring flow,
keep shape IDs contiguous per slidepublishing_process.shownotes_site added
to speaker profile schema. Resources-gathering rules section 8 documents the
read path: construct talk URLs from shownotes_site + shownotes_url_pattern,
never guess or search the webpublishing_process.shownotes_sitegenerate-talk-timings.py (pacing parsing, cumulative times,
Q&A insertion, sub-minute resolution, subdivision)Vault-clarification eval + test suite — First dedicated eval for the interactive clarification session, fixed volatile eval scenarios, and full pytest coverage for every script with CI.
clarification-interactive-session — first eval testing the vault-clarification
skill's interactive session: rhetoric clarification (one question at a time), humor
post-mortem (per-beat grading), blind spot probing, infrastructure config capture,
intent confirmation storage, and session completion marking. Fixed test data with 1
analyzed talk, empty config, 10-criterion weighted checklisteval-resources/scenario-12/ (recent + old talk analyses)eval-resources/scenario-13/ (6 concrete recording cases)pptx-extraction.py — fixed AttributeError crash on _NoneColor when extracting
font colors from slides with unset color propertiestests.yml) — runs on push to main + PRs, Python 3.12,
installs ffmpeg and LibreOffice for full integration coveragepyproject.toml — declares all dependencies (python-pptx, lxml, qrcode, Pillow,
imagehash, numpy) with [test] optional group for pyteststrip-template.py — wrapped in strip_slides() + main() guard for importabilitydelete-slides.py — wrapped in delete_slides() + main() guardreorder-slides.py — wrapped in reorder_slide() + main() guard (now raises
IndexError on out-of-range instead of sys.exit)export-pdf.py — wrapped in main() guard, functions now take parameters_pptx_repair.py — extracted shared clean_viewprops() from strip-template and
delete-slides into a single module, eliminating code duplicationPlaceholder slides, resources gathering, and post-event workflow — New deck adaptation tooling, Phase 6.0 resources extraction, Phase 7 post-event workflow, and hardened QR generation.
insert-placeholder-slides.py — new script inserts bright-yellow placeholder
slides at specified positions (1-indexed). Supports JSON file or --at/--title
CLI input, --output flag for non-destructive saves. Processes positions in
descending order to avoid index shiftingextract-resources.py script parses
presentation outlines for URLs, GitHub repos, book references, RFCs, and
tool/library mentions. Deduplicates, tracks slide context, outputs JSON or markdowngenerate-thumbnail.py — YouTube thumbnail generation via Gemini, composing
slide images + speaker photos with style variants and YouTube spec validationpresentation-spec.mdgenerate-qr.py supports custom domains (e.g., jbaru.ch)--png-only mode — generate QR PNG without opening a decksecrets.json creation commands in error messagesQR code generation — Automated QR code generation and insertion into decks during Phase 6 publishing, with slide background color matching and auto-contrast foreground.
Gemini API key in secrets.json — generate-illustrations.py now reads the Gemini
API key from {vault}/secrets.json (gemini.api_key) first, falling back to the
GEMINI_API_KEY environment variable for backward compatibility. This unifies all API
keys in one file. New --vault CLI argument for custom vault paths.
generate-qr.py script — new script generates unbranded QR codes from shownotes
URLs (or pre-shortened URLs), matches the QR background to the target slide's color,
and auto-selects white or black foreground based on WCAG relative luminance. Inserts
the QR as a 2" square in the bottom-right corner of the configured slide(s){vault}/secrets.json (not env vars),
documented with chmod 600 recommendationqr_code — 5 new fields: custom_url, shortener,
rebrandly_domain, bg_color_match, preferred_short_pathqr_codes[] — new top-level array tracking per-talk QR
metadata: talk slug, target URL, shortener, short path/URL, link ID, PNG pathIllustration pipeline — AI-generated illustrations are now a first-class part of the presentation creation process, with collaborative style decisions and per-slide image prompts generated during outline creation.
--compare mode), and visual
continuity devices[STYLE ANCHOR] token
referencing the header. [IMAGE NN] placeholder type for EXCEPTION slides[SKIP] for non-illustrated outlinesgenerate-illustrations.py to batch-generate images before slide population.
Image Generation Setup docs with API key, model, and --compare instructionsillustration_style, illustration_coherence,
image_source_distribution, visual_continuity_devices added to extraction outputvisual_style_history — new section with default style,
style departures, mode-specific visual profiles, and confirmed visual intentstranscript_source added as required field on talk entries and
subagent return schema. delivery_language and co_presenter added to subagent
return schema. English-first quote rule promoted to inline in SKILL.mdskills/presentation-creator/references/generate-illustrations.py — stdlib-only
Python script for Gemini API image generation with --compare mode, resumable
batch runs, rate limiting, and progress reporting[SKIP] illustrations lineSmall print — Sessions catalog entries now include a "Small Print" field for Program Committee notes (talk positioning, what it is/isn't, reviewer context).
Sessions catalog — New sessions-catalog.md file in the vault for maintaining
submission-ready conference materials (title, abstract, outline) per active talk.
sessions-catalog.md to the vault skill's Key Files tableCanonical vault path — The vault now uses ~/.claude/rhetoric-knowledge-vault/ as
a fixed, discoverable location. No more asking "where should the vault live?" every
session. Custom locations (e.g., Google Drive) are symlinked to the canonical path.
vault_root — checks canonical
path first, creates or symlinks on first runvault_storage_path config field tracks the actual directory when using a custom
locationMaintenance — Version bump and CLI publish.
Eval scenarios — Added 5 new server-generated eval scenarios via tessl scenario generate, covering both skills end-to-end. Reviewed and fixed all 15 scenarios for
quality, then ran the full eval suite (baseline avg 62% → with-skill avg 98%).
optimize_slide_text with python-pptx
overflow handling)video_extraction field, clarified planning-time vs download-outcome for
slide_source)capability.txt files to all new scenariosVideo-extracted slides — When no slides file exists, extract slides directly
from video: ffmpeg frame extraction → crop to slide area (exclude PiP) → perceptual
hash deduplication → combine into PDF. Marks slide_source: "video_extracted".
Non-YouTube video support — Step 3A now supports ingesting talks from InfoQ,
Vimeo, conference platforms, and any source yt-dlp supports. Downloads audio via
yt-dlp -f http_audio, transcribes locally with MLX Whisper (Apple Silicon) or
OpenAI Whisper. Tags transcript source as "whisper" vs "youtube_auto".
Data integrity fixes:
co_presenter,
delivery_language, and other structured DB fields directly from analysis results,
not burying them in rhetoric_notes free text.Blind spot clarification + language policy — Two additions to the vault skill:
blind_spot_observations.delivery_language on the talk entry. Prevents non-English content from polluting
the signature list or rhetoric summary.Robustness & conciseness — Addressed gaps found during tile review and tightened both skills for the review gate.
pattern_observations and marks them needs-reprocessingclarification_sessions_completed counter to tracking DB configschemas.md, compressed profile mapping and badgesPresentation Patterns integration — Integrated the pattern taxonomy from Presentation Patterns (Ford, McCullough, Schutta 2013) as a structured reference, vault scoring system, and brainstorming vocabulary across both skills. Patterns are classified as observable (scored by the vault) or unobservable (surfaced as a go-live checklist before delivery).
id, name, type, part,
phase_relevance, vault_dimensions, detection_signals, related_patterns,
inverse_of, difficulty, and observable (true by default, false for 11 entries)references/patterns/_index.md): flat catalog table, phase-grouped
lookup, vault dimension reverse mapping, and unobservable patterns go-live checklistobservable: false in their frontmatter, excluded
from vault scoring and pattern_profile, and surfaced as a go-live preparation
checklist in creator Phase 6observable: falsepattern_observations field added to both subagent return schema and tracking
database talk entries (schemas.md)pattern_profile in the speaker profile with mastery
levels, usage trends, signature combinations, antipattern frequency, and never-used
patterns (observable only)pattern_profile section added to speaker-profile-schema.md with documentation
that only observable patterns are includedrhetoric-dimensions.md cross-referenced with their
related patterns and antipatternsreferences/patterns/_index.md alongside vault documentspattern_profile:
[RECURRING] flags from pattern_profile.antipattern_frequency and [CONTEXTUAL]
flags from outline analysisREADME.md — rewritten with Presentation Patterns section, observable/unobservable
table, updated file tree, updated vault/creator descriptionstile.json — bumped to v0.5.0, added "patterns" keywordCHANGELOG.md — this entryReview & consistency fixes — Addressed consistency gaps found during tile review.
analyses/ (fixes broken adaptation workflow in creator)badges schema to speaker-profile-schema.mdpublishing_process question into targeted sub-questions matching the schemacfp, abstract, pptx keywords to tile.jsontessl.json project name from scaffold placeholderslide-generation.mdCI/publish pipeline tuning — Iterative adjustments to the GitHub Actions publish workflow: switched to the publish action's built-in skill review gate, tested optimize input, and settled on the default review threshold (50%).
Evaluation scenarios — Added 10 eval scenarios covering both skills (vault analysis and presentation creation), plus Tessl eval infrastructure.
Speaker badges & profile Step 6 enhancement — Profile regeneration now generates personalized speaker badges as a fun summary of portfolio-wide achievements, mined from real vault data (meme counts, employer transitions, recurring patterns, signature quirks).
PPTX as primary slide source — The vault skill no longer requires Google Drive slide
PDFs for every talk. Talks with .pptx files can now be processed directly, providing
richer data (exact hex colors, font names, layout names) than PDF visual inspection.
video_url + at least one of slides_url or pptx_pathslide_source field on each talk: "pdf", "pptx", or "both"slides_url and pptx_path are both optional (at least one required)Initial release with two skills:
.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