一键导入
paper-to-note
Use when reading an academic paper or paper URL and saving structured Chinese notes with metadata/assets to Obsidian.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when reading an academic paper or paper URL and saving structured Chinese notes with metadata/assets to Obsidian.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | paper-to-note |
| description | Use when reading an academic paper or paper URL and saving structured Chinese notes with metadata/assets to Obsidian. |
| allowed-tools | ["Read","Write","Edit","Bash","Glob","Grep","WebFetch","WebSearch","Agent","mcp__codex__codex"] |
Generate high-quality structured reading notes for academic papers.
paper-to-skill skill,产物在 ~/ai-skills/skills/。paper-to-skill 提炼工程手册。本 skill 在保存笔记时,如果已有对应 paper-to-skill skill,会自动填写 frontmatter 的 paper_to_skill 属性以便互相跳转。~/.claude/skills/_shared/commit-anchor.md — commit SHA 锚点格式(Pitfall P5 引用)~/.claude/skills/_shared/pseudocode-rules.md — 伪代码质量规则~/.claude/skills/_shared/known-categories.md — Obsidian 分类paper-to-note/scripts/extract_figures.py — canonical 图像提取工具;arXiv source-first,保留原始 source raster/vector 质量。若 runtime 还有旧的 $SHARED/extract_figures.py 镜像,必须确认它与本脚本一致后再用。paper-to-note/scripts/calibrate_widths.py — 全 vault 批量校准/验收图片嵌入:<img width="N"> 与 extract_figures.recommend_width 同步,并通过 --auto-center 把裸 <img> 包成 <div align="center"> ... </div> / 多 img 同行时整行 inline-wrap。dry-run 默认无副作用,--apply 时自动 backup 到 ~/.cache/paper_notes_calibration_backup/<ts>/。同时承担自适应图片宽度推荐(--auto-width 子命令;详见 Step 5d / P12)。paper-to-note/scripts/calibrate_widths.py — 全 vault 批量校准 / 验收工具,支持 dry-run 与 --apply(带备份)。Step 5e Verify 阶段必须用 --tolerance 0 --limit-diffs 0 dry-run 检查"所有 <img> width 都已是脚本推荐值"。本 skill 同时存在两份副本,在不同 runtime 下读取不同的物理路径(内容保持一致):
| 逻辑引用 | Claude Code 原生路径 | Cursor / Codex / 其他共享 runtime |
|---|---|---|
$SHARED/<file> | ~/.claude/skills/_shared/<file> | ~/.agents/skills/_shared/<file> |
$REVIEWER | ~/.claude/agents/paper-to-note-reviewer.md | ~/.agents/skills/paper-to-note/agents/paper-to-note-reviewer.md |
下文所有 ~/.claude/skills/_shared/... 与 ~/.claude/agents/paper-to-note-reviewer.md 引用,执行时按上表选择当前 runtime 下实际存在的那条路径即可(两条路径等价)。
The most common failure mode is re-counting the same long context across many turns and agents. Treat context as a budgeted artifact, not a transcript.
.md, image directory, repo checkout, and source files. Only paste small excerpts needed for the current decision.review_packet.md under $PAPER_TO_NOTE_WORKDIR/<paper-slug>/ if set, otherwise ${TMPDIR:-/tmp}/paper-to-note/<paper-slug>/. It should contain only paper metadata, note path, image dir, code repo/ref, figure inventory, unresolved risks, and changed sections since the last review. Keep it under ~120 lines by default and never exceed ~200 lines.review_packet*.md, revew_packet*.md, tmp/, _tmp/, _work/, extracted paper text, reviewer notes, or cloned repos anywhere under /Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/ (or the equivalent ~/OneDrive/paper_notes/). The vault may contain only the final note under notes/ and final referenced assets under files/.github_ref will be set). The only valid skip condition is: the note explicitly states 代码搜索未找到开源实现 after a documented search.$REVIEWER, the external review_packet.md path, and concrete file paths.HEAD (git rev-parse --verify HEAD fails). In that case, review in-place with read-only file access and do not try to resolve base branch HEAD.$...$, display $$...$$Unless the user explicitly asks for a short summary, write the note as a detailed reading note, not an abstract-style overview. The target is that a reader can understand the paper's motivation, method, experiments, and practical caveats from the note itself without immediately reopening the PDF.
Chinese CJK characters + English/alphanumeric word tokens after stripping YAML frontmatter, code blocks, image tags/embeds, URLs, and Markdown syntax. If the note is shorter, expand with substantive method details, experiment evidence, appendix material, code-to-paper interpretation, figure/table explanations, and limitations; never pad with repetitive filler, artificial line breaks, or one-sentence-per-line formatting.When asked to optimize existing notes below the minimum length, the trigger is effective word count < 3000, not Markdown line count.
.md to an external scratch/backup directory outside the vault before overwriting the note.effective_words = CJK chars + Latin/alphanumeric tokens script in Step 5e. The final note must be effective_words >= 3000 unless the user explicitly sets a lower threshold.These are non-negotiable structural items. A note missing any of them will be marked P0 by the Format Reviewer and must be fixed before approval.
title field with the full paper title (not just the short name). Reason: Obsidian uses title as the canonical display name; without it, search and graph view both degrade.
---
title: "World-R1: Reinforcing 3D Constraints for Text-to-Video Generation"
authors: ...
---
Code / Code reference lines only when no public code exists.
# <Paper Title>
> **Paper**: [arXiv:XXXX.XXXXX](https://arxiv.org/abs/XXXX.XXXXX)
> **Code**: [<owner>/<repo>](https://github.com/<owner>/<repo>)
> **Code reference**: `<branch>` @ `<short_sha>` (YYYY-MM-DD)
tags with ≥4 specific technical tags (concrete techniques, not generic categories). Bad: ["RL", "video"]. Good: ["RL", "video-generation", "Flow-GRPO", "3D-consistency"].config/<paper_name>.py, configs/<exp>.yaml, scripts/train_*.py), NOT from config/base.py default values or generic README defaults. Whenever the reported number could plausibly be a default, the note MUST cite the specific file path that overrides it.hpsv2; paper writes "average over $K$ frames" but code samples 1 random frame), the note MUST explicitly call out the gap in §3 Method, format: 论文公式与 released code 实现差异:.... Do NOT silently align the note to one side.Blog / article mode: if the input is a blog post, project article, or non-paper technical essay, save it under paper_notes/blogs/ (unless the user says otherwise), not the paper taxonomy. The Markdown filename MUST start with the visible/published date, e.g. YYYY-MM-DD Blog Title.md, and frontmatter MUST include date or published. Prefer the date visible in the rendered page/user-facing post; if raw metadata disagrees due to timezone or site build artifacts, record the rendered date in the filename/frontmatter and keep the raw value only as a secondary note if useful.
Output strictly in these 5 sections, each with substantive content. Default to maximum useful detail: do not compress a multi-page method/experiment section into a few bullets unless the user explicitly asks for a brief note. The note's explanatory center of gravity should be Motivation → Idea → Method; experiments and results are still required, but they support the reader's understanding rather than replacing method explanation.
This is the most important section — expand in detail. Cover every novel component and every paper section that materially contributes to the method. If the paper has multiple modules/stages/objectives, each should get its own subsection with intuition, mechanics, formula/code where applicable, and interaction with the rest of the pipeline.
<img> tag, separated by a blank line (see P8)WebSearch to verify: [paper title] github, [author org] github [method name]torch.tensor, nn.Module, F.cross_entropy)python code blocks for syntax highlightingdef train_step(model, batch, optimizer):
x, y = batch
logits = model(x) # forward pass
loss = F.cross_entropy(logits, y)
loss.backward()
optimizer.step()
return loss.item()
> **Code reference**: `main` @ `abc12345` (2026-04-17) — pseudocode and mapping based on this commit
Read toolWebFetch for normal web pages; if WebFetch fails with domain safety verification / enterprise policy errors, immediately fall back to Bash(curl -L ...) or python urllib/request and continue.curl as the default path in proxy/offline-routed runtimes; fetch the HTML abstract page and PDF directly:
curl -L "https://arxiv.org/abs/<arxiv_id>" -o ~/ai-skills/papers/<arxiv_id>.html
curl -L "https://arxiv.org/pdf/<arxiv_id>" -o ~/ai-skills/papers/<arxiv_id>.pdf
WebSearch to find the paper, then fetch it~/ai-skills/papers/ for figure extractionSource-first rule for arXiv papers(默认路径): if the paper has an arXiv ID, you MUST extract figures from the arXiv LaTeX source before considering any PDF crop. The PDF is for reading/verification; it is not the default figure source. Do not create cropped PDF screenshots for arXiv papers unless source extraction returns no usable figure for a specific required figure/table, and record that exception in the working notes.
For arxiv papers (default): download source tarball/e-print to get original high-res figure files:
python3 ~/.claude/skills/paper-to-note/scripts/extract_figures.py \
--arxiv <arxiv_id> <notes_image_dir>
This downloads the LaTeX source, extracts original figure files (PNG/JPG/SVG/PDF/EPS), preserves source raster dimensions by default, converts source PDFs to SVG when possible, and only uses high-DPI PNG conversion when vector conversion is unavailable. This avoids the blur introduced by PDF-page crops or downsampled screenshots.
If source extraction returns too few figures, first inspect the arXiv source tree / .tex \includegraphics paths to find missing assets. Use PDF cropping only as a documented fallback for assets that are not present in the source package (for example, a publisher-only table image).
When the arXiv source places multiple panels under one figure caption (e.g. subfigure, subcaptionbox, minipage, tabular, or repeated \includegraphics with (a)/(b)/(c) labels), preserve that grouped layout in the note.
.tex figure environment whenever extracted filenames look like panels (fig3a, breakdown, quant_proxy) or the caption/text references Figure 3a/3b/3c; the source .tex is the authority for grouping/layout.fig3_group.svg / fig3_abc.svg matching the source/PDF layout, then embed it once at normal width.python3 ~/.claude/skills/paper-to-note/scripts/extract_figures.py \
<notes_image_dir> \
--compose "fig3_group:row:breakdown.svg,quant_degradation.svg,quant_proxy.svg"
Figure 3a–3c 解读:..., explaining each panel inside the paragraph. Do not embed grouped panels as three separate full-width images.width="320"–width="450" or place them side-by-side.For blogs and frontend-rendered article pages, raw HTML is only a hint. Many important figures are injected after hydration or drawn as SVG/HTML/CSS chart blocks, so curl/WebFetch/Defuddle may show no useful <img> even when the browser visibly shows figures.
og:image / social preview images / thumbnails as content figures unless the user explicitly asks for a cover/thumbnail. Blog notes should not include thumbnails by default.document.images, currentSrc, srcset). Then inspect rendered-only visual blocks: [role=img], .recharts-wrapper, svg, canvas, figure-like containers, and nearby captions./Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/files/blogs/<yyyy-mm-dd-slug>/ and reference them from the blog note as ../files/blogs/<yyyy-mm-dd-slug>/<file>.png.After writing notes: delete any extracted figures that are NOT referenced by an <img> tag or Obsidian image embed in the final notes. Keep only files that are actually embedded.
Before finalizing the note, verify every embedded image is a real figure crop, not a mostly-blank canvas:
<img width="...">, suspect hidden whitespace inside the PNG/PDF crop first; fix the asset, not only the Markdown width.Helper command:
python3 ~/.claude/skills/paper-to-note/scripts/extract_figures.py <notes_image_dir> --trim
Use --trim-pad <px> if labels are close to the edge.
For non-arxiv papers, or documented arXiv-source misses only (fallback): crop individual figures from PDF pages:
# Crop individual figures (PREFERRED for non-arxiv)
python3 ~/.claude/skills/paper-to-note/scripts/extract_figures.py \
--pdf <pdf_path> --crop <notes_image_dir> \
--figures "fig1:4:72,48,540,370" "table1:4:100,490,520,610" "fig2:5:72,48,540,260"
Each --figures entry format: name:page:x0,y0,x1,y1 (page is 1-indexed, coordinates in PDF points).
How to determine crop coordinates: Read PDF pages with the Read tool to see content layout, then estimate coordinates. Standard PDF page is 612×792 points. Typical margins are 72pt on each side. Iterate: extract, verify with Read tool, re-crop if needed.
# Full-page rendering (LAST RESORT only — avoid this)
python3 ~/.claude/skills/paper-to-note/scripts/extract_figures.py \
--pdf <pdf_path> <notes_image_dir> --pages <page_numbers...>
The output directory should mirror the current multi-level note category: /Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/files/<TopCategory>/<SubCategory>/<PaperTitle>/ (or the equivalent ~/OneDrive/paper_notes/files/<TopCategory>/<SubCategory>/<PaperTitle>/ runtime path).
NEVER skip this step. NEVER assume code is unavailable.
WebSearch: [paper title] github, [first author org] [method name] githubWebFetch on the GitHub API tree endpoint to map repo structuregh api repos/<owner>/<repo>/commits/HEAD --jq '(.sha[:8]) + " (" + .commit.author.date[:10] + ")"'
# e.g. outputs: abc12345 (2026-04-17)
Save as <branch>@<short_sha> (e.g. main@abc12345). This anchors all pseudocode and mapping to a reproducible code state.Use the Obsidian CLI to create and manage notes. This ensures proper vault integration (backlinks, tags, search indexing).
Vault name: paper_notes (current macOS path: /Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/; some runtimes may expose the same vault as /Users/bytedance/OneDrive/paper_notes/)
Blog / article destination: if the user asks for a blog/article note, write directly to /Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/blogs/YYYY-MM-DD <Title>.md; use assets in /Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/files/blogs/<yyyy-mm-dd-slug>/; relative figure paths from the note should start with ../files/blogs/....
Always classify into the actual current multi-level taxonomy, not the historical flat folders. The taxonomy may change often, so first inspect the live vault categories:
obsidian vault="paper_notes" folders folder="notes"
# If the CLI output is incomplete, inspect the filesystem too:
find "/Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/notes" -maxdepth 2 -type d | sort
Current top-level taxonomy and expected subcategories:
Agent
├── Agentic Systems & Applications
├── Memory
├── Personalization
└── RL
LLM & VLM
├── Pretraining & Architecture
├── Long-Context & Streaming
├── RL & Post-Training
├── Evaluation & Analysis
└── Theory
Multimodal Generation
├── Pretraining & Architecture
├── Video & Audio-Video Generation
├── Acceleration & Distillation
├── RL & Alignment
└── Reasoning & Test-Time Scaling
World Model
├── Long-Horizon Generation
├── Real-Time & Streaming
├── Interactive & Controllable
└── 3D & Multi-View Simulation
Physical AI
├── VLA & World-Action Models
├── Robot Data & Manipulation
├── Physical Video Generation
└── Embodied & Driving Simulation
Classification rules:
notes/<TopCategory>/<SubCategory>/ destination using the paper's main contribution, not just keywords in the title.files/<TopCategory>/<SubCategory>/<PaperTitle>/ asset path.Pretraining & Architecture, not VLM Pretraining & Architecture).tags must include one hierarchical category tag of the form paper/<top-slug>/<sub-slug> plus specific technical tags. Remove stale historical category tags such as visual-understanding, rl-for-visual-generation, world-model-long-video-generation, multi-modal-generation, diffusion-acceleration, rl-for-llm-vlm, and agent-memory.Use obsidian create with the full note content. The note should use Obsidian Flavored Markdown:
![[fig_name.png]] Obsidian embed syntax for figures (instead of raw <img> tags)[[wikilinks]] for cross-references to other papers in the vaultobsidian vault="paper_notes" create \
name="<PaperTitle>" \
path="notes/<TopCategory>/<SubCategory>/" \
content="<note_content>" \
silent
Note: for long content that exceeds shell argument limits, use the Write tool to create the file directly at /Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/notes/<TopCategory>/<SubCategory>/<PaperTitle>.md, then use Obsidian CLI to set properties.
After creating the note, set structured YAML frontmatter properties:
obsidian vault="paper_notes" property:set name="authors" value="Name1, Name2" type=text file="<PaperTitle>"
obsidian vault="paper_notes" property:set name="affiliations" value="Univ A, Company B" type=text file="<PaperTitle>"
obsidian vault="paper_notes" property:set name="arxiv" value="XXXX.XXXXX" type=text file="<PaperTitle>"
obsidian vault="paper_notes" property:set name="github" value="https://github.com/..." type=text file="<PaperTitle>"
obsidian vault="paper_notes" property:set name="venue" value="NeurIPS 2025" type=text file="<PaperTitle>"
obsidian vault="paper_notes" property:set name="year" value="2025" type=text file="<PaperTitle>"
obsidian vault="paper_notes" property:set name="tags" value="S2V,benchmark,dataset" type=list file="<PaperTitle>"
obsidian vault="paper_notes" property:set name="github_ref" value="main@abc12345" type=text file="<PaperTitle>"
# 若该论文已有对应的 paper-to-skill skill,追加此属性以便双向跳转
obsidian vault="paper_notes" property:set name="paper_to_skill" value="<skill-name>" type=text file="<PaperTitle>"
github_ref 格式为 <branch>@<short_sha>,如 main@abc12345。若无开源代码则省略此属性。
paper_to_skill 检测方式:扫描 ~/ai-skills/skills/*/sources.json,若某个 skill 的 papers[].id 或 papers[].url 中的 arxiv_id 与当前论文(归一化后,strip v<N> / 前缀 arxiv:)匹配,则填入该 skill 的目录名。若未找到匹配 skill 则省略此属性。
伪代码:
import json, glob, os, re
def to_arxiv(s):
"""Normalize an arxiv id from either a bare id string or a full URL.
Examples:
'2405.01234v2' -> '2405.01234'
'arxiv:2405.01234' -> '2405.01234'
'https://arxiv.org/abs/2405.01234v1' -> '2405.01234'
"""
if not s:
return ""
s = str(s).lower().replace('arxiv:', '')
m = re.search(r'arxiv\.org/abs/([^/?#]+)', s)
if m:
s = m.group(1)
return re.sub(r'v\d+$', '', s)
target_arxiv = to_arxiv(this_paper_arxiv)
paper_to_skill_value = None
for sd in glob.glob(os.path.expanduser("~/ai-skills/skills/*/")):
src_path = os.path.join(sd, "sources.json")
if not os.path.isfile(src_path):
continue
src = json.load(open(src_path))
for p in src.get("papers", []):
if to_arxiv(p.get("id", "")) == target_arxiv or to_arxiv(p.get("url", "")) == target_arxiv:
paper_to_skill_value = os.path.basename(os.path.dirname(sd))
break
if paper_to_skill_value:
break
# 若 paper_to_skill_value 仍为 None,说明该论文暂无对应 skill → 省略该 property
Figures are stored in /Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/files/<TopCategory>/<SubCategory>/<PaperTitle>/, mirroring the note category.
In note content, reference figures using relative paths from the note file. Every embed is wrapped in a <div align="center"> ... </div> block so it renders centered in Obsidian / GitHub / VS Code preview (a bare <img> left-aligns by default — see P13).
Standard single-figure form:
<div align="center">
<img src="../../../files/<TopCategory>/<SubCategory>/<PaperTitle>/fig_name.png" alt="Figure X" width="<W>">
</div>
Robust single-figure form for blog screenshots / unusually wide HTML-CSS diagrams that still look left-shifted in Obsidian despite align="center":
<div align="center" style="text-align:center;">
<img src="../files/blogs/<yyyy-mm-dd-slug>/fig_name.png" alt="Figure X" style="display:inline-block; width:760px; max-width:100%; height:auto;">
</div>
Use this only for the affected figure, and prefer a compact width such as 720–800 px for very wide rendered screenshots instead of stretching them to 920+ px.
Side-by-side comparison strip (multi-<img> on one line — typical for (a)/(b) qualitative panels) uses inline wrap so the panels stay on one row:
<div align="center"><img src=".../fig_a.png" alt="..." width="<Wa>"> <img src=".../fig_b.png" alt="..." width="<Wb>"></div>
Obsidian-embed form (also wrapped):
<div align="center">
![[fig_name.png|<W>]]
</div>
The <W> value is never chosen by feel and never a fixed default like 1000. It is always taken from the per-figure recommendation produced by extract_figures.py --auto-width (or the equivalent manual rule when the script is unavailable). The <div align="center"> wrapper is also non-optional — it is enforced in Step 5e Verify and is a P12 / P13 regression check.
BEFORE writing the first <img> / ![[]], run --auto-width on the figure dir and keep the report visible while you draft the figure section. Never write <img> tags from memory or from another note's values.
python3 ~/.claude/skills/paper-to-note/scripts/extract_figures.py \
--auto-width "/Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/files/<TopCategory>/<SubCategory>/<PaperTitle>"
The report prints, for every figure file, intrinsic dimensions, aspect ratio, whether it is a hero/composite, and a width="<N>" value to paste into the <img> tag. Note: the script also runs this automatically after --arxiv extraction and --compose, so on a fresh note you usually do not need a separate manual call — but you MUST still consume the report.
Use the recommended value verbatim for each figure. Do not pick "round numbers" like 800 / 1000 / 1200. The recommendation is already quantized to 20 px and cap-balanced so adjacent figures look visually consistent.
Allowed deviation: ±80 px from the recommendation, only with a one-line justification in the working notes (e.g. "Figure 4 shrunk to 460 because adjacent figure already uses 520 and they share a row of comparison"). Never silently deviate.
For unusual aesthetics (e.g. a heavy composite that must stay compact, or a sparse plot that needs to breathe) rerun with a different height cap rather than handpicking a width:
--rec-max-height 460 → tighter, ~10% smaller across the board--rec-max-height 580 → looser, ~10% larger
Pasting the rerun's recommendation back into the note keeps the values in the script's audit trail.Width policy reference (for reading the recommendation, not for replacing the script):
_group.svg): 420–540 pxManual fallback (only when extract_figures.py is unavailable): open the figure, read the intrinsic size (SVG viewBox or raster pixel dims), then compute width = round(min(920, 520 × aspect) / 20) × 20, clamped to [360, 920]. For hero / overview / framework / pipeline / architecture / _group figures with aspect < 1.3, use a 1.2× height cap (520 × 1.2 = 624). Document in the note that the manual rule was used.
Wide table hygiene: do not put long code paths and long hyperparameter lists into one Markdown table row. Obsidian will force awkward column widths and make key values appear as a narrow unreadable strip. For training configs or code mappings with long values, prefer short sections / definition lists:
#### RAVEN DMD
- **Config path**: `configs/.../raven.jsonc`
- **Sampling / shape**: `training_steps=220`, ...
- **Optimizer**: backbone LR `2e-6`, ...
Use tables only when every cell is short enough to wrap cleanly.
CRITICAL (P8): always leave a blank line between the closing </div> of the figure wrapper and the "Figure N 解读" text. Without the blank line, Markdown treats the next paragraph as part of the HTML block and $...$ inline math won't render:
<div align="center">
<img src="..." alt="Figure 3" width="720">
</div>
Figure 3 解读:由 $K$ 个 reward models 打分…
For grouped subfigures (Figure 3a/3b/3c, (a)/(b)/(c) under one caption), embed one composite image at the per-figure recommended width (typically 460–540 px for portrait composites, not 1000), still inside a <div align="center"> wrapper:
<div align="center">
<img src="../../../files/<TopCategory>/<SubCategory>/<PaperTitle>/fig3_group.svg" alt="Figure 3a–3c" width="<recommended>">
</div>
Figure 3a–3c 解读:…
Never place each subpanel as a separate large image unless the original paper shows them as separate figures.
Effective-word count:
obsidian vault="paper_notes" read file="<PaperTitle>"
python3 - <<'PY'
from pathlib import Path
import re
p = Path("/Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes/notes/<TopCategory>/<SubCategory>/<PaperTitle>.md")
text = p.read_text(errors="ignore")
text = re.sub(r"^---\s*\n.*?\n---\s*\n", " ", text, flags=re.S)
text = re.sub(r"```.*?```", " ", text, flags=re.S)
text = re.sub(r"!\[\[[^\]]+\]\]|<img\b[^>]*>|https?://\S+|`[^`]*`|<[^>]+>", " ", text, flags=re.I)
text = re.sub(r"[#>*_\[\]()|{}:;,.,。!?、()《》“”\"'=-]", " ", text)
cjk = len(re.findall(r"[\u4e00-\u9fff]", text))
latin = len(re.findall(r"[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*", text))
print(f"effective_words={cjk + latin} cjk_chars={cjk} latin_tokens={latin}")
PY
Adaptive-width + centering sanity (MANDATORY — fails if any embed deviates from --auto-width recommendation, and fails if any <img> is not wrapped in a centering container):
# Run dry-run with --auto-center so both regressions show up in one pass.
# Filter to the note we just created. Expect:
# width calibrate: <none for this note>
# center wrap : <none for this note>
# center wrap-line: <none for this note>
python3 ~/.claude/skills/paper-to-note/scripts/calibrate_widths.py \
--auto-center --tolerance 0 --limit-diffs 3000 --limit-issues 0 \
--vault "/Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes" \
| grep -E "<PaperTitle>"
Expected output is empty for the note we just created (no calibrate / no wrap / no wrap-line listed). If anything shows up:
[img ] / [line] lines = the <img> tag(s) are not wrapped in <div align="center">. Fix by wrapping each embed before saving.width N -> M lines = the <img> width does not match the --auto-width recommendation. Paste the recommended values back into the <img> tags and rerun.[width:skip-no-asset] lines = the note references a figure file that does not exist (broken ref); fix the filename or extract the missing asset before saving.Content checklist:
| Paper Concept | Source File | Key Class/Function | (§section-level granularity; line numbers are paper-to-skill's job)> **Code reference**: \branch` @ `short_sha` (date)` appears before the mapping tablegithub_ref property: set via Obsidian CLI (format: branch@short_sha)paper_to_skill property: set if matching skill found in ~/ai-skills/skills/<img> / ![[name|N]] width matches the per-figure --auto-width recommendation (or differs by ≤80 px with a working-notes justification). The calibrate_widths.py --auto-center --tolerance 0 dry-run above must report width calibrate: 0 and center wrap / wrap-line: 0 for this note.<img> is wrapped in a <div align="center">...</div> block (or its inline form for multi-<img> side-by-side lines). Bare <img> is a P13 regression.files/blogs/<date-slug>/.Directory structure (unchanged):
paper_notes/
├── notes/
│ └── <TopCategory>/
│ └── <SubCategory>/
│ └── <PaperTitle>.md ← reading notes
└── files/
└── <TopCategory>/
└── <SubCategory>/
└── <PaperTitle>/ ← extracted figures (PNG/SVG)
Vault hygiene check (MANDATORY): before notifying the user, confirm this run did not create scratch artifacts inside the vault:
VAULT="/Users/bytedance/Library/CloudStorage/OneDrive-个人/paper_notes"
find "$VAULT" \( -type f \( -name 'review_packet*.md' -o -name 'revew_packet*.md' -o -name 'review-packet*.md' \) -o -type d \( -name tmp -o -name _tmp -o -name _work -o -name 'review_packet*' \) \) -print
If the command shows artifacts created by the current run, move them to the external scratch directory or delete them before finalizing. If it shows pre-existing user artifacts, do not create more; mention the pre-existing paths separately.
Notify the user: print the top category, subcategory, hierarchical category tag, and the full file path after saving.
After saving, run at least one review round, but keep the review context small.
review_packet.md there (≤ ~120 lines by default; hard cap ~200 lines). Use $PAPER_TO_NOTE_WORKDIR/<paper-slug>/ if set, otherwise ${TMPDIR:-/tmp}/paper-to-note/<paper-slug>/. Never place the packet next to the note, under paper_notes/notes/, under paper_notes/files/, or under vault-level tmp/ / _tmp folders. The packet should contain: note path, image directory, paper/PDF path, source repo URL, github_ref, figure/table inventory, sections changed, exact unresolved risks, and reviewer scopes requested.$REVIEWER only as the reviewer instruction source; pass reviewers file paths + the compact packet, not the full paper, full note, full prior transcript, full skill text, or full source tree.<img> tags, LaTeX syntax.代码搜索未找到开源实现. Checks Mandatory Skeleton items 4–5, pseudocode vs actual code, training-config sourcing, paper-vs-code gaps.REQUEST_CHANGES: fix all P0/P1 issues, then run targeted re-review only for the affected scope(s). Do not re-run unrelated reviewers.APPROVE or only P2 style issues remain. If a second targeted re-review still has P0/P1 issues, notify the user with the remaining blocker instead of repeatedly spawning agents.This step is non-negotiable for quality, but repeated full-context review is forbidden.
<img> / ![[name|N]] MUST use the per-figure --auto-width recommendation. Hard-coded width="1000" for everything is a P12 regression. Verify with calibrate_widths.py --auto-center --tolerance 0 dry-run before considering the note done.<img> MUST be wrapped in a <div align="center"> ... </div> block (multi-line form for single-img lines, inline form for multi-img side-by-side lines). Bare <img> left-aligns and is a P13 regression. The same dry-run above also enforces this.\boldsymbol{...} over \bm{...} in note formulas; avoid macros that Obsidian/KaTeX commonly renders as raw red text unless the vault is known to support themThese are real bugs found in past notes. Check every note against this list.
```python instead of plain ``````python, closing fence is ALWAYS just ``` with nothing after itpage_5.png (a full PDF page) instead of cropping out just fig2_overview.png--arxiv mode as the default path; it extracts original LaTeX-source figures, preserves source raster dimensions, and prefers vector SVG for source PDFs--pdf --crop for that specific assetfig2_overview.png, table1_comparison.png, algo1_training.png — NOT page_5.png<img> tags or Obsidian image embedsFigure 3a/3b/3c as one row/grid, but the note embeds fig3a, fig3b, fig3c separately at width="1000".fig<N>_group.svg / fig<N>_abc.svg that preserves the source layout..tex figure environment for subfigure, subcaptionbox, minipage, tabular, or repeated \includegraphics.extract_figures.py --compose "fig3_group:row:a.svg,b.svg,c.svg" or crop the already-combined PDF figure.<img ... width="<recommended>"> for the composite (run --auto-width to get the value; per P12 this is typically 460–540 for portrait composites, NOT 1000) plus one Figure 3a–3c 解读 paragraph.Figure 3a, Figure 3b, Figure 3c). If found, replace with a grouped composite.<branch>@<short_sha> (date) as both a blockquote header before the mapping table and as the github_ref Obsidian propertygh api repos/<owner>/<repo>/commits/HEAD --jq '(.sha[:8]) + " (" + .commit.author.date[:10] + ")"'github_ref property exists in frontmatter and reference blockquote appears before the mapping table<img> tag and text on adjacent lines breaks inline LaTeX<img> tag on its own line followed by "Figure N 解读" text on the very next line (no blank line in between) causes $...$ inline math in the text to render as raw text instead of LaTeX$K$, $\ell_k$, $\pi_\theta$, etc.) in figure descriptions display as literal dollar-sign strings<img> line starts an HTML block; all subsequent lines until the next blank line are treated as raw HTML, where MathJax/KaTeX delimiters are not processed<img> tag and the following "Figure N 解读" paragraph<img src="..." alt="Figure 3" width="720">
Figure 3 解读:分别由 $K$ 个 reward models 打分…
<img src="..." alt="Figure 3" width="720">
Figure 3 解读:分别由 $K$ 个 reward models 打分…
<img ...> line is immediately followed by text on the next line without a blank line separatorwidth is correct.Stage | Config path | Key values into one row with long code paths and dozens of hyperparameters.review_packet*.md / revew_packet*.md, extracted paper text, reviewer scratch notes, cloned repos, or folders such as tmp/, _tmp/, _work/ inside paper_notes/.notes/ and final referenced figures/assets under files/; all review packets and scratch files must live outside the vault in $PAPER_TO_NOTE_WORKDIR/<paper-slug>/ or ${TMPDIR:-/tmp}/paper-to-note/<paper-slug>/.width="1000" for embedded figures<img> tag in the note uses width="1000" regardless of the figure's actual aspect ratio. This made sense for early notes that were mostly wide horizontal plots, but it now mixes badly with composite/portrait figures (e.g. fig*_group.svg, framework / overview diagrams whose viewBox is taller than wide).width="1000" renders ~1294 px tall in Obsidian and dominates an entire screen, while a 493×352 plot at the same width="1000" only renders ~715 px tall and looks fine. The visual mismatch is the symptom — the root cause is hard-coded width, not the figure itself.<img width=...> per figure based on its intrinsic geometry. Cap rendered height at ~520 px for normal figures (~624 px for hero / overview / framework / pipeline / architecture / _group figures with aspect < 1.3); back-solve width from aspect ratio; clamp to [360, 920] and round to 20 px. Wide horizontal plots can use 700–900; near-square figures 520–720; portrait/composite figures 420–540.python3 ~/.claude/skills/paper-to-note/scripts/extract_figures.py --auto-width <files-dir> (also runs automatically after --arxiv / --compose).width="<N>" value from the report into each <img> tag — do NOT edit numbers by feel.--rec-max-height 460 (more compact) or --rec-max-height 580 (looser); never silently revert to width="1000".<img> tag's width should match the script's recommendation for that file (or differ by ≤80 px with a one-line justification in the working notes). Multiple <img> tags all sharing the same width="1000" is a regression.<img> left-aligns in Obsidian<img src="..." alt="..." width="N"> on its own line, with no surrounding container.<img> is an inline element; without a block-level parent it hugs the left margin. Adjacent figures all line up against the left edge while the right side of the page sits empty, which looks lopsided next to the body text and misaligns "Figure N 解读" paragraphs that the reader expects to scan top-to-bottom under each figure.<!-- Single-figure line: multi-line block -->
<div align="center">
<img src="..." alt="..." width="<W>">
</div>
<!-- Multi-figure side-by-side line: inline block -->
<div align="center"><img src=".../a.png" alt="..." width="<Wa>"> <img src=".../b.png" alt="..." width="<Wb>"></div>
Obsidian-embed ![[fig|W]] should be wrapped the same way (with the embed on its own line surrounded by blank lines inside the <div>).<img> from extract_figures.py --auto-width already inside a <div align="center"> block.python3 ~/.claude/skills/paper-to-note/scripts/calibrate_widths.py \
--auto-center --tolerance 0 # dry-run, prints wrap plan
python3 ~/.claude/skills/paper-to-note/scripts/calibrate_widths.py \
--auto-center --apply # rewrite in place with backup
--auto-center automatically detects already-centered embeds (skips them), wraps standalone <img> lines as multi-line blocks, and wraps pure multi-<img> lines (e.g. <img a> <img b>) as inline blocks so the side-by-side layout is preserved. <img> tags that share a line with prose are flagged skip-inline-img and must be wrapped manually.calibrate_widths.py --auto-center --tolerance 0 must print center wrap: 0 and center wrap-line: 0 for the affected note. Any non-zero count is a P13 regression.curl / WebFetch / Defuddle output as the complete figure inventory for a blog or frontend-rendered article, then concluding "no figures" because raw HTML has no useful <img> tags.document.images, currentSrc, [role=img], .recharts-wrapper, svg, canvas, and figure-like containers. Do not include og:image, cover art, or thumbnails by default.paper_notes/files/blogs/<date-slug>/ with a semantic filename. After writing, verify referenced-local-assets count, missing refs, and unused files.<div align="center">, but Obsidian/theme rendering still makes the visual appear left-shifted or visually too dominant.<div align="center" style="text-align:center;">
<img src="../files/blogs/<date-slug>/fig_name.png" alt="Figure X" style="display:inline-block; width:760px; max-width:100%; height:auto;">
</div>
paper-to-skill skillfile:L<a>-L<b>) is not required here (that's paper-to-skill's job)