
Self Review
- 11 installs
- 255 repo stars
- Updated February 27, 2026
- lingzhi227/claude-skills
This is a copy of self-review by lingzhi227 - installs and ranking accrue to the original listing.
Helps with ai & agent building tasks.
About
self-review is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- self-review
- AI & Agent Building
- AI-coding skill
Self Review by the numbers
- 11 all-time installs (skills.sh)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lingzhi227/claude-skills --skill self-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 255 |
| Last updated | February 27, 2026 |
| Repository | lingzhi227/claude-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Self-Review
Review an academic paper using a structured review form with multiple reviewer personas.
Input
$ARGUMENTS— Path to PDF file or.texfile
Scripts
Extract text from PDF
python ~/.claude/skills/self-review/scripts/extract_pdf_text.py paper.pdf --output paper_text.txt
python ~/.claude/skills/self-review/scripts/extract_pdf_text.py paper.pdf --format markdownTries pymupdf4llm (best) → pymupdf → pypdf. Install: pip install pymupdf4llm pymupdf pypdf
Parse PDF into structured sections
python ~/.claude/skills/self-review/scripts/parse_pdf_sections.py \
--pdf paper.pdf --output sections.jsonExtracts title (via font size), section headings, and section text. Requires: pip install pymupdf Key flags: --format text, --verbose
Workflow
Step 1: Load Paper
- If PDF: use
extract_pdf_text.pyto extract text - If
.tex: read the LaTeX source directly
Step 2: Three-Persona Review
Run three independent reviews using different personas (from references/review-form.md):
1. Harsh but fair reviewer: Expects good experiments that lead to insights 2. Harsh and critical reviewer: Looking for impactful ideas in the field 3. Open-minded reviewer: Looking for novel ideas not proposed before
For each persona, generate a review following the NeurIPS review JSON format in references/review-form.md.
Step 3: Reflection Refinement (up to 3 rounds per reviewer)
After each review, apply the reflection prompt: re-evaluate accuracy and soundness, refine if needed. Stop when "I am done".
Step 4: Aggregate
- Combine all three reviews
- Average numerical scores (round to nearest integer)
- Synthesize a meta-review finding consensus
- Weight scores using AgentLaboratory weights: Overall (1.0), Contribution (0.4), Presentation (0.2), others (0.1 each)
Step 5: Actionable Report
Output format:
## Review Summary
- **Overall Score**: X/10 (Weighted: Y/10)
- **Decision**: Accept / Reject
- **Confidence**: Z/5
## Strengths (consensus across reviewers)
1. ...
2. ...
## Weaknesses (consensus across reviewers)
1. ...
2. ...
## Questions for Authors
1. ...
## Specific Suggestions for Improvement
1. [Section X, Page Y]: ...
2. [Section Z, Page W]: ...
## Score Breakdown
| Dimension | R1 | R2 | R3 | Avg |
|-----------|----|----|-----|-----|
| Overall | ... | ... | ... | ... |
| Contribution | ... | ... | ... | ... |
| ... | ... | ... | ... | ... |References
- NeurIPS review form, scoring weights, personas, reflection prompts:
~/.claude/skills/self-review/references/review-form.md - PDF text extraction:
~/.claude/skills/self-review/scripts/extract_pdf_text.py
Missing Sections Check
You MUST verify that all required sections are present: Abstract, Introduction, Methods/Approach, Experiments/Results, Discussion/Conclusion. Reduce scores if any are missing.
Related Skills
- Upstream: paper-compilation
- Downstream: paper-revision, rebuttal-writing
- See also: slide-generation
NeurIPS Review Form
Extracted verbatim from AI-Scientist perform_review.py.Review Form Instructions
Below is a description of the questions you will be asked on the review form for each paper and some guidelines on what to consider when answering these questions.
1. Summary: Briefly summarize the paper and its contributions. This is not the place to critique the paper; the authors should generally agree with a well-written summary.
2. Strengths and Weaknesses: Please provide a thorough assessment of the strengths and weaknesses of the paper, touching on each of the following dimensions:
- Originality: Are the tasks or methods new? Is the work a novel combination of well-known techniques? Is it clear how this work differs from previous contributions? Is related work adequately cited?
- Quality: Is the submission technically sound? Are claims well supported (e.g., by theoretical analysis or experimental results)? Are the methods used appropriate? Is this a complete piece of work or work in progress?
- Clarity: Is the submission clearly written? Is it well organized? Does it adequately inform the reader?
- Significance: Are the results important? Are others likely to use the ideas or build on them? Does it advance the state of the art in a demonstrable way?
3. Questions: Please list and carefully describe any questions and suggestions for the authors.
4. Limitations: Have the authors adequately addressed the limitations and potential negative societal impact of their work?
5. Soundness: 1-4 scale (poor, fair, good, excellent)
6. Presentation: 1-4 scale (poor, fair, good, excellent)
7. Contribution: 1-4 scale (poor, fair, good, excellent)
8. Overall: 1-10 scale:
- 10: Award quality
- 9: Very Strong Accept
- 8: Strong Accept
- 7: Accept
- 6: Weak Accept
- 5: Borderline accept
- 4: Borderline reject
- 3: Reject
- 2: Strong Reject
- 1: Very Strong Reject
9. Confidence: 1-5 scale (low to absolute certainty)
10. Decision: Accept or Reject (binary only)
Review JSON Format
{
"Summary": "...",
"Strengths": ["..."],
"Weaknesses": ["..."],
"Originality": 3,
"Quality": 3,
"Clarity": 3,
"Significance": 3,
"Questions": ["..."],
"Limitations": ["..."],
"Ethical Concerns": false,
"Soundness": 3,
"Presentation": 3,
"Contribution": 3,
"Overall": 6,
"Confidence": 4,
"Decision": "Accept"
}Scoring Weights (from AgentLaboratory)
| Dimension | Weight | Scale |
|---|---|---|
| Overall | 1.0 | /10 |
| Contribution | 0.4 | /4 |
| Presentation | 0.2 | /4 |
| Soundness | 0.1 | /4 |
| Confidence | 0.1 | /5 |
| Originality | 0.1 | /4 |
| Significance | 0.1 | /4 |
| Clarity | 0.1 | /4 |
| Quality | 0.1 | /4 |
Weighted score formula: (sum of weight × normalized_score) / sum_of_weights × 10
NeurIPS acceptance bar: ~5.9/10. Workshop acceptance: ~6.0/10 (60-70% acceptance rate).
Three Reviewer Personas (from AgentLaboratory)
1. Harsh but fair: Expects good experiments that lead to insights 2. Harsh and critical: Looking for impactful ideas in the field 3. Open-minded: Looking for novel ideas not proposed before
Reflection Prompt (up to 5 rounds)
Round {N}/{total}.
In your thoughts, first carefully consider the accuracy and soundness of the review you just created.
Include any other factors that you think are important in evaluating the paper.
Ensure the review is clear and concise, and the JSON is in the correct format.
Do not make things overly complicated.
In the next attempt, try and refine and improve your review.
Stick to the spirit of the original review unless there are glaring issues.
If there is nothing to improve, simply repeat the previous JSON EXACTLY and include "I am done" at the end of the thoughts.#!/usr/bin/env python3
"""Extract text from a PDF file for review.
Tries multiple extraction methods in order of quality:
1. pymupdf4llm (markdown output, best quality)
2. pymupdf/fitz (plain text)
3. pypdf (fallback)
Adapted from AI-Scientist perform_review.py load_paper().
Usage:
python extract_pdf_text.py paper.pdf
python extract_pdf_text.py paper.pdf --output paper_text.txt
python extract_pdf_text.py paper.pdf --format markdown
"""
import argparse
import sys
def extract_with_pymupdf4llm(pdf_path: str) -> str | None:
"""Try pymupdf4llm for markdown extraction (best quality)."""
try:
import pymupdf4llm
return pymupdf4llm.to_markdown(pdf_path)
except ImportError:
return None
except Exception as e:
print(f"pymupdf4llm failed: {e}", file=sys.stderr)
return None
def extract_with_pymupdf(pdf_path: str) -> str | None:
"""Try pymupdf/fitz for plain text extraction."""
try:
import fitz
doc = fitz.open(pdf_path)
text_parts = []
for page in doc:
text_parts.append(page.get_text())
doc.close()
return "\n".join(text_parts)
except ImportError:
return None
except Exception as e:
print(f"pymupdf failed: {e}", file=sys.stderr)
return None
def extract_with_pypdf(pdf_path: str) -> str | None:
"""Try pypdf for fallback text extraction."""
try:
from pypdf import PdfReader
reader = PdfReader(pdf_path)
text_parts = []
for page in reader.pages:
text = page.extract_text()
if text:
text_parts.append(text)
return "\n".join(text_parts)
except ImportError:
return None
except Exception as e:
print(f"pypdf failed: {e}", file=sys.stderr)
return None
def extract_text(pdf_path: str, preferred_format: str = "auto") -> str:
"""Extract text from PDF using the best available method."""
methods = [
("pymupdf4llm", extract_with_pymupdf4llm),
("pymupdf", extract_with_pymupdf),
("pypdf", extract_with_pypdf),
]
if preferred_format == "markdown":
# Prefer pymupdf4llm
methods = [methods[0], methods[1], methods[2]]
elif preferred_format == "plain":
# Skip pymupdf4llm
methods = [methods[1], methods[2], methods[0]]
for name, func in methods:
text = func(pdf_path)
if text and text.strip():
print(f"Extracted using {name} ({len(text)} chars)", file=sys.stderr)
return text
print("ERROR: All extraction methods failed. Install one of: pymupdf4llm, pymupdf, pypdf", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description="Extract text from PDF for review")
parser.add_argument("pdf_file", help="Path to PDF file")
parser.add_argument("--output", "-o", help="Output text file (default: stdout)")
parser.add_argument("--format", choices=["auto", "markdown", "plain"],
default="auto", help="Preferred output format")
args = parser.parse_args()
text = extract_text(args.pdf_file, args.format)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(text)
print(f"Written to {args.output} ({len(text)} chars)", file=sys.stderr)
else:
print(text)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Parse PDF papers into structured sections using font analysis.
Extracts title (via largest font detection), section headings, and
section text from academic PDF papers.
Requires: PyMuPDF (pip install pymupdf)
Usage:
python parse_pdf_sections.py --pdf paper.pdf --output sections.json
python parse_pdf_sections.py --pdf paper.pdf --format text
python parse_pdf_sections.py --pdf paper.pdf --output sections.json --verbose
"""
import argparse
import json
import os
import re
import sys
from collections import Counter
try:
import fitz # PyMuPDF
except ImportError:
print("Error: PyMuPDF required. Install: pip install pymupdf", file=sys.stderr)
sys.exit(1)
def get_title(doc) -> tuple[str, int]:
"""Extract paper title by finding the largest font text on early pages.
Returns (title_string, title_page_index).
"""
max_font_sizes = []
for page_index, page in enumerate(doc):
if page_index > 2:
break
text = page.get_text("dict")
for block in text.get("blocks", []):
if block.get("type") == 0 and block.get("lines"):
for line in block["lines"]:
for span in line.get("spans", []):
max_font_sizes.append(span.get("size", 0))
if not max_font_sizes:
return "", 0
max_font_sizes.sort()
top_sizes = max_font_sizes[-2:] if len(max_font_sizes) >= 2 else max_font_sizes[-1:]
title_parts = []
title_page = 0
for page_index, page in enumerate(doc):
if page_index > 2:
break
text = page.get_text("dict")
for block in text.get("blocks", []):
if block.get("type") != 0 or not block.get("lines"):
continue
for line in block["lines"]:
for span in line.get("spans", []):
font_size = span.get("size", 0)
cur_text = span.get("text", "").strip()
if any(abs(font_size - ts) < 0.3 for ts in top_sizes):
if len(cur_text) > 4 and "arXiv" not in cur_text:
title_parts.append(cur_text)
title_page = page_index
title = " ".join(title_parts).replace("\n", " ").strip()
return title, title_page
def get_font_size_threshold(doc) -> float:
"""Determine the most common font size (body text) as threshold."""
font_sizes = []
for page in doc:
blocks = page.get_text("dict").get("blocks", [])
for block in blocks:
for line in block.get("lines", []):
for span in line.get("spans", []):
font_sizes.append(span.get("size", 0))
if not font_sizes:
return 10.0
most_common, _ = Counter(font_sizes).most_common(1)[0]
return most_common
def extract_sections(doc, threshold: float) -> list[dict]:
"""Extract sections from the PDF using font-based heading detection.
Uses two strategies:
1. ALL-CAPS headings (common in IEEE/ACM style)
2. Larger-font headings (common in NeurIPS/ICML style)
"""
sections = []
current_heading = None
current_text = []
current_page = 0
heading_font = -1
found_abstract = False
upper_heading = False
font_heading = False
roman_nums = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"}
digit_nums = {str(d) for d in range(1, 11)}
for page_index, page in enumerate(doc):
blocks = page.get_text("dict").get("blocks", [])
for block in blocks:
if not found_abstract:
try:
block_text = json.dumps(block)
except (TypeError, ValueError):
continue
if re.search(r"\bAbstract\b", block_text, re.IGNORECASE):
found_abstract = True
current_heading = "Abstract"
current_page = page_index
if not found_abstract:
continue
for line in block.get("lines", []):
for span in line.get("spans", []):
text = span.get("text", "").strip()
size = span.get("size", 0)
if not text:
continue
is_upper_heading = (
not font_heading
and text.isupper()
and sum(1 for c in text if c.isupper() and 'A' <= c <= 'Z') > 4
)
is_font_heading = (
not upper_heading
and size > threshold
and re.match(r"[0-9]*\.* *[A-Z][a-z]+(?:\s[A-Z][a-z]+)*", text)
)
if is_upper_heading:
upper_heading = True
if "References" in text or "REFERENCES" in text:
if current_heading:
sections.append({
"name": current_heading,
"text": " ".join(current_text).strip(),
"page": current_page + 1,
})
return sections
if current_heading:
sections.append({
"name": current_heading,
"text": " ".join(current_text).strip(),
"page": current_page + 1,
})
current_heading = text
current_text = []
current_page = page_index
elif is_font_heading:
font_heading = True
if heading_font == -1:
heading_font = size
elif abs(heading_font - size) > 0.5:
current_text.append(text)
continue
if "References" in text:
if current_heading:
sections.append({
"name": current_heading,
"text": " ".join(current_text).strip(),
"page": current_page + 1,
})
return sections
if current_heading:
sections.append({
"name": current_heading,
"text": " ".join(current_text).strip(),
"page": current_page + 1,
})
current_heading = text
current_text = []
current_page = page_index
elif current_heading is not None:
current_text.append(text)
# Flush last section
if current_heading:
sections.append({
"name": current_heading,
"text": " ".join(current_text).strip(),
"page": current_page + 1,
})
return sections
def parse_pdf(pdf_path: str, verbose: bool = False) -> dict:
"""Parse a PDF paper into structured sections.
Returns: {title: str, pages: int, sections: [{name, text, page}]}
"""
doc = fitz.open(pdf_path)
title, title_page = get_title(doc)
threshold = get_font_size_threshold(doc)
if verbose:
print(f"Title: {title}", file=sys.stderr)
print(f"Body font size threshold: {threshold:.1f}", file=sys.stderr)
print(f"Total pages: {len(doc)}", file=sys.stderr)
sections = extract_sections(doc, threshold)
num_pages = len(doc)
doc.close()
return {
"title": title,
"pages": num_pages,
"sections": sections,
}
def main():
parser = argparse.ArgumentParser(description="Parse PDF papers into structured sections")
parser.add_argument("--pdf", required=True, help="Input PDF file")
parser.add_argument("--output", "-o", help="Output JSON file (default: stdout)")
parser.add_argument("--format", choices=["json", "text"], default="json",
help="Output format (default: json)")
parser.add_argument("--verbose", action="store_true", help="Print progress info")
args = parser.parse_args()
if not os.path.exists(args.pdf):
print(f"Error: {args.pdf} not found", file=sys.stderr)
sys.exit(1)
result = parse_pdf(args.pdf, verbose=args.verbose)
if args.format == "text":
output = f"# {result['title']}\n\n"
for sec in result["sections"]:
output += f"## {sec['name']} (page {sec['page']})\n\n"
output += sec["text"] + "\n\n"
else:
output = json.dumps(result, indent=2, ensure_ascii=False)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(output)
print(f"Parsed {len(result['sections'])} sections to {args.output}", file=sys.stderr)
else:
print(output)
if __name__ == "__main__":
main()