deck-review

star 0

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.

yaniv-golan By yaniv-golan schedule Updated 6/13/2026

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.jsonfinancial-model-review, ic-sim, fundraise-readiness (future)

Skill Execution Model (READ FIRST)

This skill runs inline in the main thread (not as a sub-agent). The main thread has full tool access including Bash, and is responsible for orchestrating the full pipeline: running producer scripts, persisting artifacts, and dispatching the deck-review sub-agent at specific moments.

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 and returns structured JSON. The main thread captures the JSON and pipes it through the producer script (slide_reviews.py or checklist.py). The sub-agent does NOT write artifacts directly.
  • Context B — Post-compose coaching dispatch: Step 7 dispatches the sub-agent after compose_report.py writes report.md. The sub-agent receives the structured coaching_payload inlined in its prompt (it does NOT Read the full report.md), composes ## Coaching Commentary from it, Edits it into report.md via the per-run uuid insertion marker, verifies all canonical artifacts on disk, and returns a structured success payload.

Why this model: In Cowork, sub-agents have a restricted tool allowlist (no Bash). By keeping orchestration in the main thread and dispatching sub-agents only for analytical or post-compose tasks that use only Read/Edit/Glob/Grep, the pipeline works correctly in both Claude Code (CLI) and Cowork.

Tolerant JSON extraction protocol (Context A): After dispatching the sub-agent, capture its final assistant message. The sub-agent should return raw JSON, but may wrap it in ```json ... ``` fences or add a prose preamble. Extract JSON tolerantly:

  1. If the message is wrapped in a ```json ... ``` (or plain ``` ... ```) fence, strip the fence first.
  2. Try to parse the stripped text directly as JSON.
  3. 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 }).
  4. 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."

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).

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"
# In Cowork, CLAUDE_PLUGIN_ROOT substitutes to a host-side path that does not
# exist inside the session VM — self-heal by locating the plugin mount:
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"
ARTIFACTS_ROOT="${ARTIFACTS_ROOT:-$(pwd)/artifacts}"
mkdir -p "$ARTIFACTS_ROOT"

# RUN_ID — used by Step 1 (founder_context init) before slug-aware setup_run.py
# runs, then passed to setup_run via --run-id. If the caller's task prompt
# supplied a RUN_ID (resume), keep it; otherwise mint a fresh one.
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:

mkdir -p "$REVIEW_DIR/.staging"   # for ad-hoc sub-agent JSON staging

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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. Then write the inventory through the producer script:

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": "...",
  "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 detection signals: The company is AI-first if ANY of: (1) core product uses ML/AI for its primary value proposition, (2) inference or training costs appear in COGS or margins, (3) deck mentions foundation models, fine-tuning, or AI infrastructure as product components, (4) retention or engagement metrics reference AI-specific patterns (usage retention vs. seat retention). Set is_ai_company: true and record the evidence in ai_evidence. When in doubt, flag it — the gate will let the founder correct.

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 via gate_state.py answer, then re-invokes this sub-agent. 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" "$REVIEW_DIR/.staging/sp.json"
    cat "$REVIEW_DIR/.staging/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" "$REVIEW_DIR/.staging/sp.json"
    cat "$REVIEW_DIR/.staging/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/sp.json first and pipe that in — never cat and -o the same file in one command, which races and truncates it.

Sub-agent JSON staging

When a sub-agent returns JSON too large for bash heredoc, write it to $REVIEW_DIR/.staging/<step>_input.json first, then pipe via:

cat "$REVIEW_DIR/.staging/<step>_input.json" | python3 "$SCRIPTS/<producer>.py" ...

The .staging/ directory is created at setup and removed at cleanup. This avoids Operation not permitted errors that occur when writing to the session outputs mount (Cowork marks it read-only post-write).

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 via the Task tool so the analysis runs in an isolated context with the full deck text and stage profile.

Before dispatching, substitute placeholders in the template below: replace every <REFS> with the resolved $REFS value (the absolute references path from Step 0 — not the literal ${CLAUDE_PLUGIN_ROOT}, which does not resolve inside the Cowork session VM), <REVIEW_DIR> with the absolute review dir, and <RUN_ID> with $RUN_ID. The sub-agent has no access to your shell variables.

Dispatch prompt template:

CONTEXT: SLIDE_REVIEWS
REVIEW_DIR: <absolute path to REVIEW_DIR>
RUN_ID: <RUN_ID>

You are the deck-review agent dispatched in Context A (SLIDE_REVIEWS). Read
the deck at <deck file path or attached content> and the stage profile at
<REVIEW_DIR>/stage_profile.json. Compare each slide against the stage-specific
framework and non-negotiable principles from
<REFS>/deck-best-practices.md and <REFS>/checklist-criteria.md.

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.

Return JSON only — 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": "..."
}
Do NOT write, edit, or create ANY files — your ONLY output is the JSON in your final assistant message. Files you write directly would bypass schema validation and run_id stamping and will be overwritten.

After the sub-agent returns: apply the tolerant JSON extraction protocol (see "Skill Execution Model" preamble) to obtain the structured JSON from the sub-agent's final message. Then pipe through the producer script:

cat <<'REVIEWS_EOF' | python3 "$SCRIPTS/slide_reviews.py" --run-id "$RUN_ID" -o "$REVIEW_DIR/slide_reviews.json" --pretty
<JSON extracted from sub-agent reply>
REVIEWS_EOF

Step 5: Score Checklist -> checklist.json (Context A dispatch)

Dispatch the deck-review sub-agent in Context A (CHECKLIST). Dispatch via the Task tool. Substitute <REFS> with the resolved $REFS, <REVIEW_DIR> with the absolute review dir, and <RUN_ID> with $RUN_ID before dispatching (same as SLIDE_REVIEWS).

Dispatch prompt template:

CONTEXT: CHECKLIST
REVIEW_DIR: <absolute path to REVIEW_DIR>
RUN_ID: <RUN_ID>

You are the deck-review agent dispatched in Context A (CHECKLIST). Evaluate all
35 criteria from <REFS>/checklist-criteria.md
using the deck content (read from deck file or from slide_reviews.json for
reference), the stage profile at <REVIEW_DIR>/stage_profile.json, and the
deck inventory at <REVIEW_DIR>/deck_inventory.json.

For non-AI companies (is_ai_company: false), mark the 4 AI criteria
(ai_retention_rebased, ai_cost_to_serve_shown, ai_defensibility_beyond_model,
ai_responsible_controls) as not_applicable.

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 (mark not_applicable for non-AI companies — is_ai_company: false):
  - 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.

Return JSON only — the items array without a summary (the producer script
computes the summary):
{"items": [{"id": "purpose_clear", "status": "pass", "evidence": "...", "notes": "..."}, ...all 35 items...]}
Do NOT write, edit, or create ANY files — your ONLY output is the JSON in your final assistant message. Files you write directly would bypass schema validation and run_id stamping and will be overwritten.

After the sub-agent returns: apply the tolerant JSON extraction protocol to obtain the structured JSON. Then pipe through the producer script:

cat <<'CHECKLIST_EOF' | python3 "$SCRIPTS/checklist.py" --run-id "$RUN_ID" --pretty -o "$REVIEW_DIR/checklist.json"
<JSON extracted from sub-agent reply>
CHECKLIST_EOF

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. Dispatch via the Task tool 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, performs Grep idempotency, Edits via the per-run uuid insertion_marker, and Grep-verifies all artifacts. 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:

CONTEXT: POST_COMPOSE_COACHING

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. grep_idempotency_check — Grep "## Coaching Commentary" (output_mode:count)
   and Grep the EXACT coaching_payload.insertion_marker (output_mode:count)
   on coaching_payload.report_path. Apply the 6-state decision matrix.
2. Compose commentary from the inlined coaching_payload (failed_items,
   warned_items, summary, high_severity_warnings, stage, is_ai_company).
   Do NOT Read the full report.md.
3. edit_via_marker — single Edit on coaching_payload.report_path:
     old_string = coaching_payload.insertion_marker  (EXACT uuid string)
     new_string = "## Coaching Commentary\n\n<your commentary>"
4. self_verify_artifacts_via_grep_run_id — Grep run_id from each producer
   artifact, confirm all 4 match; bounded Read (limit:1) on report.json
   and report.md; re-Grep the marker (must be 0) and the
   "## Coaching Commentary" header (must be 1).
5. Return the success payload:
   {"status": "complete", "review_dir": "<path>", "report_path": "<path>",
    "score_pct": <number>, "overall_status": "<string>",
    "high_severity_warnings": [<list>]}
   OR if verification fails:
   {"status": "blocked", "reason": "<specific gap>"}

Stop after returning JSON. Do not narrate.

After the sub-agent returns: apply the tolerant JSON extraction protocol to obtain the success/blocked payload. If status == "blocked", stop and report the reason to the founder. If status == "complete", present report_path to the founder.

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).

rm -rf "$REVIEW_DIR/.staging" 2>/dev/null || true
# Remove the answered gate so a later fresh review of this company is not
# misread as a resume. setup_run.py --clean also guards this, but clearing it
# at the end of a completed run keeps the working directory honest.
rm -f "$REVIEW_DIR/gate_state.json" 2>/dev/null || true

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 structured success payload from the Context B sub-agent (Step 7): {status, review_dir, report_path, score_pct, overall_status, high_severity_warnings}.
  • 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.

Install via CLI
npx skills add https://github.com/yaniv-golan/founderskills-test --skill deck-review
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator