
Self Review
- 1.2k installs
- 255 repo stars
- Updated February 27, 2026
- lingzhi227/agent-research-skills
self-review is an agent skill that self-reviews research drafts for clarity, claim support, and consistency before submission.
About
The self-review skill supports researchers running structured self-critique passes on drafts such as paper sections, experiment writeups, or grant paragraphs before external review. It checks claim-evidence alignment, undefined terms, missing citations, inconsistent notation, and paragraph-level clarity without rewriting entire documents blindly. Agents flag unsupported statements, suggest tighter wording, and verify that figures, tables, and text references agree. The skill is invoked after a draft exists and before paper-compilation or submission workflows in the agent-research-skills family. It emphasizes conservative edits that improve rigor while preserving author voice and existing factual content.
- Structured self-critique for research drafts before external review.
- Checks claim-evidence alignment and missing citations.
- Flags inconsistent notation and figure-table reference mismatches.
- Suggests conservative clarity edits without wholesale rewrites.
- Pairs with paper-compilation and writing skills in the research stack.
Self Review by the numbers
- 1,241 all-time installs (skills.sh)
- +38 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #226 of 1,879 Documentation skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
self-review capabilities & compatibility
- Capabilities
- claim evidence alignment checks · citation and notation consistency review · figure and table reference verification · conservative clarity edit suggestions · pre submission critique workflow
- Use cases
- documentation · code review
What self-review says it does
self-review
npx skills add https://github.com/lingzhi227/agent-research-skills --skill self-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.2k |
|---|---|
| repo stars | ★ 255 |
| Security audit | 2 / 3 scanners passed |
| Last updated | February 27, 2026 |
| Repository | lingzhi227/agent-research-skills ↗ |
What clarity, evidence, or consistency issues remain in this research draft before I submit it?
Self-review research drafts for clarity, claim support, and consistency before submission or compilation.
Who is it for?
Researchers running pre-submission self-critique on paper sections or experiment writeups.
Skip if: Skip for generating experiments or tables from raw data without an existing draft.
When should I use this skill?
User asks for self-review of a research draft, section, or grant paragraph before submission.
What you get
A marked-up review with unsupported claims, citation gaps, notation issues, and conservative edit suggestions.
- NeurIPS JSON review per persona
- Weighted meta-review
- Actionable revision report
By the numbers
- Simulates 3 reviewer personas with up to 3 reflection rounds each
- Uses 9-section NeurIPS review form including 1-10 overall and 1-5 confidence scales
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()
Related skills
Forks & variants (2)
Self Review has 2 known copies in the catalog totaling 12 installs. They canonicalize to this original listing.
- lingzhi227 - 11 installs
- lingzhi227 - 1 installs
How it compares
Use self-review for academic paper drafts; use code-review skills for pull request and production codebase quality checks.
FAQ
What does self-review check?
Claim-evidence alignment, undefined terms, citations, notation consistency, and paragraph clarity.
Does it rewrite the entire draft?
No. It emphasizes conservative edits and flagged issues rather than wholesale rewrites.
When should it run in the research stack?
After a draft exists and before paper-compilation or external submission workflows.
Is Self Review safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.