| name | deck-review |
| description | Scores and strengthens startup pitch decks (pre-seed through Series A) against 35 investor-grade criteria grounded in Sequoia, DocSend, YC, a16z, and Carta data. |
| when_to_use | Use ONLY when the user has attached a pitch deck file (PDF, PPTX, markdown, or pasted slide text describing slide-by-slide content) AND has asked for review, scoring, feedback, or critique of the deck. Do not auto-invoke on general fundraising or pitch questions; use ONLY when there is actual deck content to review.
|
| user-invocable | true |
Deck Review Skill
Help startup founders strengthen their pitch decks before sending them to investors. Produce a structured, scored review with specific, actionable recommendations grounded in current best practices from Sequoia, DocSend, YC, a16z, and Carta data. The tone is founder-first: a candid coaching session, not a VC evaluation.
Skill Metadata
- Author: lool-ventures
- Version: managed in
founder-skills/.claude-plugin/plugin.json
- Compatibility: Python 3.10+ and
uv for script execution.
- Exports:
checklist.json → financial-model-review, ic-sim, fundraise-readiness (future)
Skill Execution Model (READ FIRST)
See founder-skills/references/skill-execution-model.md for the full inline-skill execution model (3 dispatch contexts, Mitigation 1+2, producer contract, Cowork quirks, per-symptom triage).
This skill runs inline in the main thread, not as a sub-agent — see the reference above ("Why Inline (Not Forked Sub-Agent)") for the rationale. Sub-agents are deliberately shell-free, so orchestration (producer scripts, artifact persistence) stays in the main thread.
Two dispatch contexts for the sub-agent:
- Context A — Per-step analytical dispatch (Mitigation 1): Steps 4 and 5 dispatch the deck-review agent via the
Task tool. The agent does deep analysis, WRITES its output JSON to the OUTPUT_PATH given in its prompt (the handoff/ dir), and returns a small receipt. The main thread gates the file with check_handoff.py, then pipes it through the producer script (slide_reviews.py or checklist.py). The sub-agent never writes canonical artifacts — only its hand-off file.
- Context B — Post-compose coaching dispatch: Step 7 dispatches the sub-agent after
compose_report.py writes report.md. The sub-agent reads the coaching_payload inlined in the dispatch prompt (Mitigation 2) — it does NOT Read the full report.md — composes the coaching commentary, WRITES it to the OUTPUT_PATH hand-off file, and returns a small receipt. The main thread gates the file (check_handoff.py) and inserts it via the shared insert_coaching.py script (idempotency matrix, uuid-marker replacement, run_id-parity verification — all deterministic). See the reference above for the full Context B contract.
Tolerant JSON extraction protocol (Context B returns; also the Context A message-channel fallback): capture the sub-agent's final assistant message. It should be raw JSON, but may be wrapped in ```json ... ``` fences or carry a prose preamble. Extract tolerantly:
- If the message is wrapped in a
```json ... ``` (or plain ``` ... ```) fence, strip the fence first.
- Try to parse the stripped text directly as JSON.
- If that fails, walk through the text looking for the first
{ character and try json.JSONDecoder().raw_decode(text[i:]) — this is brace-aware and handles nested objects correctly (unlike regex, which truncates on the first }).
- If extraction fails entirely, re-prompt the sub-agent with: "Your previous reply could not be parsed as JSON. Return ONLY the JSON object — no markdown fences, no prose preamble."
Context A receipts don't need this protocol by hand — check_handoff.py --receipt-json - applies the same tolerant extraction internally; pass the final message verbatim.
Input Formats
Accept any format: PDF, PowerPoint (PPTX), markdown, or text descriptions of slides.
Available Scripts
All scripts are at ${CLAUDE_PLUGIN_ROOT}/skills/deck-review/scripts/:
setup_run.py — Resolves REVIEW_DIR, detects resume vs. fresh run, cleans stale artifacts (--clean)
deck_inventory.py — Producer for deck_inventory.json (agent provides JSON via stdin; schema-validated)
stage_profile.py — Producer for stage_profile.json; --rebuild-stage + --confidence {high,low} for founder-corrected stages
gate_state.py — Producer (emit) + answer-writer (answer) for the stage-confirmation gate
slide_reviews.py — Producer for slide_reviews.json (agent provides JSON via stdin; schema-validated)
checklist.py — Scores 35 criteria across 7 categories (pass/fail/warn/not_applicable)
compose_report.py — Assembles artifacts into final report with cross-artifact validation; --strict exits 1 on high/medium warnings
visualize.py — Generates self-contained HTML with SVG charts (not JSON)
Also available from ${CLAUDE_PLUGIN_ROOT}/scripts/ (shared):
founder_context.py — Per-company context management (init/read/merge/validate)
Run with: python3 ${CLAUDE_PLUGIN_ROOT}/skills/deck-review/scripts/<script>.py --pretty [args]
Available References
Read as needed from ${CLAUDE_PLUGIN_ROOT}/skills/deck-review/references/:
deck-best-practices.md — Full best practices: slide frameworks, stage-specific guidelines, design rules, AI-company requirements
checklist-criteria.md — Definitions for all 35 criteria with pass/fail/warn thresholds
artifact-schemas.md — JSON schemas for all artifacts
Artifact Pipeline
Every review deposits structured JSON artifacts into a working directory. The final step assembles all artifacts into a report and validates consistency. This is not optional.
| Step | Artifact | Producer |
|---|
| 1 | founder context | founder_context.py read/init |
| 2 | deck_inventory.json | deck_inventory.py (agent provides JSON via stdin) |
| 3 | stage_profile.json | stage_profile.py (agent provides JSON via stdin) |
| 4 | slide_reviews.json | slide_reviews.py (agent provides JSON via stdin) |
| 5 | checklist.json | checklist.py |
| 6 | Report | compose_report.py (writes both report.json and report.md) |
Rules:
- Deposit each artifact before proceeding to the next step
- For producer-script artifacts (Steps 2-4), the agent supplies JSON on stdin and the script schema-validates against
references/schemas/<artifact>.schema.json. Never write artifacts directly via Write or Edit — always pipe through the producer script so metadata.run_id is injected and the schema is enforced.
- If a step is not applicable, deposit a stub:
{"skipped": true, "reason": "..."}
Keep the founder informed with brief, plain-language updates at each step. Never mention file names, scripts, or JSON. After each analytical step (4–5), share a one-sentence finding before moving on.
Workflow
Step 0: Path Setup
SCRIPTS="${CLAUDE_PLUGIN_ROOT}/skills/deck-review/scripts"
if [ ! -d "$SCRIPTS" ]; then
SCRIPTS="$(find /sessions -type d -path '*/skills/deck-review/scripts' 2>/dev/null | head -1)"
fi
if [ -z "$SCRIPTS" ] || [ ! -d "$SCRIPTS" ]; then
SCRIPTS="$(find / -type d -path '*/skills/deck-review/scripts' 2>/dev/null | head -1)"
fi
PLUGIN_ROOT="${SCRIPTS%/skills/*}"
REFS="$PLUGIN_ROOT/skills/deck-review/references"
SHARED_SCRIPTS="$PLUGIN_ROOT/scripts"
python3 "$SHARED_SCRIPTS/resolve_artifacts_root.py"
RUN_ID="${RUN_ID:-$(date -u +%Y%m%dT%H%M%SZ)}"
The Step 0 block self-heals when ${CLAUDE_PLUGIN_ROOT} doesn't resolve (Cowork). If it still comes up empty, locate the anchor manually: find / -path '*/skills/deck-review/scripts/checklist.py' 2>/dev/null | head -5 and derive the variables from it.
After Step 1 (when the slug is known) — substitute $SLUG below with the company slug from Step 1's printed JSON, then call setup_run.py to resolve REVIEW_DIR, detect whether this is a resume, and clean stale state in one atomic step. Always call setup_run.py with --clean and --run-id "$RUN_ID"; do not pre-read gate_state.json yourself. setup_run.py decides resume vs. fresh by comparing the answered gate_state.json's run_id against --run-id, and on a fresh (non-resume) run it deletes a stale answered gate_state.json so a prior completed run cannot be misread as a resume:
python3 "$SCRIPTS/setup_run.py" \
--artifacts-root "$ARTIFACTS_ROOT" \
--slug "$SLUG" \
--run-id "$RUN_ID" \
--clean \
--pretty
Read review_dir, run_id, resume, and gate_answer from the JSON printed by the previous Bash command. Substitute REVIEW_DIR with the review_dir value, RUN_ID with the run_id value, and IS_RESUMING with 1 if resume is true, else empty, in every subsequent bash block. Then:
HANDOFF_DIR="$REVIEW_DIR/handoff/$RUN_ID"
mkdir -p "$HANDOFF_DIR"
python3 "$SHARED_SCRIPTS/resolve_artifacts_root.py" --agent
HANDOFF_AGENT="<printed AGENT_ARTIFACTS_ROOT>/<basename of REVIEW_DIR>/handoff/$RUN_ID"
REVIEW_DIR_AGENT="<printed AGENT_ARTIFACTS_ROOT>/<basename of REVIEW_DIR>"
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/deck-review-${SLUG:-deck}.staging.XXXXXX")"
To resume across a gate round-trip, the caller's task prompt must supply the prior RUN_ID (so RUN_ID above is set before this block runs). Then setup_run.py sees the answered gate_state.json whose run_id matches and returns resume: true — and because resume is true, --clean leaves gate_state.json, deck_inventory.json, and stage_profile.json in place (they are same-run checkpoints for this RUN_ID).
Pass RUN_ID to every producer script via --run-id. Producer scripts inject it into metadata.run_id automatically. compose_report.py enforces that all required artifacts share the same run_id and emits a MISSING_METADATA (high) warning for any artifact without one. Keeping RUN_ID stable across the gate is what prevents a STALE_ARTIFACT mismatch with the pre-gate artifacts.
On re-invocation ($IS_RESUMING is set): gate_state.json, deck_inventory.json, and stage_profile.json all survive --clean (same-run artifacts are preserved on resume). Skip Steps 2 and 3 if both deck_inventory.json and stage_profile.json exist and their metadata.run_id matches $RUN_ID; otherwise re-run them with the same RUN_ID.
Step 1: Read or Create Founder Context
python3 "$SHARED_SCRIPTS/founder_context.py" read --artifacts-root "$ARTIFACTS_ROOT" --pretty
Exit 0 (found): Use the company slug and pre-filled fields. Proceed to Step 2.
Exit 1 (not found): This is normal for a first run — do not treat it as an error. Use AskUserQuestion (NOT plain chat) to ask for company name, stage, sector, and geography. Provide at least 2 options. Then create:
python3 "$SHARED_SCRIPTS/founder_context.py" init \
--company-name "Acme Corp" --stage seed --sector "B2B SaaS" \
--geography "US" --artifacts-root "$ARTIFACTS_ROOT" \
--run-id "$RUN_ID"
Exit 2 (multiple): Present the list, ask which company, re-read with --slug.
Step 2: Ingest Deck -> deck_inventory.json
Ingestion pitfalls — common issues that degrade review quality:
- PDF image-only slides: Some PDFs embed slides as images with no extractable text. If Read returns blank or garbled content, note
input_quality: "image_only" in deck_inventory.json and base the review on visual description + OCR-level best effort. Flag reduced confidence in coaching commentary.
- PPTX speaker notes vs. slide content: Speaker notes often contain the real narrative; slide text is abbreviated. Extract both — notes go into
content_summary, slide text into headline. Do not discard notes.
- Multi-file submissions: Founder sends v1 + v2, or deck + appendix as separate files. Ask which is the primary deck before proceeding. Do not merge or review both simultaneously.
- Partial decks: Deck has fewer than 5 slides or is clearly a subset. Proceed but set
confidence: "low" in stage_profile and note the limitation. Missing-slides detection still runs normally.
- Wrong file type: File named
.pdf but is actually a Word doc or image. If Read fails, try alternate format before asking the founder for a re-upload.
Read the provided deck. For each slide, extract: headline, content summary, visuals description, word count estimate. Also determine ai_company_status using the two sub-questions below. Then write the inventory through the producer script:
AI company classification (mandatory — field is required): Answer two sub-questions:
- Does the deck make an AI claim? (tagline, "AI-native"/"AI-powered" positioning, or AI in the product description)
- Is there evidence AI is core? Use ALL FOUR signals: ML in value prop / inference-or-training in COGS / foundation-model or fine-tuning mentions / AI-specific retention metrics.
Map to ai_company_status:
- Evidence present (any core-AI signal) →
"ai_core"
- AI claim but no core-AI evidence →
"ai_claimed_unverified"
- No AI claim and not AI →
"not_ai"
Record what evidence or claim was found in ai_evidence (required for ai_core and ai_claimed_unverified; brief for not_ai).
cat <<'INVENTORY_EOF' | python3 "$SCRIPTS/deck_inventory.py" --run-id "$RUN_ID" -o "$REVIEW_DIR/deck_inventory.json" --pretty
{
"company_name": "...",
"review_date": "YYYY-MM-DD",
"input_format": "pdf",
"total_slides": 12,
"claimed_stage": "...",
"claimed_raise": "...",
"ai_company_status": "...",
"ai_evidence": "...",
"slides": [
{"number": 1, "headline": "...", "content_summary": "...", "visuals": "...", "word_count_estimate": 15}
]
}
INVENTORY_EOF
The script validates against references/schemas/deck_inventory.schema.json and injects metadata.run_id. Never write deck_inventory.json directly via heredoc — the schema-validation gate is what keeps the pipeline honest.
Step 3: Detect Stage -> stage_profile.json
Determine pre-seed/seed/series-a from signals in the deck. Read references/deck-best-practices.md for stage-specific frameworks. Record: detected stage, confidence, evidence, whether AI company, expected slide framework, stage benchmarks.
Stage signals: Pre-seed: no revenue, LOIs/waitlist, prototype, <$2.5M ask. Seed: early ARR, paying customers, <$6M ask. Series A: $1M+ ARR, cohort data, repeatable GTM, $10M+ ask. Later-stage: set detected_stage to "series_b" or "growth" — use the Gate below. Do not ask outside the gate.
AI company note: ai_company_status was determined in Step 2 (deck inventory) and is already in deck_inventory.json. Set is_ai_company in stage_profile.json to true if ai_company_status is "ai_core" or "ai_claimed_unverified", otherwise false. Record the same evidence in ai_evidence.
Then write the profile through the producer script:
cat <<'PROFILE_EOF' | python3 "$SCRIPTS/stage_profile.py" --run-id "$RUN_ID" -o "$REVIEW_DIR/stage_profile.json" --pretty
{
"detected_stage": "seed",
"confidence": "high",
"evidence": ["Claims $2M ARR", "..."],
"is_ai_company": false,
"ai_evidence": "...",
"expected_framework": ["..."],
"stage_benchmarks": {"round_size_range": "...", "expected_traction": "...", "runway_expectation": "..."},
"reference_file_read": ["deck-best-practices.md", "checklist-criteria.md", "artifact-schemas.md"]
}
PROFILE_EOF
Gate: Confirm Stage and Scope
Sub-agent execution model: sub-agents in Cowork cannot reliably call AskUserQuestion. The gate uses a checkpoint-and-resume pattern — the sub-agent writes a gate_state.json to disk and emits a structured needs_input payload as its final message. The parent (main thread or invoking agent) calls AskUserQuestion if available — or otherwise asks the founder via plain text — then writes the answer back into gate_state.json with gate_state.py answer (use exactly this flag shape):
python3 "$SCRIPTS/gate_state.py" answer \
--file "$REVIEW_DIR/gate_state.json" \
--run-id "$RUN_ID" \
--answer "<the founder's chosen option, verbatim>"
then re-invokes this sub-agent. (--file, --run-id, --answer; -o/--output are accepted as aliases for --file, and --run-id is checked for parity against the gate's metadata.run_id.) The sub-agent detects re-invocation by checking whether gate_state.json already has an answer field. (The plain-text round-trip works correctly even without AskUserQuestion.)
How to detect re-invocation: you were re-invoked if $REVIEW_DIR/gate_state.json exists, has a non-empty answer, AND its metadata.run_id matches $RUN_ID. The run_id comparison is what distinguishes a genuine gate resume from a stale answered gate left by a prior completed run for the same company. (setup_run.py already deletes that stale file on a fresh run, but verify here as well.) Skip the gate-emit and read the answer:
python3 -c 'import json,sys
g=json.load(open(sys.argv[1]))
print(g.get("answer","") if g.get("metadata",{}).get("run_id","")==sys.argv[2] else "")' "$REVIEW_DIR/gate_state.json" "$RUN_ID" 2>/dev/null || true
If the previous Bash command printed a non-empty value, that is the gate answer — jump to "After the gate" below.
Otherwise, write gate_state.json via the producer script and emit a needs_input payload:
cat <<'GATE_EOF' | python3 "$SCRIPTS/gate_state.py" emit --run-id "$RUN_ID" -o "$REVIEW_DIR/gate_state.json" --pretty
{
"gate_id": "stage_confirmation",
"question": "Does this stage detection look right?",
"options": ["Looks right", "Different stage", "Not sure — proceed anyway"],
"context_summary": "Detected stage: Seed (high confidence). Key evidence: $4.2M ARR, 3 paying enterprise customers, $5M raise ask. AI company: Yes — inference costs in COGS. Expected framework: Sequoia seed (12-15 slides). Slides in deck: 14."
}
GATE_EOF
The script schema-validates the body and injects metadata.run_id. Never write gate_state.json directly via heredoc.
Then return — as your final assistant message — a JSON object the parent agent can act on:
{
"needs_input": {
"gate_state_path": "<absolute path to gate_state.json>",
"question": "Does this stage detection look right?",
"options": ["Looks right", "Different stage", "Not sure — proceed anyway"],
"context_summary": "..."
},
"review_dir": "<REVIEW_DIR>",
"run_id": "<RUN_ID>"
}
For out-of-scope stages (series_b, growth): use gate_id: "out_of_scope_choice", question "This looks out of scope. What should I do?", options ["Stop review", "Different stage", "Proceed anyway (best-effort)"].
After the gate (when the gate-check Bash command printed a non-empty answer): branch on the printed value:
-
Looks right: proceed to Step 4 with the detected stage.
-
Different stage: emit a second gate (gate_id stage_choice) via gate_state.py emit to ask which stage. Treat this as a fresh gate — return a new needs_input payload and let the parent answer it the same way. When that one comes back answered, rebuild the profile for the chosen stage at high confidence (the founder explicitly picked it), then re-emit the original stage_confirmation gate to confirm:
cp "$REVIEW_DIR/stage_profile.json" "$STAGING_DIR/sp.json"
cat "$STAGING_DIR/sp.json" | python3 "$SCRIPTS/stage_profile.py" \
--rebuild-stage <chosen> --confidence high \
--run-id "$RUN_ID" -o "$REVIEW_DIR/stage_profile.json"
-
Not sure — proceed anyway: proceed with the detected stage at low confidence:
cp "$REVIEW_DIR/stage_profile.json" "$STAGING_DIR/sp.json"
cat "$STAGING_DIR/sp.json" | python3 "$SCRIPTS/stage_profile.py" \
--rebuild-stage <detected> --confidence low \
--run-id "$RUN_ID" -o "$REVIEW_DIR/stage_profile.json"
-
Stop review (out-of-scope): exit. Do not run later steps.
-
Proceed anyway (best-effort): rebuild the profile at low confidence with --rebuild-stage series_a --confidence low (same staged-stdin invocation as above).
stage_profile.py requires --run-id and -o, and reads the existing profile from stdin. Stage the current stage_profile.json to $STAGING_DIR/sp.json first and pipe that in — never cat and -o the same file in one command, which races and truncates it.
Context A hand-off protocol (file transport + gate)
Every Context A dispatch prompt carries an OUTPUT_PATH: line built from $HANDOFF_AGENT. The
sub-agent WRITES its output JSON to that path with its Write tool and returns only a small receipt:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}. The payload leaves the model
exactly once (into the Write call) — never re-type sub-agent JSON into a heredoc.
Path idiom for dispatch prompts (host-loop path gate): OUTPUT_PATH and any under-outputs artifact
READ path a sub-agent is given are relative to the sub-agent's file-tool cwd (the outputs mount) —
built from the resolve_artifacts_root.py --agent namespace ($HANDOFF_AGENT / $REVIEW_DIR_AGENT).
Never hand a sub-agent an absolute /sessions/... path for a file-tool Read/Write — the host-loop path
gate denies it (steering shell work to the bash tool instead). Bundled references/*.md are the one
exception: pass them as the literal ${CLAUDE_PLUGIN_ROOT}/skills/deck-review/references/... token (it is
pre-resolved to a host-readable path); do NOT substitute a find /sessions-discovered $REFS (a shell
path a file tool can't read).
After EVERY Context A dispatch, gate before piping (<step> = the dispatch's file stem):
printf '%s' '<agent final message verbatim>' | \
python3 "$SHARED_SCRIPTS/check_handoff.py" "$HANDOFF_DIR/<step>_output.json" \
--agent-path "$HANDOFF_AGENT/<step>_output.json" --receipt-json -
Branch on the exit code (complete state machine — do not improvise):
- Exit 0 → pipe the file through the producer:
cat "$HANDOFF_DIR/<step>_output.json" | python3 "$SCRIPTS/<producer>.py" ...
- Exit 3 (missing/empty file — receipt may be fabricated) → redo-dispatch: fresh Task, same prompt plus one line: "your receipt claimed a file at
<path> but none exists; use Write to create exactly that path."
- Exit 4 (file exists, invalid JSON) → repair-dispatch: fresh Task: "Read
<OUTPUT_PATH>; it fails JSON parsing with <verbatim detail from the diagnostic>; fix and rewrite it; return the receipt."
- Exit 5 (receipt echoes a different path) → repair-dispatch telling the agent the exact expected OUTPUT_PATH (it wrote somewhere else).
- Exit 6 (receipt unparseable / no
output_path key) → redo-dispatch with "return ONLY the receipt JSON — no fences, no prose."
- Producer schema rejection (the pipe fails next) → repair-dispatch with the producer's stderr verbatim.
- Any other exit (script crash etc.) → STOP with the stderr.
- After ANY corrective dispatch, resume from
check_handoff.py — never pipe to the producer unchecked.
Retry budget: max 2 corrective dispatches per step, of any kind, in any combination (max 3
total dispatches). After the second corrective dispatch fails any gate: STOP and report the exact
diagnostic to the founder. The main thread MUST NOT author or patch analytical content itself —
filling in the JSON is the fabrication failure mode this architecture exists to prevent. A
status: "blocked" return is not a gate retry, but it is bounded: at most ONE input-fix
re-dispatch per step; a second blocked return STOPs with both reasons quoted.
Graceful degrade (fleet heterogeneity): if the FIRST corrective dispatch also exits 3 while the
agent's receipt claims complete with the correctly echoed path, treat the host's filesystem
topology as hand-off-incompatible: fall back to message-channel transport for the REST of this run
(sub-agent returns full JSON in its final message; apply the tolerant JSON extraction protocol;
stage to $STAGING_DIR/<step>_input.json; same producer pipe), and note the fallback in your
final summary.
Retries overwrite the same OUTPUT_PATH (the mount is write-allowed / delete-denied — never rm
under $REVIEW_DIR). Hand-off files are not canonical artifacts: producers consume them only via
the explicit pipe, and compose_report.py never reads handoff/.
Ad-hoc scratch (NOT sub-agent hand-off) still goes to $STAGING_DIR in /tmp — see the reference
(founder-skills/references/skill-execution-model.md). Hard rule: never stage scratch under
$REVIEW_DIR, and never delete anything under it.
Step 4: Review Each Slide -> slide_reviews.json (Context A dispatch)
Dispatch the deck-review sub-agent in Context A (SLIDE_REVIEWS). Do not do the slide analysis yourself in the main thread — dispatch it. Call the Task tool with subagent_type: "founder-skills:deck-review" and the prompt below, so the analysis runs in the scoped agent (its tools: allowlist binds; a type-less dispatch falls back to the wildcard general-purpose agent).
Before dispatching, substitute placeholders in the template below: replace <HANDOFF_AGENT> and <REVIEW_DIR_AGENT> with the agent-namespace values (from resolve_artifacts_root.py --agent — relative paths the sub-agent's file tools resolve against the outputs mount; NOT absolute /sessions/... paths, which the host-loop gate denies), <RUN_ID> with $RUN_ID, and inline the deck's full text under DECK: (the sub-agent needs verbatim figures — do not point it at an upload path). Leave the ${CLAUDE_PLUGIN_ROOT}/... reference paths literal — they are pre-resolved to a host-readable path. The sub-agent has no access to your shell variables.
Dispatch prompt template:
CONTEXT: SLIDE_REVIEWS
OUTPUT_PATH: <HANDOFF_AGENT>/slide_reviews_output.json
RUN_ID: <RUN_ID>
You are the deck-review agent dispatched in Context A (SLIDE_REVIEWS). The deck's
full text is inlined below under DECK. Read the stage profile at
<REVIEW_DIR_AGENT>/stage_profile.json. Compare each slide against the stage-specific
framework and non-negotiable principles from
${CLAUDE_PLUGIN_ROOT}/skills/deck-review/references/deck-best-practices.md and
${CLAUDE_PLUGIN_ROOT}/skills/deck-review/references/checklist-criteria.md.
DECK:
<inline the deck's full extracted text here — verbatim, all slides>
For each slide: identify strengths, weaknesses, and specific recommendations.
Map to expected framework. Flag missing expected slides. Every critique must
cite a specific best-practice principle. When you reference deck figures, quote
them verbatim from the deck content — do not paraphrase or round numbers,
percentages, dates, or named metrics.
Use your Write tool to write to OUTPUT_PATH exactly the shape expected by
slide_reviews.py (no metadata block; the producer script adds it):
{
"reviews": [
{"slide_number": 1, "maps_to": "...", "strengths": ["..."],
"weaknesses": ["..."], "recommendations": ["..."],
"best_practice_refs": ["..."]}
],
"missing_slides": [
{"expected_type": "...", "importance": "important", "recommendation": "..."}
],
"overall_narrative_assessment": "..."
}
Then return ONLY the receipt JSON in your final assistant message:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}
Do NOT write any file other than OUTPUT_PATH — canonical artifacts are
producer-script-only; anything else you write bypasses schema validation and
run_id stamping.
After the sub-agent returns: gate the hand-off per the Context A hand-off protocol, then pipe:
cat "$HANDOFF_DIR/slide_reviews_output.json" | \
python3 "$SCRIPTS/slide_reviews.py" --run-id "$RUN_ID" -o "$REVIEW_DIR/slide_reviews.json" --pretty
Step 5: Score Checklist -> checklist.json (Context A dispatch)
Dispatch the deck-review sub-agent in Context A (CHECKLIST). Call the Task tool with subagent_type: "founder-skills:deck-review" and the prompt below. Substitute <HANDOFF_AGENT> / <REVIEW_DIR_AGENT> with the agent-namespace values and <RUN_ID> with $RUN_ID; leave the ${CLAUDE_PLUGIN_ROOT}/... reference path literal (same idiom as SLIDE_REVIEWS).
Dispatch prompt template:
CONTEXT: CHECKLIST
OUTPUT_PATH: <HANDOFF_AGENT>/checklist_output.json
RUN_ID: <RUN_ID>
You are the deck-review agent dispatched in Context A (CHECKLIST). Evaluate all
35 criteria from ${CLAUDE_PLUGIN_ROOT}/skills/deck-review/references/checklist-criteria.md
using the deck content (read from <REVIEW_DIR_AGENT>/slide_reviews.json for
reference), the stage profile at <REVIEW_DIR_AGENT>/stage_profile.json, and the
deck inventory at <REVIEW_DIR_AGENT>/deck_inventory.json.
Assess ALL 35 criteria including the 4 AI criteria — do NOT self-gate.
`checklist.py` applies deterministic AI-criteria gating from `deck_inventory.json`
after you return — assess every criterion.
Evidence quality rules:
- Every fail and warn MUST cite a specific best-practice principle or benchmark.
- Every pass MUST note what was checked.
- not_applicable items MUST include a reason.
Evaluate every one of these 35 criteria — one item per id, no omissions, no
invented ids. Grouped by category:
Narrative Flow:
- purpose_clear
- headlines_carry_story
- narrative_arc_present
- strongest_proof_early
- story_stands_alone
Slide Content:
- problem_quantified
- solution_shows_workflow
- why_now_has_catalyst
- market_bottom_up
- competition_honest
- business_model_clear
- gtm_has_proof
- team_has_depth
Stage Fit:
- stage_appropriate_structure
- stage_appropriate_traction
- stage_appropriate_financials
- ask_ties_to_milestones
- round_size_realistic
Design & Readability:
- one_idea_per_slide
- minimal_text
- slide_count_appropriate
- consistent_design
- mobile_readable
Common Mistakes:
- no_vague_purpose
- no_nice_to_have_problem
- no_hype_without_proof
- no_features_over_outcomes
- no_dodged_competition
AI Company (score all 4; the producer script gates them after you return):
- ai_retention_rebased
- ai_cost_to_serve_shown
- ai_defensibility_beyond_model
- ai_responsible_controls
Diligence Readiness:
- numbers_consistent
- data_room_ready
- contact_info_present
When you cite deck figures in evidence, quote them verbatim from the deck
content — do not paraphrase or round numbers, percentages, dates, or named
metrics.
Use your Write tool to write to OUTPUT_PATH the items array without a summary
(the producer script computes the summary):
{"items": [{"id": "purpose_clear", "status": "pass", "evidence": "...", "notes": "..."}, ...all 35 items...]}
Then return ONLY the receipt JSON in your final assistant message:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}
Do NOT write any file other than OUTPUT_PATH — canonical artifacts are
producer-script-only; anything else you write bypasses schema validation and
run_id stamping.
After the sub-agent returns: gate the hand-off per the Context A hand-off protocol, then pipe through the producer script (with --inventory so the producer applies deterministic AI-criteria gating from deck_inventory.json):
cat "$HANDOFF_DIR/checklist_output.json" | python3 "$SCRIPTS/checklist.py" --run-id "$RUN_ID" --pretty \
--inventory "$REVIEW_DIR/deck_inventory.json" \
-o "$REVIEW_DIR/checklist.json"
Step 6: Compose Report
python3 "$SCRIPTS/compose_report.py" --dir "$REVIEW_DIR" --pretty \
-o "$REVIEW_DIR/report.json" \
--write-md "$REVIEW_DIR/report.md"
compose_report.py writes both report.json and report.md deterministically. Do NOT read report_markdown out of report.json and re-write it via heredoc — heredoc re-writing can corrupt report.json. Compose owns the file outputs.
Fix high-severity warnings and re-run. Use --strict to enforce a clean report.
Post-write verification: compose_report.py exits non-zero (code 2) if the declared output files don't exist or are empty after writing. If compose exits non-zero, stop and report the exact stderr — do not proceed to Step 7.
Step 7: Post-Compose Coaching Commentary (Context B dispatch — POST_COMPOSE_COACHING)
Dispatch the deck-review sub-agent in Context B. Call the Task tool with subagent_type: "founder-skills:deck-review" after compose_report.py has successfully written both report.json and report.md.
Mitigation 2 protocol: the main thread reads the structured coaching_payload from report.json and inlines it into the dispatch prompt. The sub-agent does NOT Read full report.md — it consumes coaching_payload directly, composes the coaching commentary, and WRITES it to the OUTPUT_PATH hand-off file with its Write tool, returning only a small receipt (the same file transport as Context A — the commentary leaves the model exactly once, into the Write call; the main thread never re-types it). The main thread gates that file with check_handoff.py, then inserts it via the shared insert_coaching.py script (idempotency matrix, uuid-marker replacement, run_id-parity verification — all deterministic). See the deck-review agent body's "Context B — Post-compose coaching dispatch (POST_COMPOSE_COACHING)" section for the full procedure.
python3 -c '
import json, sys
data = json.load(open(sys.argv[1]))
print(json.dumps(data["coaching_payload"], indent=2))
' "$REVIEW_DIR/report.json"
The payload prints to stdout — copy it from the tool result into the dispatch
prompt below. (Never capture it into a shell variable: each Bash call runs in a
fresh shell, so the variable would be unreadable and gone.)
Dispatch prompt template (substitute <HANDOFF_AGENT> with the Step-0 agent-namespace value — the same rule as every Context A dispatch; the sub-agent has no shell vars, so paste the printed value):
CONTEXT: POST_COMPOSE_COACHING
OUTPUT_PATH: <HANDOFF_AGENT>/coaching_commentary_output.json
You are dispatched to add coaching commentary to a deck review.
The compose_report.py script has finished. The structured `coaching_payload`
from report.json is:
<paste the coaching_payload JSON printed by the previous Bash command here verbatim>
Follow your agent body's Context B procedure (POST_COMPOSE_COACHING):
1. Compose commentary from the inlined coaching_payload (failed_items,
warned_items, summary, high_severity_warnings, stage, ai_company_status).
Do NOT Read the full report.md. Do NOT edit report.md or any canonical artifact.
2. Use your Write tool to write to OUTPUT_PATH exactly:
{"commentary_markdown": "<your coaching commentary — markdown body text, WITHOUT
a '## Coaching Commentary' heading and WITHOUT the insertion_marker string>"}
Do NOT write any file other than OUTPUT_PATH — insertion into report.md is the
main thread's job, via the shared insert_coaching.py script.
3. Return:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}
OR, if the payload is unusable (write no file):
{"status": "blocked", "reason": "<specific gap>"}
Stop after returning the receipt JSON. Do not narrate.
After the sub-agent returns: if its final message is a {"status": "blocked", "reason": ...} object, stop and report the reason to the founder — do not run the gate. Otherwise gate the hand-off, then (on gate exit 0) insert deterministically. The commentary leaves the model exactly once (into the sub-agent's Write call) — NEVER re-type the sub-agent's JSON into a heredoc or a python -c argument.
SHARED_SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
if [ ! -d "$SHARED_SCRIPTS" ]; then
DR_SCRIPTS="$(find /sessions -type d -path '*/skills/deck-review/scripts' 2>/dev/null | head -1)"
[ -n "$DR_SCRIPTS" ] || DR_SCRIPTS="$(find / -type d -path '*/skills/deck-review/scripts' 2>/dev/null | head -1)"
SHARED_SCRIPTS="${DR_SCRIPTS%/skills/*}/scripts"
fi
printf '%s' '<agent final message verbatim>' | \
python3 "$SHARED_SCRIPTS/check_handoff.py" "$HANDOFF_DIR/coaching_commentary_output.json" \
--agent-path "$HANDOFF_AGENT/coaching_commentary_output.json" --receipt-json -
On gate exit 0, insert the gated hand-off FILE (feed the file, never re-type the message):
SHARED_SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
if [ ! -d "$SHARED_SCRIPTS" ]; then
DR_SCRIPTS="$(find /sessions -type d -path '*/skills/deck-review/scripts' 2>/dev/null | head -1)"
[ -n "$DR_SCRIPTS" ] || DR_SCRIPTS="$(find / -type d -path '*/skills/deck-review/scripts' 2>/dev/null | head -1)"
SHARED_SCRIPTS="${DR_SCRIPTS%/skills/*}/scripts"
fi
python3 "$SHARED_SCRIPTS/insert_coaching.py" \
--report "$REVIEW_DIR/report.md" \
--marker '<EXACT insertion_marker string from report.json coaching_payload>' \
--commentary-file "$HANDOFF_DIR/coaching_commentary_output.json" \
--verify-artifact "$REVIEW_DIR/deck_inventory.json" \
--verify-artifact "$REVIEW_DIR/stage_profile.json" \
--verify-artifact "$REVIEW_DIR/slide_reviews.json" \
--verify-artifact "$REVIEW_DIR/checklist.json"
The gate (check_handoff.py) verifies the sub-agent's hand-off file exists, parses, and matches the receipt's echoed path; on gate exit 0, insert_coaching.py performs the 6-state idempotency check, replaces the marker with ## Coaching Commentary + the commentary in a single in-place write, and verifies run_id parity across all 4 producer artifacts. Branch on the exit code (complete state machine — do not improvise):
- Exit 0 from the chain —
insert_coaching.py's receipt on stdout says inserted (or already_inserted on a resume). Present report_path to the founder and proceed.
check_handoff.py exit 3 (missing/empty file — receipt may be fabricated) → redo-dispatch: fresh Task, same prompt plus one line: "your receipt claimed a file at <path> but none exists; use Write to create exactly that path."
- Exit 4 (file exists, invalid JSON) → repair-dispatch: fresh Task: "Read
<OUTPUT_PATH>; it fails JSON parsing with <verbatim detail from the diagnostic>; fix and rewrite it; return the receipt."
- Exit 5 (receipt echoes a different path) → repair-dispatch telling the agent the exact expected OUTPUT_PATH.
- Exit 6 (receipt unparseable / no
output_path key) → redo-dispatch with "return ONLY the receipt JSON — no fences, no prose." (A status: "blocked" final message is NOT exit 6 — it was handled before the gate.)
insert_coaching.py exit 1 (blocked; stdout carries {"status": "blocked", "reason": ...}) → stop and report the exact reason. Do NOT hand-edit report.md — if the reason mentions a truncated report or a missing marker, re-run compose_report.py --write-md and retry the chain. If the reason is commentary_markdown missing or empty, treat as a malformed hand-off: repair-dispatch quoting the reason.
- After ANY corrective dispatch, resume from the gate chain — never feed
insert_coaching.py an ungated file.
Retry budget: max 2 corrective dispatches (same rule as Context A). Graceful degrade: if the FIRST corrective dispatch also exits 3 while the receipt claims complete with the correctly echoed path, treat the host topology as hand-off-incompatible — fall back to message-channel transport: apply the tolerant JSON extraction protocol to the sub-agent's final message, stage its commentary_markdown payload to $REVIEW_DIR/coaching_commentary.json via a quoted <<'COACHING_EOF' heredoc (single-quoted → apostrophe-safe; NEVER python -c, NEVER the outputs/ root), and run the same insert_coaching.py --commentary-file "$REVIEW_DIR/coaching_commentary.json" against that staged file.
Step 8 (Optional): Generate Visual Report
python3 "$SCRIPTS/visualize.py" --dir "$REVIEW_DIR" -o "$REVIEW_DIR/report.html"
Present the HTML file path to the user so they can open the visual report.
Step 9: Deliver Artifacts
Copy final deliverables to workspace root: {Company}_Deck_Review.md, .html (if generated), .json (optional).
Do not rm anything under $REVIEW_DIR — it is the promoted outputs/ tree in Cowork, where
deleting a user-visible path is unsafe (and the parity gate flags it). Scratch already lives in
$STAGING_DIR (/tmp, reclaimed by the sandbox). The answered gate_state.json is left in place; a
later fresh review of this company is not misread as a resume because setup_run.py --clean deletes a
stale answered gate (run_id mismatch) at the start of the next run.
Gotchas
- "Looks polished" bias: A well-designed deck is not a strong deck. Score content, narrative, and evidence independently of visual quality. The checklist separates design (5 items) from content (8 items) for this reason.
- Template / AI-generated copy: If multiple slides use generic phrasing ("revolutionize," "disrupt," "world-class team") with no specifics, flag this in coaching commentary as a credibility risk — investors notice formulaic decks. This is not a checklist item but affects overall narrative assessment.
- Benchmarks are medians, not gates: A $3M seed round in a $1B TAM market is not automatically wrong — context matters. Use benchmarks from
deck-best-practices.md as reference points, not hard pass/fail thresholds. The coaching commentary should explain deviations rather than penalize them.
- Founder provided text, not a file: When the founder describes slides in conversation rather than uploading a file, adapt: write
deck_inventory.json from the conversation, set input_format: "text", and note reduced confidence in visual/design assessments. Design category items become not_applicable unless the founder shares screenshots.
- Cross-skill context: If
founder_context.py returned prior market-sizing or financial-model-review runs, mention relevant findings in coaching commentary (e.g., "Your market sizing calculated $X TAM — your deck claims $Y"). Do not hard-fail on discrepancies; flag them for the founder.
Main-Thread Return
This skill runs inline in the main thread (not as a sub-agent). The final outcome the main thread delivers to the founder is:
- The path to
$REVIEW_DIR/report.md — the primary deliverable.
- The headline outcome fields, sourced from the
coaching_payload you inlined in Step 7 (score_pct, overall_status, high_severity_warnings) plus the insert_coaching.py receipt (status, report_path, run_id). The Context B sub-agent no longer echoes these — do not source them from its return.
- Optionally: the HTML report path from Step 8.
Do NOT inline report_markdown in the assistant message. The founder reads the file via the path — inlining round-trips ~25 KB of markdown through the parent context for no benefit.
Scoring
- Each of 35 items: pass / fail / warn / not_applicable
score_pct = pass / (total - not_applicable) x 100
- Overall: "strong" (>=85%), "solid" (>=70%), "needs_work" (>=50%), "major_revision" (<50%)
What-If Recomputation Rule
If the founder asks "what's my score if I fix X" or any score recomputation question: re-run checklist.py with the updated item statuses (or read the Appendix evidence from report.md) and present the script's output. Never compute a revised score by mental arithmetic in the chat — the formula is non-trivial (warn/fail earn no credit; N/A items are excluded from the denominator) and off-by-one errors cause real harm.
Feedback
If a run ends blocked or failed, after you report the reason to the founder, add one line:
If this looks wrong or didn't finish, you can flag it: /founder-skills:feedback.
On unsolicited praise or frustration, you may mention /founder-skills:feedback once — never routinely, never mid-workflow, never more than once per session.