Eleven skills for conference speakers covering rhetoric analysis, talk authoring, deck building and export, CFP submissions, publishing, illustrations, shownotes, and screencast production, informed by a 113-entry Presentation Patterns taxonomy (83 observable: 64 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns).
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
#!/usr/bin/env python3
"""Emit the build-expansion manifest for deck assembly.
Reads outline.yaml (the single source of truth) + the generated build frames and
produces a JSON manifest describing how each progressive-reveal parent slide
expands into its sequence of full-bleed frames. The manifest is consumed by the
ExpandBuilds VBA pass (via expand-builds.sh) which replaces each parent slide
with its frames.
Why a manifest (not direct deck edits): structural deck edits go through real
PowerPoint / RunDeckOps, never python-pptx — see rules/deck-editing-rules.md.
This script never touches the deck. Its one write besides ``--out`` is the
stamped frame copies described below, when the outline stamps its footer.
Manifest shape (schema_version 1):
{
"schema_version": 1,
"builds": [
{
"parent": 7,
"frames": ["/abs/builds/slide-07-build-00.jpg", ..., "slide-07-build-03.jpg"],
"notes": ""
}
]
}
- `parent` — the 1-based position, in the built deck, of the slide that
carries the `builds` block (`outline_schema.deck_positions`): the slide
ExpandBuilds replaces. It equals the outline number only when numbering runs
1..N; a zero-based or gapped outline still names the right slide.
- `frames` — absolute paths to build-00 .. build-N, in ascending step order.
The parent slide is replaced by these frames (build-00 first, build-N last).
- `notes` — speaker notes for the FINAL frame only (empty when none); earlier
frames get no notes (see skills/illustrations/references/builds.md).
When the outline stamps its footer (`style_anchor.footer_style`), each poster
parent's frames are stamped by `footer_stamp.stamp_footer_file` into
`BUILDS_DIR/footer-stamped/slide-NN-build-MM.png` and `frames` lists those
copies; the raw frames stay footer-free for editing. Every build parent of
such a deck must satisfy `footer_stamp.stamped_slide`, the same answer
apply-illustrations-to-deck.py uses for parent slides; any other parent exits
non-zero naming the slide.
ExpandBuilds processes parents in descending slide order so inserting a parent's
frames never shifts the indices of lower-numbered parents still to be expanded.
Usage:
build-expansion-manifest.py OUTLINE_YAML BUILDS_DIR [--out manifest.json]
stdout: the manifest JSON (also written to --out when given).
Exit non-zero with a stderr diagnostic when a declared build parent has no
frames on disk, or a declared frame is not a file that decodes as an image.
"""
import argparse
import json
import sys
from pathlib import Path
# A sibling module: this script's own directory is already importable.
from footer_stamp import (
FRAME_RE,
FooterStampError,
deck_stamp_set,
require_one_geometry,
stamp_footer_file,
stamp_spec,
stamped_slide,
unusable_image,
)
# outline.yaml is the single source of truth; its schema + loader live with the
# presentation-creator scripts.
sys.path.insert(
0,
str(
Path(__file__).resolve().parent.parent.parent
/ "presentation-creator"
/ "scripts"
),
)
import outline_schema # noqa: E402 (path appended above)
SCHEMA_VERSION = 1
def parse_build_specs(outline_path: Path) -> list[dict]:
"""Return build specs for each parent slide: {parent, count, steps, footer}.
Read from outline.yaml's per-slide `builds`. The schema guarantees steps are
contiguous from 0, so `steps` is [0, 1, ..., N-1] and `count` == len(steps).
`footer` is the (text, style) to stamp on the parent's frames, or None.
"""
outline = outline_schema.load_outline_partial(outline_path)
footer = stamp_spec(outline.style_anchor)
specs = []
for slide in outline.slides:
if not slide.builds:
continue
if footer is not None and not stamped_slide(slide):
raise SystemExit(
f"ERROR: slide {slide.n} is a build parent in a poster deck that "
"stamps its footer, but it is not an illustrated FULL slide "
"without safe_zone; poster-theatrical decks allow only those. "
"Set it to format FULL with an image_prompt and remove safe_zone, "
"or drop its builds."
)
steps = sorted(b.step for b in slide.builds)
specs.append(
{
"parent": slide.n,
"count": len(steps),
"steps": steps,
"footer": footer,
}
)
return specs
def frames_for(builds_dir: Path, parent: int) -> dict[int, Path]:
"""Map step number -> build frame file for a parent (slide-NN-build-MM.<ext>)."""
found = {}
for p in builds_dir.glob(f"slide-{parent:02d}-build-*"):
fm = FRAME_RE.search(p.name)
if fm:
found[int(fm.group(1))] = p
return found
def _require_usable_frames(specs: list[dict], builds_dir: Path) -> None:
"""Every declared frame is on disk and decodes, before any is stamped or listed.
parse_build_specs reads outline.yaml through the schema, which already
guarantees each slide's build steps are contiguous from 0, so step count,
ordering and contiguity need no re-checking here.
"""
for spec in specs:
parent, steps = spec["parent"], spec["steps"]
found = frames_for(builds_dir, parent)
missing = [s for s in steps if s not in found]
if missing:
raise SystemExit(
f"ERROR: slide {parent} is missing build frame(s) for step(s) "
f"{missing} (declared build-NN entries: {steps}). A partial "
"sequence would expand into a broken reveal. Regenerate: "
f"generate-illustrations.py <outline> --build {parent}"
)
unusable = [
why for step in steps if (why := unusable_image(found[step])) is not None
]
if unusable:
raise SystemExit(
f"ERROR: slide {parent}'s build frames cannot be placed: "
f"{'; '.join(unusable)}. ExpandBuilds would fail on them inside "
f"PowerPoint. Regenerate: generate-illustrations.py <outline> "
f"--build {parent}"
)
def _require_one_stamped_geometry(
outline_path: Path, specs: list[dict], builds_dir: Path
) -> None:
"""The whole deck's stamp set shares one size before any frame is stamped.
The same set the apply step checks: every stamped slide image and every
declared build frame, so whichever command runs first refuses a mixed deck.
A stale frame outside the declared steps is never stamped, so it never
constrains geometry.
"""
if not any(spec["footer"] is not None for spec in specs):
return
slides = outline_schema.load_outline_partial(outline_path).slides
try:
require_one_geometry(deck_stamp_set(slides, builds_dir.parent, builds_dir))
except FooterStampError as exc:
raise SystemExit(f"ERROR: cannot stamp the build frames: {exc}") from exc
def build_manifest(
outline_path: Path, builds_dir: Path, notes_map: dict | None = None
) -> dict:
"""Build the expansion manifest.
notes_map: optional {"<0-based deck slide #>": "notes"} (the historical
inject-notes format). A build parent at 1-based deck position P
(``outline_schema.deck_positions``, so outline numbering may start at 0)
maps to 0-based key str(P-1), and P is the manifest's ``parent``, the index
ExpandBuilds replaces; its notes ride onto the FINAL build frame so expansion
doesn't drop them. When omitted, notes are empty and the post-expansion
notes pass must carry them instead.
"""
notes_map = notes_map or {}
# ExpandBuilds and the notes map index the built deck, not outline numbers.
positions = outline_schema.deck_positions(
outline_schema.load_outline_partial(outline_path).slides
)
specs = sorted(parse_build_specs(outline_path), key=lambda s: s["parent"])
_require_usable_frames(specs, builds_dir)
_require_one_stamped_geometry(outline_path, specs, builds_dir)
builds = []
for spec in specs:
parent, steps = spec["parent"], spec["steps"]
found = frames_for(builds_dir, parent)
# A null/absent note is empty; a non-string note is an error — never
# coerce it, or a JSON null would become the literal "None" on the final
# build frame (mirrors notes-to-packed.py's drop-empty contract).
position = positions[parent]
raw_note = notes_map.get(str(position - 1))
if raw_note is None:
raw_note = ""
elif not isinstance(raw_note, str):
raise SystemExit(
f"ERROR: note for slide {parent} (notes key {position - 1}) must be a "
f"string, got {type(raw_note).__name__} — fix the --notes JSON."
)
frames = [found[s] for s in steps]
if spec["footer"] is not None:
text, style = spec["footer"]
stamped_dir = builds_dir / "footer-stamped"
stamped: list[Path] = []
for frame in frames:
try:
stamped.append(stamp_footer_file(frame, stamped_dir, text, style))
except FooterStampError as exc:
raise SystemExit(
f"ERROR: cannot stamp the footer on slide {parent}'s build "
f"frames: {exc}"
) from exc
except OSError as exc:
raise SystemExit(
f"ERROR: cannot stamp {frame.name} into {stamped_dir} "
f"({type(exc).__name__}: {exc.strerror or exc}); regenerate "
"or replace that frame if it is unreadable, or make the "
"directory writable, then re-run"
) from exc
frames = stamped
builds.append(
{
"parent": position,
"frames": [str(frame.resolve()) for frame in frames],
"notes": raw_note,
}
)
return {"schema_version": SCHEMA_VERSION, "builds": builds}
def main(argv=None):
ap = argparse.ArgumentParser(
description="Emit the build-expansion manifest for deck assembly."
)
ap.add_argument(
"outline", type=Path, help="Path to outline.yaml (the single source of truth)"
)
ap.add_argument(
"builds_dir", type=Path, help="Directory with slide-NN-build-MM.<ext> frames"
)
ap.add_argument(
"--out", type=Path, default=None, help="Also write the manifest JSON here"
)
ap.add_argument(
"--notes",
type=Path,
default=None,
help='Optional notes JSON {"<0-based slide #>": "text"}; a build '
"parent's notes ride onto its final frame so expansion keeps them",
)
args = ap.parse_args(argv)
if not args.outline.is_file():
raise SystemExit(f"ERROR: outline not found: {args.outline}")
try:
outline_schema.require_current_outline(args.outline)
except outline_schema.OutlineVersionError as exc:
raise SystemExit(f"ERROR: {exc}") from exc
notes_map = None
if args.notes is not None:
try:
notes_map = json.loads(args.notes.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise SystemExit(f"ERROR: cannot read notes JSON {args.notes}: {exc}")
if not isinstance(notes_map, dict):
raise SystemExit(
f"ERROR: notes JSON {args.notes} must be an object mapping slide # -> text"
)
manifest = build_manifest(args.outline, args.builds_dir, notes_map)
text = json.dumps(manifest, indent=2)
if args.out is not None:
try:
args.out.write_text(text + "\n", encoding="utf-8")
except OSError as exc:
print(
f"ERROR: cannot write manifest {args.out}: {exc.strerror or exc} — "
"check the path and that its directory is writable",
file=sys.stderr,
)
return 1
print(text)
return 0
if __name__ == "__main__":
sys.exit(main()).tessl-plugin
rules
skills
cfp-writer
references
illustrations
presentation-builder
presentation-creator
references
patterns
build
deliver
prepare
scripts
presentation-publisher
screencast-editor
screencast-recorder
shownotes-publisher
vault-clarification
vault-ingress
references
scripts
vault-profile