- name
- pitchdeck
- description
- Convert a product README into a source-controlled pitch-deck bundle and editable PPTX for Google Slides, PowerPoint for the web, Keynote, or other presentation editors; use when asked to make a deck from a README, create a pitch deck manifest, build README-to-PPTX slides, or separate public and private deck claims.
- triggers
- ["readme-to-pitchdeck","update the pitch deck","edit the pitch deck","convert README to pitch deck","make a deck from README","create pitch deck from repository README","README to PPTX","generate Google Slides handoff from README","create public and private pitch decks"]
- provides
- ["pitch-deck-manifest","deck-claim-ledger","editable-pptx-export","slide-contact-sheet","public-private-claim-filter","deck-ui-bundle","browser-deck-renderer","house-conformance-gate","publish-verification","react-deck-payload","house-style-measurement","author-voice-profile","native-editable-icon-library"]
- composes
- ["memory","embedding","codex","imagegen","create-figure","best-practices-slide-design","ux-lab","browser-oracle","surf","ask","agentic-evals","project-knowledge"]
- complies
- ["best-practices-skills","best-practices-python","best-practices-security"]
- runtime_self_improvement
- basic
- taxonomy
- ["presentation","validation","compliance","precision","claim-boundary"]
- metadata
- {"short-description":"Claim-bound README-to-PPTX compiler"}
- domains
- ["marketing"]
- disciplines
- ["content-creation"]
# README to Pitch Deck
Use this skill to turn one or more local README files into a **reviewable deck
bundle**, not to treat README prose as automatically approved marketing copy.
The compiler emits editable PowerPoint slides that can be imported into Google
Slides and refined by a project agent or human designer.
## Immutable operating rule
**The README is source material, not authority for stronger claims.** Every slide
must retain source references, visibility, claim state, and required qualifiers.
Public decks fail closed if they reference private sources or claims.
## Ingest a complete canonical ZIP
`./run.sh ingest-package --package handoff.zip --output-dir /path/to/NEW-bundle`
validates without creating the destination. Add `--execute` to import unchanged
bytes into a new directory; existing destinations are refused. The root
`deck.document.json` is authoritative, with relative assets/source snapshots and
optional debugger/slide-map companions. Include the complete `deck.theme_tokens`
snapshot; an optional plain `theme.json` must agree. The expanded
`deck.authoring.json` is review material only, never merged as an editing input.
See `docs/PACKAGE_INTAKE.md` and `fixtures/package_intake.json` for the producer
contract, command, resource limits and proof boundary. Intake is not publication
approval or browser/native playback verification.
## Workflow
1. **Scaffold** a generic or project-specific bundle.
2. **Pin sources** in `source_manifest.yaml` with public/private visibility.
3. **Plan** candidate claims, assets, and a draft slide narrative.
4. **Review** `claim_ledger.yaml`; approve, reject, or qualify candidate claims.
5. **Edit** `deck.public.yaml` and any private appendix manifest.
6. **Build** an editable 16:9 PPTX with native text and shapes.
7. **Verify** claim boundaries, source coverage, required assets, and PPTX structure.
8. **Render** a PDF and contact sheet on Linux when LibreOffice and `pdftoppm` are available.
9. Import the PPTX into Google Slides for human visual tuning.
## Commands
```bash
./run.sh doctor --json
./run.sh scaffold \
--profile generic \
--output-dir /path/to/repo/docs/pitch/product
./run.sh scaffold \
--profile sparta-explorer \
--output-dir /path/to/sparta/docs/pitch/sparta-explorer
./run.sh plan \
--source-manifest docs/pitch/product/source_manifest.yaml \
--output-dir docs/pitch/product/generated
./run.sh build \
--deck docs/pitch/product/deck.public.yaml \
--claim-ledger docs/pitch/product/claim_ledger.yaml \
--source-manifest docs/pitch/product/source_manifest.yaml \
--asset-manifest docs/pitch/product/asset_manifest.yaml \
--output /mnt/storage12tb/skills/pitchdeck/outputs/product-public.pptx
./run.sh verify \
--bundle-dir docs/pitch/product \
--pptx /mnt/storage12tb/skills/pitchdeck/outputs/product-public.pptx
./run.sh emit-ui \
--bundle-dir docs/pitch/product \
--output-dir ui/public
./run.sh emit-md \
--bundle-dir docs/pitch/product \
--output-dir docs/pitch/product/md # one-way Marp export; render: npx @marp-team/marp-cli deck.md --pdf
./run.sh memory-sync \
--deck-data ui/public/deck.data.json
./run.sh render \
--pptx /mnt/storage12tb/skills/pitchdeck/outputs/product-public.pptx \
--output-dir /mnt/storage12tb/skills/pitchdeck/outputs/product-public-render
```
## Composition
Every entry is wired in code or committed configuration — this list is what the
skill actually calls, not what it could plausibly use.
| Skill | Used for | Where |
|-------|----------|-------|
| `best-practices-slide-design` | House theme, exemplars, render envelope, band asset; the design rules the compiler measures against | `design_system.py`, `design_lint.py`, `document_pptx.py`, `voice_profile.py` |
| `embedding` | Multimodal slide vectors (:8603) for visual sync; ALSO used by research-only layout retrieval, which is not in the compiler path | `visual_sync.py` |
| `memory` | Per-deck summaries and recall; the ONLY route to ArangoDB | `memory_sync.py` |
| `ux-lab` | Shared `ChatWell` powering the claim-review chat in the browser deck | `ui/src/components/DeckChat.tsx` |
| `browser-oracle` + `surf` + `ask` | Visual review of rendered slides by a browser oracle | `.ask/browser-oracles.yaml` (project `pitchdeck-review`) |
| `agentic-evals` | Seeded-defect evaluation of the design gates | `fixtures/agentic_eval.json` |
| `project-knowledge` | Shared current-state document for human + agent | `docs/PROJECT_KNOWLEDGE.md` |
| `codex` + `imagegen` | Candidate IMAGE fan-out: N theme-locked prompts generated under an OAuth session, contact-sheeted for human selection | `image_variations.py` |
**Considered and rejected, with reasons** (so the next agent does not re-litigate):
- `create-figure` — raster/Mermaid output cannot be a slide DIAGRAM, which must
stay natively editable shapes. It IS the right backend for the table lane of
`variations`, where a chart is an illustration asset like any screenshot, so
it is composed for that purpose only. `figure-lab` remains unused.
- `create-icon` — produces 72x72 Stream Deck PNGs, not vector line art.
- `tau` — the creator/reviewer loop for slide critique SHOULD run as a tau DAG
rather than hand-orchestrated subagents. Not yet wired; tracked in #1315.
Icons come from **lucide** (ISC), imported into the hash-pinned library by
`scripts/import_lucide_icons.py`. Only icons whose primitives map to native
PowerPoint objects are imported; curve-bearing icons are skipped rather than
approximated, because silently degrading a curve to a polygon would be a lie
about editability that `resolve_icon()` could not detect.
## Candidate figures and images
Two different problems, two different mechanisms — and neither uses
`/create-figure`, whose raster/Mermaid output cannot satisfy the
native-editable-shape contract.
**Diagrams and scenes are composed deterministically, not generated.** A slide's
illustration is built from the hash-pinned icon library by `scenes.py`, so every
part stays an editable PowerPoint object and nothing is invented. Candidates
come from three deterministic sources:
- **recipe alternatives** — a module may be compatible with several composition
recipes (`roadmap-gates` vs `roadmap-lanes`), each a different slide shape;
- **scene compositions** — six semantic scenes, each with its own weight and
spacing structure;
- **nearest real slides** — `find-layout` returns the top-k slides from the
author's own corpus, so a layout candidate is a slide that actually exists
rather than a guess.
**Photographic/illustrative IMAGES fan out through `imagegen`, run by `codex`
under an OAuth session** (this house has no funded API-key lane). `image-variations`
compiles a theme-locked brief from the deck's own palette — so variants do not
drift into generic AI art — emits N prompt variants across four style axes, and
contact-sheets the results for human selection. A selected image enters through
the NORMAL asset intake (magic bytes, alt text) as an ILLUSTRATION asset whose
`generation_brief` marks it for the `GENERATED_ASSET_CLAIM_SURFACE` gate: a
generated image can decorate a claim, never evidence one.
One command covers all three inputs — you do not have to know which backend fits:
```bash
./run.sh variations --prompt "an evidence thread from guidance to human review" \
--output-dir out/candidates --count 4 --execute # imagegen via codex (OAuth)
./run.sh variations --image shot.png --output-dir out/candidates --execute
./run.sh variations --table metrics.json --output-dir out/candidates \
--title "QRA corpus" --execute # create-figure: bar/hbar/pie/line
```
Without `--execute` it plans only. Every lane writes numbered candidates, a
`contact-sheet.png` to choose from, and `candidates.json` recording the exact
command behind each. `create-figure` is the correct backend for the TABLE lane
(a chart is an illustration asset, not a native slide diagram) — the rejection
above applies only to slide diagrams, which must stay editable shapes.
The deck-coupled form, when you want variations for a specific slide:
```bash
./run.sh image-variations --bundle-dir docs/pitch/product \
--slide-id 04-how-it-works --output-dir out/variations --count 4 # plan only
./run.sh image-variations ... --execute # live, via codex
```
Missing `codex`, or a failed generation, reports `NEEDS_ATTENTION` — never a
fabricated image and never a silent skip.
## shadcn primitives (ui/)
`ui/` is scaffolded as shadcn proper: `components.json`, `src/lib/utils.ts`
(`cn()` = clsx + tailwind-merge), and primitives under `src/components/ui/`.
Tailwind v4 is CSS-first, so the design tokens live in `src/index.css` rather
than a `tailwind.config.js`, and they carry the DECK's measured palette
(`--primary #076889`, `--ring #1D7694`) so primitives inherit the product look
instead of shadcn's slate defaults.
The interaction contract is enforced at the type level, not by review. On
`Button`, `data-qid`, `data-qs-action`, and `title` are REQUIRED props — a button
that no test manifest can select and no agent can drive fails to compile:
```tsx
<Button
variant="ghost" size="icon"
data-qid="deck:shortcuts:close"
data-qs-action="DECK_SHORTCUTS_CLOSE"
title="Close (Esc)"
onClick={onClose}
>
```
`useRegisterAction` deliberately stays in the CALLER's component body, at the
top, never inside the primitive: hooks must run at a component's top level, and
registering from inside `Button` would fire wherever a Button renders, including
inside `.map()`. Non-interactive primitives (`Badge`, `Card`) carry no action
contract — if a badge becomes clickable it must become a `Button`.
Verified: `verify_ui_contracts.py` PASS across 27 files, `tsc --noEmit` clean,
every changed module served 200 by the live Vite dev server (tsc and Vite resolve
imports differently, so tsc alone is not proof), and `pnpm build` succeeds.
Imports are direct (`./ui/button`), never through a barrel file.
## Three export targets, one source
| Target | Command | Nature |
|--------|---------|--------|
| **PPTX** | `emit-document-pptx` (+ `--house-template`) | native editable shapes; inherits the house theme/master/layouts |
| **PDF** | `render --pptx …` | a RENDER of the PPTX via LibreOffice, plus per-slide PNGs and a contact sheet — never an independent source |
| **React deck** | `emit-document-ui` | projects the canonical document into the payload `ui/` already loads (React 19 + Tailwind 4, lucide-react, clsx + tailwind-merge) |
All three now derive from `pitchdeck.deck_document.v1`. They did NOT before:
PPTX and static HTML compiled from the canonical document while the React app
consumed a `deck.data.json` emitted from the older bundle path, so composition
recipes, scene illustrations, template inheritance, and qualifier footers reached
PowerPoint but never the browser deck (#1264). `emit-document-ui` closes that,
and passes element geometry (bbox, z, style) plus full diagram graphs — including
scene `decoration` and `scale` — so the renderer places what the document decided
rather than re-deriving a layout.
```bash
./run.sh emit-document-pptx --document deck.document.json --output deck.pptx \
--asset-base <bundle> --house-template house.pptx
./run.sh render --pptx deck.pptx --output-dir out/render # PDF + PNGs
./run.sh emit-document-ui --document deck.document.json \
--output-dir ui/public/canonical --asset-base <bundle> # React payload
cd ui && pnpm dev # then open ?deck=./canonical/deck.data.json
```
Note on the stack: `ui/` uses Tailwind with shadcn's dependency set
(lucide-react, clsx, tailwind-merge) but is not scaffolded as shadcn — there is
no `components.json` and no `components/ui`. Adopting shadcn primitives proper is
a separate decision, not something this export implies.
Verified: 6 slides / 26 elements / 8 assets projected with zero gaps; scene
illustrations reach the React payload on the architecture and roadmap slides;
PDF renders from the templated PPTX (sha256 recorded in its receipt).
## Readiness (adversarially audited, 2026-08-08 and 2026-08-11)
Two external adversarial audits returned **NOT_READY**. Recorded here rather
than softened. Archived at `outputs/state-review-2026-08-08.md` and
`outputs/review-2026-08-11.md`.
**The publication gate is currently bypassable and the skill must not be treated
as a proving compiler.** Verified live on 2026-08-11: changing a diagram edge
label in the approved document to "Relevance always establishes support" — the
inverse of its claim — while preserving its `claim_id` and `binding_paths`, then
emitting and running `verify-publish`, returns PASS with zero findings. A
compiler-emitted AssertionAtom is an assertion BY the compiler; it is not
evidence that the assertion was legally derived. Tracked in #1371.
The first audit's blocking reason was narrower: a diagram carried one
element-level binding, so a label could reach a slide without string-level claim
proof. That
is fixed (#1328): labelled nodes require binding paths, the materializer upgrades
coarse element paths, and `verify-publish` consumes compiler-emitted
AssertionAtoms rather than a hand-maintained approvals list. The BROADER
guarantee — that each atom was legally derived and approved for that exact
occurrence — is NOT closed (#1371, #1372).
What the audit corrected in how this skill reports itself:
- **"All gates green" was never true** while `test_case13_browser_vs_libreoffice_visual_diff`
is a strict xfail. An expected failure is an acknowledged unproved requirement,
not evidence. Release status must fail on it (#1329).
- **PPTX is the only candidate publication artifact.** PDF and the React deck are
PREVIEWS until each has its own delivered-artifact verification (#1329).
- **house-conformance was validated on positive controls only.** The author's own
decks passing proves the analyzer runs, not that it discriminates house
accuracy; real negative controls are pending (#1333).
- **A corpus median is descriptive, not normative** — the cover-density finding is
advisory, not a defect to satisfy by adding a visual.
- **Authorship is not a machine gate.** Blind attribution is retained as a
diagnostic only (#1316 deprecated).
- **Test count, LOC, command count and icon count are not readiness evidence.**
Honest status: a supervised internal authoring system. A knowledgeable operator
should review every visible label and slide before external use.
## Publish verification (the final boundary)
The compiler proves claim fidelity at EMISSION. Nothing re-proved it afterwards,
so "claim-faithful" and "editable by a human" had a hole between them: any string
could be retyped before delivery and no gate would notice. `verify-publish`
closes that boundary by re-extracting evidence from the delivered file — slides,
groups, tables, notes, and package XML — instead of trusting the manifest that
produced it.
```bash
./run.sh verify-publish --pptx final-human-edited.pptx \
--ledger claim_ledger.yaml \
--approvals publish-approvals.json \
--template-contract house-template.contract.json \
--out publish-receipt.json # exit 1 on any finding
```
| Code | Refuses |
|------|---------|
| `UNCLAIMED_TEXT` | a visible string that is not an approved rendering, a legal claim excerpt, or declared chrome |
| `STALE_OWNER_MARKER` | a previous template owner's name anywhere in the package, notes and properties included |
| `NON_EDITABLE_CONTENT` | a slide flattened to imagery, whose claims no verifier can read and no human can edit |
| `TEMPLATE_DRIFT` | slides using layouts outside the approved template contract |
| `VISIBLE_CLAIM_LOSS` | text that is off-canvas, zero-sized, or truncated mid-word |
Nine mutation tests cover each code by mutating a real emitted deck the way a
person actually could. Verified live: the deck passes with 36 strings checked,
and retyping one claim into `"Search always establishes support"` is refused.
Running it caught two defects the pre-emission gates could not see: a stale owner
disclaimer surviving in 28 layouts (`presentation.slide_layouts` exposes only the
FIRST master's layouts, and this template has two), and diagram node/edge labels
reaching the render bound only at element level rather than string level.
## House-style measurement
The author's decks are invariant, so "does this match the house style?" is a
measurement rather than a judgement. Measured over a 263-slide corpus: 100%
carry a header band, bottom-left mark, bottom-right footer text, and a title;
261/263 bands are `#076889`; the median slide carries 2 pictures and 8 shapes.
```bash
./run.sh measure-house-spec --decks /path/to/real/decks --output house_spec.json
./run.sh house-conformance --pptx out/deck.pptx # exit 1 on findings
./run.sh compile-voice-profile --corpus ../best-practices-slide-design
./run.sh index-house-slides --decks /path/to/decks --renders /path/to/renders
./run.sh find-layout --query "closing slide: what must happen before deployment"
```
`house-conformance` is validated against POSITIVE controls only: it passes the author's own
decks, and it caught two of its own definition errors that way (chrome
inherited from the slide layout, and a bottom-left mark that is a logo row
rather than text).
عرض على GitHub