Back to Skills
yuan1z0825/nature-skillsCheck passed

SKILL DETAIL

nature-reader

yuan1z0825/nature-skills/nature-reader

This skill creates bilingual (Chinese-English) Markdown readers for academic papers, supporting input from PDF, DOI, arXiv, publisher HTML, or pasted text. It extracts figures, tables, and equations, placing them near relevant prose, and preserves source anchors for every block to ensure traceability. The workflow includes source mapping, translation, and terminology management, producing a complete bilingual Markdown file, a source map JSON, and translation notes. It does not degrade to summary-only output unless explicitly requested.

Installs · 151View source

Installation

npx skills add https://github.com/yuan1z0825/nature-skills --skill nature-reader

Skill files

SKILL.md

Last synced · Aug 27, 2026

agents/openai.yaml
interface:
  display_name: "Nature Reader"
  short_description: "Build bilingual, figure-aware full-paper readers"
  default_prompt: "Use $nature-reader to build a Chinese-English, figure-aware reader for this paper."
evals/evals.json
{
  "skill_name": "nature-reader",
  "evals": [
    {
      "id": 1,
      "prompt": "把这篇 Nature 风格 PDF 做成全文中英文对照阅读稿:不要只做摘要或前几页。每个正文段落要有 Original 和中文翻译,图表要裁剪出来并插到首次实质引用附近,英文图注和中文图注都要保留,每一段都要能点回原文位置。",
      "expected_output": "A full-paper Markdown reader with paragraph-level Original/中文 pairs, stable source anchors, cropped figure/table assets, figure cards near first substantive mentions, bilingual captions, and grounded follow-up pointers.",
      "files": ["/Users/yuanyz/nature-read/s41586-026-10452-4.pdf"]
    },
    {
      "id": 2,
      "prompt": "我只有一篇会议论文的 DOI。请先把它整理成一个可读的全文原文-译文对照 Markdown;如果拿不到全文,就明确标注缺口,并告诉我哪些内容只能从摘要或元数据确认。正文块必须保留 Original/中文 格式,不能只给摘要页。",
      "expected_output": "A full-document or draft-mode Markdown reader that clearly separates confirmed content from missing content, keeps block/page navigation, and preserves Original/中文 alignment for every available block.",
      "files": []
    },
    {
      "id": 3,
      "prompt": "这是一篇扫描版论文。请先做 OCR,再生成全文中英文对照 Markdown,保留图注和正文的对应关系;凡是识别不稳的地方都要标低置信度。图表需要裁剪到 assets 并放在对应正文位置。",
      "expected_output": "An OCR-aware full-document Markdown reader with Original/中文 pairs, uncertainty labels, cropped figure/table assets, nearby bilingual captions, page navigation, and source-linked follow-up support.",
      "files": []
    },
    {
      "id": 4,
      "prompt": "精读这篇含有大量公式的可检索 PDF。paper.md 里不要展示一行行的 LaTeX 源码;行内公式和独立公式都要直接渲染。每个独立公式要有 E 编号、页码、原论文公式编号和 source_map 记录,三个以上公式时增加公式索引。",
      "expected_output": "A bilingual full-paper reader whose inline and display equations use renderable Markdown math, whose display equations have stable E IDs and source-map entries, and whose equation index links to each formula without exposing bare LaTeX in prose.",
      "files": []
    },
    {
      "id": 5,
      "prompt": "这篇扫描论文的公式无法可靠 OCR。不要根据上下文猜公式:请把每个原始公式裁剪出来放进 assets/equations,在正文中显示原图;如果给 LaTeX 转写,必须标成低置信度并提示以原图为准。",
      "expected_output": "An image-first equation workflow with E IDs, existing equation crop paths, explicit low-confidence transcription labels, no guessed symbols, and source-map linkage for every displayed equation.",
      "files": []
    },
    {
      "id": 6,
      "prompt": "生成中英对照全文时,数学公式本体不要翻译或在中文段落里重写。显示公式只放一份;中文只解释含义。I_0、E_0、w_0、tau 等行内符号必须继续使用与英文相同的 LaTeX 和数学定界符,不能变成 (I_0)、(tau) 或普通文本。",
      "expected_output": "A bilingual reader with one shared copy of each display equation, unchanged mathematical notation in Chinese prose, valid $...$/$$...$$ delimiters, no parenthesized pseudo-math, and a strict validator pass.",
      "files": []
    }
  ]
}
manifest.yaml
name: nature-reader
version: 2.1.0
description: >
  Declarative manifest for the static/dynamic split. SKILL.md uses this to
  decide which fragments to load for a given paper-reading request. The main
  axis is the source format, which changes how text, figures, and tables are
  extracted before the bilingual reader is built.

always_load:
  # Shared layer — common to the nature-* skills
  - ../nature-shared/core/terminology-ledger.md
  # Skill-local core
  - static/core/principles.md
  - static/core/workflow.md
  - static/core/output-contract.md

axes:
  source_format:
    detect: |
      Determine the input form the user provided. Use pdf-text for a
      selectable-text PDF, scanned-pdf for an image-only or OCR-required PDF,
      html for a publisher or preprint HTML page, doi-arxiv for a bare DOI or
      arXiv identifier/link that must be resolved first, and pasted-text when
      the user pastes prose or notes with no retrievable original layout.
      If several apply (for example a DOI that resolves to a PDF), load the
      resolution fragment first, then the fragment for the resolved artifact.
    values:
      pdf-text:     static/fragments/source/pdf-text.md
      scanned-pdf:  static/fragments/source/scanned-pdf.md
      html:         static/fragments/source/html.md
      doi-arxiv:    static/fragments/source/doi-arxiv.md
      pasted-text:  static/fragments/source/pasted-text.md
    default: pdf-text
    multi: true

references:
  on_demand:
    - condition: cropping figures/tables, placement near first mention, tight-crop rules
      path: references/figure-extraction.md
    - condition: exact paper.md / source_map.json field schema and block shapes
      path: references/output-spec.md
    - condition: equations, mathematical expressions, chemical formulae, custom LaTeX macros, or image-only formulae
      path: references/equation-handling.md
    - condition: answering follow-up questions with source-grounded citations
      path: references/grounding-rules.md
    - condition: labelling the argumentative function of each section (gap/contribution/result/limits) and genre tells as a reading aid
      path: references/article-anatomy.md
README_EN.md
# `nature-reader` Skill

[中文说明](README.md)

`nature-reader` converts paper PDFs, DOIs, arXiv links, publisher HTML, or pasted text into traceable full-paper Markdown readers with Chinese-English text, figure/table placement, rendered equations, and a source map.

## What To Use It For

- Create full-paper Chinese-English Markdown readers.
- Insert figures, tables, and translated legends near the first substantive discussion.
- Build page and source anchors for every paragraph, figure, and table.
- Render equations as Markdown math and assign a stable `E...` anchor to every display equation.
- Preserve original equation crops when a scanned or image-only expression cannot be transcribed reliably.
- Extract reviewable reading materials from PDFs, HTML, or preprint text.
- Produce reading notes that can support later writing, presentations, or citation checks.

## Typical Requests

- "Turn this PDF into a full Chinese-English Markdown reader."
- "Translate and explain this paper, placing figures near the relevant text."
- "Use this DOI to get the paper content and generate a reader with a source map."

## What You Need To Provide

- PDF, DOI, arXiv link, publisher HTML, title, or pasted text.
- Output directory and whether image cropping is needed.
- Whether to keep English original text, Chinese translation, figures, tables, and translation notes.

## Outputs

- `paper.md`: full reading material.
- `source_map.json`: mapping for pages, text blocks, and figures/tables.
- `translation_notes.md`: terminology, uncertain content, and translation notes.
- `assets/`: figures, cropped images, and required attachments.
- `assets/equations/`: original crops for low-confidence or image-only equations when needed.

## Boundaries

- The default output is Markdown-centered; it does not generate an interactive Q&A panel by default.
- Scanned PDFs, two-column ordering, or copyright-restricted full text may need additional human checking; image-only equations retain an original crop and an explicit transcription confidence.
- If full text cannot be legally obtained, the skill states the limitation and works from available abstract/metadata.

## Related Skills

- `nature-downloader`: legally obtain PDF or HTML full text first.
- `nature-paper2ppt`: turn reading materials into Chinese presentation slides.
- `nature-citation`: extract claims that need citation support.
README.md
# `nature-reader` 技能

[English](README_EN.md)

`nature-reader` 用于把论文 PDF、DOI、arXiv、出版社 HTML 或粘贴文本转换为可回溯的全文 Markdown 阅读材料,包含中英对照、图表定位、可直接阅读的公式和 source map。

## 适合用它做什么

- 制作论文全文中英对照 Markdown。
- 在首次实质性讨论处插入对应图表和图注翻译。
- 为每段文本、图、表建立页码和来源锚点。
- 将公式渲染为 Markdown 数学块,并为每个独立公式建立 `E...` 锚点。
- 扫描件或无法可靠转写的公式会保留原图裁剪,不会用猜测出的 LaTeX 替代原式。
- 从 PDF、HTML 或预印本文本中提取可复核阅读材料。
- 生成适合后续写作、汇报或引用核查的阅读底稿。

## 典型请求

- “把这篇 PDF 做成中英对照全文 Markdown。”
- “翻译并解读这篇论文,图表要放在对应正文附近。”
- “根据 DOI 获取论文内容,生成带 source map 的阅读材料。”

## 你需要提供

- PDF、DOI、arXiv 链接、出版社 HTML、题名或粘贴文本。
- 输出目录和是否需要图片裁剪。
- 是否保留英文原文、中文翻译、图表、表格和翻译备注。

## 产出

- `paper.md`:全文阅读材料。
- `source_map.json`:页码、文本块和图表来源映射。
- `translation_notes.md`:术语、无法确认内容和翻译说明。
- `assets/`:图表、裁剪图片和必要附件。
- `assets/equations/`:低置信度或图片化公式的原始裁剪(按需生成)。

## 边界

- 默认以 Markdown 为中心,不默认生成交互式问答面板。
- 扫描 PDF、双栏错序或版权受限全文可能需要额外人工校对;图片化公式会优先显示原图并标记转写置信度。
- 无法合法获取全文时,会基于可用摘要/元数据说明限制。

## 相关技能

- `nature-downloader`:先合法获取 PDF 或 HTML 全文。
- `nature-paper2ppt`:把阅读材料转成中文汇报 PPT。
- `nature-citation`:从阅读材料中抽取需要引用支撑的 claim。
references/article-anatomy.md
# Article Anatomy — a reading aid (Nat Commun 2025 CS/AI corpus)

Use this file as a **reading aid** while building the bilingual reader, to label
the *argumentative function* of each block so the reader can locate the gap, the
contribution, the decisive result, and the self-contained figure legends. It is
distilled from a 2025 set of 20 open-access *Nature Communications* computer
science / AI papers across genres.

> This is an aid for **locating structure**, not a license to summarise. The
> `core/principles.md` contract still holds: translate every block for meaning,
> keep the bilingual side-by-side, and never degrade to a summary-only output.
> Use the function labels to help a reader navigate, e.g. in a short orientation
> note, not to replace the full translation.

## Where each function lives

- **Abstract = a funnel.** Read it as five moves: field value → gap (almost
  always after **However**) → hinge `Here we show/present X` → one quantified
  result → significance. The hinge sentence is the fastest way to state what the
  paper actually contributes.
- **Introduction = hook → gap → contribution.** Gap signal words to spot:
  **However / remains / Unfortunately / underexplored / the scarcity of … /
  Without X, Y cannot be …**. The contribution is the explicit `Here we… / In
  this work, we…` sentence, often with a `(Fig. 1)` pointer.
- **Results = conclusion-first.** Each paragraph opens with the judgement; the
  figure call (`Fig. Xa`, or `Figure X shows…`) and the numbers follow.
  Subheadings are often conclusions or method names, so the subheading list
  alone sketches the evidence ladder.
- **Figure/Table legends are self-contained.** `Fig. N | bold noun title`, then
  `a/b/c` panels, with `n=`, error type, and test written in. A legend's last
  sentence sometimes advances a claim (*"…indicating that the models are not
  predicting poses based on physics…"*) — flag it; it is interpretation, not
  description.
- **Discussion = restate contribution → why credible → wider meaning → limits
  (`Another limitation…` / `remains to be tested`) → future work.** The limits
  sentence is where the paper bounds its own claim.

## Genre tells (so the reader frames the paper correctly)

- **Research article**: own data, IMRaD, `we` + passive.
- **Review**: chapters by topic/modality synthesising others' citations, little
  own data, closes with `Conclusions and outlook`.
- **Perspective**: history/era hook, numbered argument, normative `X should…`,
  roadmap close.
- **Comment**: first-person `I`, rhetorical-question opening, analogy/history,
  no IMRaD, no figures.
- **Benchmark/framework**: the gap is "the field lacks an agreed standard";
  tables dominate; stresses community / reproducibility / versioning.

## 中文阅读提示

- 给读者的导航可标注每段功能(背景/空白/贡献/结果/局限),帮助快速抓论证骨架;
  但**不得**因此省略逐段对照翻译或退化为摘要。
- "然而 / 仍是挑战 / 鲜有研究 / 缺乏 / 没有 X 就无法 Y"是空白信号词;`Here we / 本文`
  之后是作者自述贡献。
- 图注通常自足(含 n、误差、检验);若图注末句给出推断结论,标注为"解读"而非"描述"。
- 先判定体裁(研究论文/综述/观点/评论/基准),再据此理解其组织方式与语气。
references/equation-handling.md
# Equation handling

## Contents

- [Goal](#goal)
- [Portable Markdown math](#portable-markdown-math)
- [Display-equation block](#display-equation-block)
- [Confidence ladder and visual fallback](#confidence-ladder-and-visual-fallback)
- [Equation index](#equation-index)
- [公式索引](#公式索引)
- [Source-map contract](#source-map-contract)
- [Compatibility fallback](#compatibility-fallback)


Load this reference whenever the source contains equations, mathematical expressions, chemical formulae, custom LaTeX macros, or image-only formulae.

## Goal

The reader must show a usable equation, not a line of raw LaTeX that forces the user back into the PDF. Preserve source fidelity and make uncertainty visible.

## Portable Markdown math

Use syntax supported by GitHub-flavoured Markdown renderers with MathJax:

- inline math: `$E = mc^2$`
- display math: put opening and closing `$$` on their own lines
- fenced `math` blocks are acceptable only when dollar signs inside the expression make `$$...$$` ambiguous

Do not put formulas in ordinary backticks or generic code fences. Do not leave commands such as `\frac`, `\sum`, `\alpha`, or `\begin{aligned}` in prose outside a math block.

Keep the publisher's printed equation number outside the math delimiters so the renderer cannot swallow or reposition it.

## Bilingual formula preservation

Do not translate mathematical content. Preserve formulas, symbols, indices, operators, Greek letters, and units exactly as they appear in the verified source. Translate only prose that introduces, defines, or interprets them.

For a display equation, emit one shared `E...` block. Do not repeat or rewrite the equation under `**中文:**`:

```markdown
**Original:** The peak intensity is

<a id="E001"></a>
**Source:** p.6 E001

$$
I_0=\frac{4E_0}{\pi w_0^2\tau}\sqrt{\frac{\ln(2)}{\pi}}
$$

**中文:** 峰值强度由上方原式给出。其中 $I_0$ 为峰值强度,$E_0$ 为脉冲能量,$w_0$ 为焦斑半径,$\tau$ 为脉宽。
```

Forbidden forms include `(I_0)`, `(E_0=...)`, `(tau)`, `(Delta T)`, translated variable names, Unicode approximations that change the source notation, and bare LaTeX outside math delimiters. Ordinary parentheses remain valid for prose labels such as `Fig. 2(a)`; they must not be used as a substitute for `$...$`.

## Display-equation block

Assign every display equation a stable `E...` ID in reading order:

```markdown
<a id="E001"></a>
**Source:** p.4 E001 · Eq. (3)

$$
\mathcal{L}(\theta) = \sum_{i=1}^{n} \log p(y_i \mid x_i, \theta)
$$

**中文说明:** 该式定义了训练目标;符号和变量名称保持原样。
```

Translate or explain surrounding prose, but do not translate variable names or alter mathematical meaning. If the source has no printed equation number, omit `Eq. (...)`; never invent one.

## Confidence ladder and visual fallback

Use the highest trustworthy representation:

1. **High confidence:** publisher MathML/LaTeX or source TeX verified against the rendered page. Emit the rendered math block.
2. **Medium confidence:** reconstructed from a selectable PDF and visually checked. Emit the math block and record `confidence: "medium"`.
3. **Low confidence or image-only:** crop the original equation into `assets/equations/E001.png` (or `.svg`), show the crop first, and add best-effort LaTeX only when it is useful. Label it `低置信度转写 / Low-confidence transcription`.

Example fallback:

```markdown
<a id="E001"></a>
**Source:** p.4 E001 · Eq. (3) · low confidence

![Original equation E001](assets/equations/E001.png)

**低置信度转写(请以原图为准):**

$$
\widetilde{f}(x) \approx \sum_k a_k \phi_k(x)
$$
```

Never infer an unreadable symbol from context. When a useful transcription cannot be produced, keep the crop and write `LaTeX transcription unavailable` rather than emitting invented math.

## Equation index

When the reader contains three or more display equations, add a compact index near the page/section index:

```markdown
## 公式索引

- [E001 · Eq. (1)](#E001) — p.3,损失函数
- [E002 · Eq. (2)](#E002) — p.4,更新规则
```

The label may add a short reader-facing description, but it must not replace the source equation number.

## Source-map contract

Each display equation must have a matching `blocks` entry of `type: "equation"`:

```json
{
  "id": "E001",
  "page": 4,
  "type": "equation",
  "order": 17,
  "equation_number": "3",
  "latex": "\\mathcal{L}(\\theta) = \\sum_{i=1}^{n} ...",
  "bbox": [88, 214, 513, 286],
  "confidence": "high",
  "image_path": null
}
```

For a visual fallback, set `image_path` to the relative crop path and retain `latex` only if a best-effort transcription is shown. `page`, `confidence`, and at least one of `latex` or `image_path` are required.

## Compatibility fallback

Markdown remains the primary artifact. If the user's target viewer does not support math or they report seeing raw LaTeX, generate `reader.html` as an additional artifact with KaTeX or MathJax and keep the same `E...` anchors. Do not silently replace `paper.md`.

Before delivery, run:

```bash
python scripts/validate_reader_math.py paper.md --source-map source_map.json
```
references/figure-extraction.md
# Figure and table extraction

Open this reference when extracting and placing figures or tables. It expands step 4 of the reading workflow.

## Placement near the relevant discussion

Do not try to recreate the PDF pixel-for-pixel. Preserve semantic proximity instead.

Default placement rule:

- crop each figure/table into `assets/` and show it near its first substantive mention in the body text
- keep the caption attached to the figure/table
- show both original caption and Chinese caption translation
- if the caption contains critical details, keep caption and figure together
- if a table is central to the claim, keep it near the paragraph that interprets it
- if a figure/table appears before the body discussion in PDF layout, still place it where it best supports the reading flow and add `Placed near: p.X SYYY`
- if a later section mentions the same figure/table again, link back to the already inserted figure/table block instead of duplicating it

If the paper has a complex multi-column layout, prefer a clean reading layout over exact visual mimicry.

## Crop figures and tables tightly

When extracting a figure or table image:

- crop only the figure or table content area, not the whole page
- use the smallest rectangle that fully contains the visual object
- exclude page headers, footers, surrounding prose, and unrelated margins
- keep the caption separate unless the caption is part of the requested visual crop
- if the crop box is uncertain, mark it as approximate instead of enlarging it

Precision matters more than convenience here. A slightly smaller but correct crop is better than a wider crop that includes unrelated page content.

## Figure/table block shape

Figure/table blocks in `paper.md` should use this shape:

```markdown
<a id="F001"></a>
### Fig. 1. [short translated title]

**Placed near:** p.3 S012
**Source:** p.4 C001

![Fig. 1](assets/fig1.png)

**Original caption:** [caption text]

**中文图注:** [caption translation]

**Reading note:** [brief explanation of what to inspect in the figure]
```
references/grounding-rules.md
# Grounding Rules

## Answering questions

When the user asks a follow-up question about the paper:

1. Find the most relevant source blocks.
2. Answer from those blocks first.
3. Cite the exact page and block IDs.
4. Include the figure or table if it is part of the evidence.
5. Say `原文未明确说明` if the paper does not support the claim.

## Good answer pattern

- `结论`
- `原文依据: p.5 S014-S016, Fig. 3 caption`
- `补充说明: 这是译文中的概括,不是原文逐字表述`

## Bad answer pattern

- vague paraphrase without source IDs
- answer based only on the title or abstract when the question needs body text
- claiming support from a figure without citing the figure or caption
- inventing missing detail when OCR or extraction is uncertain

## Translation rules

- Keep specialized terms stable.
- Keep equations, units, symbols, and citations unchanged.
- Do not over-simplify method steps.
- If a term has no clear Chinese equivalent, keep the original term and add a short note.
- Preserve paragraph-level original/Chinese alignment in `paper.md`.
- Do not convert a full-paper translation request into a Chinese-only summary or critique.
- If a full English paragraph cannot be included because of source restrictions or extraction failure, keep the block anchor and explain the limitation in `translation_notes.md`.

## Figure and table rules

- Cite the caption when explaining a figure.
- Cite the relevant table row or table block when explaining a table.
- If the claim relies on both text and figure, cite both.
- If figure placement is uncertain, mark it as a layout approximation.
- Extract figures/tables to `assets/` whenever possible.
- Place each figure/table card near the first substantive mention in the bilingual text.
- Include original caption, Chinese caption translation, and a short reading note.
- Do not use whole-page screenshots as figure/table replacements unless no tighter crop is possible; mark those as approximate.
references/output-spec.md
# Output Spec

## Contents

- [Standard bundle](#standard-bundle)
- [Full-text mode](#full-text-mode)
- [source_map.json](#source_mapjson)
- [paper.md](#papermd)
- [reader.html](#readerhtml)
- [Layout rules](#layout-rules)
- [Figure/table card format](#figuretable-card-format)
- [Citation format in the page](#citation-format-in-the-page)


## Standard bundle

Produce these files when possible:

- `paper.md`
- `source_map.json`
- `translation_notes.md`
- `assets/` for extracted images, crops, figure snippets, and equation fallbacks
- `reader.html` only when the user explicitly asks for a browser preview

## Full-text mode

When the source is a full paper, include all pages or all extractable sections in the reader. Do not limit the bundle to selected pages, a teaser excerpt, or the abstract unless the user explicitly requests a preview.

`paper.md` is the primary deliverable. It must expose paragraph-level bilingual alignment:

```markdown
<a id="S001"></a>
**Source:** p.1 S001

**Original:** ...

**中文:** ...
```

For source material that cannot be extracted or translated confidently, keep the source anchor and write a visible uncertainty note instead of dropping the block.

## `source_map.json`

Keep a stable source map so follow-up questions can cite the same anchors.

```json
{
  "paper": {
    "title": "",
    "venue": "",
    "source_type": "pdf|html|doi|arxiv|text",
    "language": "en",
    "source_path": ""
  },
  "blocks": [
    {
      "id": "S001",
      "page": 1,
      "type": "heading|paragraph|caption|table|table_row|equation|note",
      "order": 1,
      "original_text": "",
      "translation": "",
      "bbox": [0, 0, 0, 0],
      "confidence": "high|medium|low",
      "refs": ["F001", "T001"],
      "insert_after": "S001"
    }
  ],
  "pages": [
    {
      "page": 1,
      "block_ids": ["S001", "S002", "S003", "C001"]
    }
  ],
  "figures": [
    {
      "id": "F001",
      "page": 3,
      "caption_id": "C001",
      "image_path": "",
      "bbox": [0, 0, 0, 0],
      "placement_hint": "near_first_mention",
      "placed_after": "S012",
      "alt_text": ""
    }
  ],
  "equations": [
    {
      "id": "E001",
      "page": 4,
      "equation_number": "3",
      "latex": "\\mathcal{L}(\\theta) = \\sum_i ...",
      "bbox": [0, 0, 0, 0],
      "confidence": "high|medium|low",
      "image_path": null
    }
  ],
  "glossary": [
    {
      "term": "",
      "translation": "",
      "note": ""
    }
  ]
}
```

## `paper.md`

The Markdown reader should support:

- stable headings in paper order
- paragraph-level original/Chinese pairs
- source IDs on every substantive block
- figure/table cards near the relevant prose
- rendered display equations with stable `E...` anchors
- an equation index when there are three or more display equations
- English captions and Chinese caption translations
- page navigation for full papers
- terminology notes and uncertainty notes

Use `$...$` for inline math and `$$...$$` for display math. For a low-confidence or image-only equation, show the original crop from `assets/equations/` and label any accompanying LaTeX as a low-confidence transcription. See `references/equation-handling.md` for the exact block shape.

## `reader.html`

The page should support:

- desktop side-by-side original and translation
- mobile stacked layout
- clickable source IDs on every block
- rendered equations and equation-index links that retain the Markdown `E...` anchors
- figure cards near the relevant text
- section navigation
- page navigation for full papers

Do not add a question area unless explicitly requested.

## Layout rules

- Keep paragraph alignment stable.
- Keep captions attached to their figures.
- Show tables where they are explained, not only where they appear in the PDF.
- Prefer semantic proximity over exact visual reconstruction.
- If a figure is referenced across multiple sections, anchor it at the first substantive discussion and link later mentions back to it.
- Crop figures and tables with the tightest valid bounding box.
- Do not use a full-page screenshot when the actual content occupies a smaller region.
- If the exact crop box cannot be verified, label the crop as approximate.
- For full papers, preserve page order and include a page index.
- Keep printed equation numbers outside math delimiters and never invent missing equation numbers.

## Figure/table card format

```markdown
<a id="F001"></a>
### Fig. 1. 中文短标题

**Placed near:** p.3 S012
**Source:** p.4 C001

![Fig. 1](assets/fig1.png)

**Original caption:** ...

**中文图注:** ...

**Reading note:** ...
```

Every image/table asset must have a corresponding card in `paper.md`. Every card must identify the source caption and placement block.

## Citation format in the page

Use short, stable source pointers:

- `p.7 S021`
- `p.8 C003`
- `Fig. 2`
- `Table 1`
- `p.4 E001` or `Eq. (3)`

For follow-up answers, combine page and block ID when available.
scripts/validate_reader_math.py
#!/usr/bin/env python3
"""Validate equation rendering and traceability in a nature-reader bundle."""

from __future__ import annotations

import argparse
import json
import re
import sys
import tempfile
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any


EQUATION_ID_RE = re.compile(r"^E\d{3,}$")
EQUATION_ANCHOR_RE = re.compile(
    r"<a\s+(?:id|name)=[\"'](E\d{3,})[\"']\s*></a>", re.IGNORECASE
)
GENERIC_FENCE_RE = re.compile(r"^\s*(`{3,}|~{3,})([^\n]*)$", re.MULTILINE)
LATEX_COMMAND_RE = re.compile(
    r"\\(?:frac|dfrac|tfrac|sum|prod|int|oint|sqrt|begin|end|left|right|"
    r"alpha|beta|gamma|delta|theta|lambda|mu|sigma|omega|partial|nabla|"
    r"mathcal|mathrm|mathbf|operatorname|overline|underline|widetilde|hat)\b"
)
MARKDOWN_IMAGE_RE = re.compile(r"!\[[^\]]*]\(([^)]+)\)")
CHINESE_LINE_RE = re.compile(
    r"^\s*\*\*中文(?:说明|图注)?[::]\*\*.*$", re.MULTILINE
)
PAREN_PSEUDO_MATH_RE = re.compile(
    r"\((?:[^()\n]*\\[A-Za-z]+[^()\n]*|"
    r"[A-Za-z][A-Za-z0-9]*_(?:\{[^}]+\}|[A-Za-z0-9]+)|"
    r"[^()\n]*=[^()\n]*)\)"
)


@dataclass(frozen=True)
class Finding:
    severity: str
    code: str
    message: str
    line: int | None = None


def line_number(text: str, offset: int) -> int:
    return text.count("\n", 0, offset) + 1


def mask_ranges(text: str, ranges: list[tuple[int, int]]) -> str:
    chars = list(text)
    for start, end in ranges:
        for index in range(max(0, start), min(len(chars), end)):
            if chars[index] != "\n":
                chars[index] = " "
    return "".join(chars)


def fenced_ranges(
    text: str,
) -> tuple[list[tuple[int, int]], list[tuple[int, int]], list[Finding]]:
    """Return all fenced ranges, math-fence ranges, and fence errors."""
    ranges: list[tuple[int, int]] = []
    math_ranges: list[tuple[int, int]] = []
    findings: list[Finding] = []
    open_fence: tuple[str, int, int, int, bool] | None = None

    for match in GENERIC_FENCE_RE.finditer(text):
        marker = match.group(1)
        if open_fence is None:
            language = match.group(2).strip().split(maxsplit=1)[0].lower() if match.group(2).strip() else ""
            open_fence = (marker[0], len(marker), match.start(), match.end(), language in {"math", "latex"})
            continue
        char, length, start, body_start, is_math = open_fence
        if marker[0] == char and len(marker) >= length:
            ranges.append((start, match.end()))
            if is_math:
                math_ranges.append((start, match.end()))
                if not text[body_start : match.start()].strip():
                    findings.append(
                        Finding(
                            "FAIL",
                            "EMPTY_DISPLAY_MATH",
                            "Fenced math block is empty.",
                            line_number(text, start),
                        )
                    )
            open_fence = None

    if open_fence is not None:
        findings.append(
            Finding(
                "FAIL",
                "UNCLOSED_FENCE",
                "Markdown contains an unclosed fenced code/math block.",
                line_number(text, open_fence[2]),
            )
        )
        ranges.append((open_fence[2], len(text)))
    return ranges, math_ranges, findings


def display_math_ranges(text: str) -> tuple[list[tuple[int, int]], list[Finding]]:
    """Locate $$ blocks after generic fenced blocks have been masked."""
    ranges: list[tuple[int, int]] = []
    findings: list[Finding] = []
    tokens = list(re.finditer(r"(?<!\\)\$\$", text))
    if len(tokens) % 2:
        token = tokens[-1]
        findings.append(
            Finding(
                "FAIL",
                "UNBALANCED_DISPLAY_MATH",
                "Display-math delimiter '$$' is not balanced.",
                line_number(text, token.start()),
            )
        )
    for index in range(0, len(tokens) - 1, 2):
        start = tokens[index].start()
        end = tokens[index + 1].end()
        body = text[tokens[index].end() : tokens[index + 1].start()].strip()
        if not body:
            findings.append(
                Finding(
                    "FAIL",
                    "EMPTY_DISPLAY_MATH",
                    "Display-math block is empty.",
                    line_number(text, start),
                )
            )
        ranges.append((start, end))
    return ranges, findings


def find_inline_dollar_problem(text: str) -> Finding | None:
    offsets = [match.start() for match in re.finditer(r"(?<!\\)(?<!\$)\$(?!\$)", text)]
    if len(offsets) % 2:
        offset = offsets[-1]
        return Finding(
            "FAIL",
            "UNBALANCED_INLINE_MATH",
            "Inline-math delimiter '$' is not balanced.",
            line_number(text, offset),
        )
    return None


def validate_markdown(text: str) -> tuple[list[Finding], list[str], list[str]]:
    findings: list[Finding] = []
    fence_ranges, math_fence_ranges, fence_findings = fenced_ranges(text)
    findings.extend(fence_findings)
    without_fences = mask_ranges(text, fence_ranges)

    display_ranges, display_findings = display_math_ranges(without_fences)
    findings.extend(display_findings)

    normalized_displays: dict[str, list[int]] = {}
    for start, end in display_ranges:
        body = without_fences[start + 2 : end - 2].strip()
        normalized = re.sub(r"\s+", "", body).rstrip(",.;")
        if normalized:
            normalized_displays.setdefault(normalized, []).append(start)
    for offsets in normalized_displays.values():
        if len(offsets) > 1:
            findings.append(
                Finding(
                    "WARN",
                    "DUPLICATE_DISPLAY_EQUATION",
                    "The same display equation appears more than once; use one shared E... block for the bilingual pair.",
                    line_number(text, offsets[1]),
                )
            )

    prose = mask_ranges(without_fences, display_ranges)

    inline_problem = find_inline_dollar_problem(prose)
    if inline_problem:
        findings.append(inline_problem)

    # Mask inline math before looking for raw commands in prose.
    inline_tokens = list(re.finditer(r"(?<!\\)(?<!\$)\$(?!\$)", prose))
    inline_ranges = [
        (inline_tokens[i].start(), inline_tokens[i + 1].end())
        for i in range(0, len(inline_tokens) - 1, 2)
    ]
    plain_prose = mask_ranges(prose, inline_ranges)
    for match in LATEX_COMMAND_RE.finditer(plain_prose):
        findings.append(
            Finding(
                "FAIL",
                "BARE_LATEX",
                f"LaTeX command '{match.group(0)}' appears outside a math block.",
                line_number(text, match.start()),
            )
        )

    for line_match in CHINESE_LINE_RE.finditer(plain_prose):
        line_text = line_match.group(0)
        if "\t" in line_text:
            findings.append(
                Finding(
                    "FAIL",
                    "TAB_IN_CHINESE_MATH_PROSE",
                    "A tab appears in a Chinese line; this can indicate a corrupted command such as \\tau.",
                    line_number(text, line_match.start()),
                )
            )
        for pseudo_match in PAREN_PSEUDO_MATH_RE.finditer(line_text):
            findings.append(
                Finding(
                    "FAIL",
                    "PARENTHESIZED_PSEUDO_MATH",
                    f"Use math delimiters instead of ordinary parentheses: {pseudo_match.group(0)!r}.",
                    line_number(text, line_match.start() + pseudo_match.start()),
                )
            )

    anchors = EQUATION_ANCHOR_RE.findall(text)
    duplicates = sorted({item for item in anchors if anchors.count(item) > 1})
    for equation_id in duplicates:
        findings.append(
            Finding("FAIL", "DUPLICATE_EQUATION_ID", f"Duplicate equation anchor: {equation_id}.")
        )

    if anchors:
        numeric = [int(item[1:]) for item in anchors]
        expected = list(range(1, len(numeric) + 1))
        if numeric != expected:
            findings.append(
                Finding(
                    "WARN",
                    "NONSEQUENTIAL_EQUATION_IDS",
                    "Equation anchors should follow reading order without gaps: E001, E002, ...",
                )
            )

    display_count = len(display_ranges) + len(math_fence_ranges)
    if display_count and not anchors:
        findings.append(
            Finding(
                "FAIL",
                "MISSING_EQUATION_ANCHORS",
                "Display equations exist, but paper.md has no E... anchors.",
            )
        )
    if display_count >= 3 and not re.search(r"^#{1,6}\s+(?:公式索引|Equation Index)\s*$", text, re.MULTILINE | re.IGNORECASE):
        findings.append(
            Finding(
                "WARN",
                "MISSING_EQUATION_INDEX",
                "Three or more display equations require an equation index.",
            )
        )

    image_paths = [match.group(1).strip().split()[0].strip("<>") for match in MARKDOWN_IMAGE_RE.finditer(text)]
    return findings, anchors, image_paths


def equation_entries(data: dict[str, Any]) -> list[dict[str, Any]]:
    entries: dict[str, dict[str, Any]] = {}
    for item in data.get("blocks", []):
        if isinstance(item, dict) and item.get("type") == "equation" and isinstance(item.get("id"), str):
            entries[item["id"]] = item
    for item in data.get("equations", []):
        if isinstance(item, dict) and isinstance(item.get("id"), str):
            entries[item["id"]] = {**entries.get(item["id"], {}), **item}
    return list(entries.values())


def validate_source_map(
    source_map_path: Path, anchors: list[str], markdown_image_paths: list[str]
) -> list[Finding]:
    findings: list[Finding] = []
    try:
        data = json.loads(source_map_path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        return [Finding("FAIL", "INVALID_SOURCE_MAP", f"Cannot parse source map: {exc}")]

    if not isinstance(data, dict):
        return [Finding("FAIL", "INVALID_SOURCE_MAP", "Source map root must be a JSON object.")]

    entries = equation_entries(data)
    map_ids = [item.get("id") for item in entries]
    duplicates = sorted({item for item in map_ids if map_ids.count(item) > 1})
    for equation_id in duplicates:
        findings.append(Finding("FAIL", "DUPLICATE_SOURCE_MAP_ID", f"Duplicate source-map ID: {equation_id}."))

    for item in entries:
        equation_id = item.get("id")
        if not isinstance(equation_id, str) or not EQUATION_ID_RE.fullmatch(equation_id):
            findings.append(Finding("FAIL", "INVALID_EQUATION_ID", f"Invalid equation ID: {equation_id!r}."))
            continue
        if equation_id not in anchors:
            findings.append(
                Finding(
                    "FAIL",
                    "SOURCE_MAP_ORPHAN",
                    f"Source-map equation {equation_id} has no matching paper.md anchor.",
                )
            )
        if not isinstance(item.get("page"), int) or item["page"] < 1:
            findings.append(Finding("FAIL", "MISSING_PAGE", f"{equation_id} needs a positive page number."))
        if item.get("confidence") not in {"high", "medium", "low"}:
            findings.append(
                Finding("FAIL", "MISSING_CONFIDENCE", f"{equation_id} needs high, medium, or low confidence.")
            )
        latex = item.get("latex")
        image_path = item.get("image_path")
        if not (isinstance(latex, str) and latex.strip()) and not (
            isinstance(image_path, str) and image_path.strip()
        ):
            findings.append(
                Finding("FAIL", "MISSING_REPRESENTATION", f"{equation_id} needs latex or image_path.")
            )
        if isinstance(image_path, str) and image_path.strip():
            candidate = (source_map_path.parent / image_path).resolve()
            if not candidate.is_file():
                findings.append(
                    Finding("FAIL", "MISSING_EQUATION_IMAGE", f"{equation_id} image does not exist: {image_path}.")
                )
            if image_path not in markdown_image_paths:
                findings.append(
                    Finding(
                        "FAIL",
                        "UNUSED_EQUATION_IMAGE",
                        f"{equation_id} image_path is not displayed in paper.md: {image_path}.",
                    )
                )

    for equation_id in anchors:
        if equation_id not in map_ids:
            findings.append(
                Finding(
                    "FAIL",
                    "MISSING_SOURCE_MAP_ENTRY",
                    f"paper.md equation {equation_id} has no matching source-map equation entry.",
                )
            )
    return findings


def run_validation(paper_path: Path, source_map_path: Path | None) -> list[Finding]:
    try:
        text = paper_path.read_text(encoding="utf-8")
    except OSError as exc:
        return [Finding("FAIL", "UNREADABLE_MARKDOWN", f"Cannot read paper.md: {exc}")]
    findings, anchors, image_paths = validate_markdown(text)
    if source_map_path is not None:
        findings.extend(validate_source_map(source_map_path, anchors, image_paths))
    elif anchors:
        findings.append(
            Finding(
                "WARN",
                "SOURCE_MAP_NOT_CHECKED",
                "Equation anchors exist, but --source-map was not provided.",
            )
        )
    return findings


def self_test() -> int:
    with tempfile.TemporaryDirectory() as directory:
        root = Path(directory)
        (root / "assets/equations").mkdir(parents=True)
        (root / "assets/equations/E002.png").write_bytes(b"test")
        good_markdown = """# Paper
<a id="E001"></a>
**Source:** p.1 E001 · Eq. (1)

$$
\\frac{a}{b} = c
$$

**中文:** 其中 $a$、$b$ 和 $c$ 保持原始数学符号。

<a id="E002"></a>
**Source:** p.2 E002 · low confidence

![Original equation E002](assets/equations/E002.png)
"""
        good_map = {
            "blocks": [
                {"id": "E001", "type": "equation", "page": 1, "confidence": "high", "latex": "\\\\frac{a}{b}=c", "image_path": None},
                {"id": "E002", "type": "equation", "page": 2, "confidence": "low", "latex": None, "image_path": "assets/equations/E002.png"},
            ]
        }
        paper = root / "paper.md"
        source_map = root / "source_map.json"
        paper.write_text(good_markdown, encoding="utf-8")
        source_map.write_text(json.dumps(good_map), encoding="utf-8")
        good_findings = run_validation(paper, source_map)
        if good_findings:
            print("Self-test failed: valid fixture was rejected.", file=sys.stderr)
            for item in good_findings:
                print(asdict(item), file=sys.stderr)
            return 1

        bad_markdown = """# Broken
<a id="E001"></a>
Raw formula: \\frac{a}{b}
**中文:** 其中 (I_0) 为峰值强度。
$$
x+y
"""
        paper.write_text(bad_markdown, encoding="utf-8")
        bad_findings = run_validation(paper, None)
        bad_codes = {item.code for item in bad_findings if item.severity == "FAIL"}
        required = {"BARE_LATEX", "UNBALANCED_DISPLAY_MATH", "PARENTHESIZED_PSEUDO_MATH"}
        if not required.issubset(bad_codes):
            print(f"Self-test failed: missing expected failures {sorted(required - bad_codes)}.", file=sys.stderr)
            return 1

    print("Self-test passed.")
    return 0


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("paper", nargs="?", type=Path, help="Path to paper.md")
    parser.add_argument("--source-map", type=Path, help="Path to source_map.json")
    parser.add_argument("--strict", action="store_true", help="Treat warnings as validation failures")
    parser.add_argument("--json", action="store_true", dest="json_output", help="Print machine-readable JSON")
    parser.add_argument("--self-test", action="store_true", help="Run built-in valid and invalid fixtures")
    args = parser.parse_args()
    if not args.self_test and args.paper is None:
        parser.error("paper is required unless --self-test is used")
    return args


def main() -> int:
    args = parse_args()
    if args.self_test:
        return self_test()

    findings = run_validation(args.paper, args.source_map)
    failed = any(item.severity == "FAIL" or (args.strict and item.severity == "WARN") for item in findings)
    payload = {
        "ready": not failed,
        "strict": args.strict,
        "summary": {
            "fail": sum(item.severity == "FAIL" for item in findings),
            "warn": sum(item.severity == "WARN" for item in findings),
        },
        "findings": [asdict(item) for item in findings],
    }
    if args.json_output:
        print(json.dumps(payload, ensure_ascii=False, indent=2))
    elif findings:
        for item in findings:
            location = f" line {item.line}" if item.line else ""
            print(f"[{item.severity}] {item.code}{location}: {item.message}")
        print(f"Ready: {'yes' if not failed else 'no'}")
    else:
        print("No math validation findings. Ready: yes")
    return 1 if failed else 0


if __name__ == "__main__":
    raise SystemExit(main())
SKILL.md
---
name: nature-reader
description: Build full-paper Chinese-English side-by-side, figure/table/equation-aware, source-grounded Markdown readers for journal or conference papers from PDF, DOI, arXiv, publisher HTML, or pasted text. Use whenever the user asks to translate or read a paper, make 中英文对照/原文对照/全文翻译解读, render equations instead of exposing raw LaTeX, extract figures or tables into the right positions, preserve figure/table placement near relevant prose, or keep exact source anchors for every block. This skill must not degrade into a summary-only output unless the user explicitly asks for a summary. Also trigger on general paper-reading and translation requests even without the word "Nature", such as reading/translating an academic paper, literature reading, understanding a paper, and Chinese phrasings like 读论文、精读论文、论文翻译、文献翻译、文献阅读、学术阅读、帮我读这篇文章、翻译这篇paper.
metadata:
  version: "2.1.1"
  author: Community contribution, refactored into static/dynamic layers
---

# Full-Paper Markdown Reader — Router

This skill is split into two layers:

- A **static layer** under `static/` that holds versioned, reusable content fragments (core principles, the reading workflow, the output contract, and per-source-format extraction guidance).
- A **dynamic layer** (this file plus `manifest.yaml`) that detects the request's source format and loads only the fragments needed for the current job.

Do not try to apply the reading logic from memory or from this router. Always load fragments from disk as described below.

## Routing protocol

Follow these five steps every time the skill is invoked.

### 1. Load the manifest and the core layer

Read [manifest.yaml](manifest.yaml). It declares the `source_format` axis, the allowed values, and the file paths each value maps to.

Also read every file listed under `always_load`. These hold the core principles, the reading workflow, and the output contract that apply to every reading job, plus the shared Terminology Ledger used to build the recurring-term table.

### 2. Detect the source format

Decide the `source_format` value using the manifest's `detect:` hint and the user's input:

- `pdf-text` — selectable-text PDF. Default.
- `scanned-pdf` — image-only or OCR-required PDF.
- `html` — publisher or preprint HTML page.
- `doi-arxiv` — a bare DOI or arXiv link that must be resolved first.
- `pasted-text` — pasted prose or notes with no retrievable original layout.

State the detected value in one short line to the user before processing, so they can correct you cheaply. A source may map to more than one value (for example a DOI that resolves to a PDF); load the resolution fragment first, then the fragment for the resolved artifact.

### 3. Load the matching fragment(s)

Read the file mapped for the detected `source_format`. Do **not** read every fragment in `static/`. Load only what step 2 selected.

### 4. Build the reader using the loaded material

Apply the loaded fragments in this priority order:

1. Core principles (`core/principles.md`) — bilingual reader by default, translate for meaning, never degrade to a summary, copyright caution.
2. Source-format fragment — how to extract text, figures, and tables for this input.
3. Reading workflow (`core/workflow.md`) — the six-step source-map-first process.
4. Output contract (`core/output-contract.md`) — required files and the pre-response verification checklist.

Build the Terminology Ledger as you translate (`../nature-shared/core/terminology-ledger.md`); it becomes the `paper.md` recurring-term table and the `source_map.json` glossary.

If constraints prevent full processing, still create a draft reader and label missing pages, figures, or low-confidence crops in `translation_notes.md`. Do not switch to summary mode.

### 5. Reach for references only when needed

The files under `references/` are deep references, not defaults. Open them on demand per the `references.on_demand` table in the manifest:

- detailed figure/table cropping and placement → `references/figure-extraction.md`.
- exact field schema for `paper.md` / `source_map.json` → `references/output-spec.md`.
- equations, mathematical expressions, chemical formulae, or image-only formulae → `references/equation-handling.md`.
- answering follow-up questions with source citations → `references/grounding-rules.md`.

## Why this split

- The static layer is versioned and reviewable. Adding a new source format is one new fragment plus one manifest line.
- The dynamic layer keeps each invocation cheap: only the fragment relevant to this input enters context.
- The router itself is short on purpose. Update fragments, not this file, when adding scope.
- This structure mirrors `nature-writing` and `nature-polishing` so shared content lives in `nature-shared/`.
static/core/output-contract.md
# Output contract

Prefer these outputs:

- `paper.md` for the full-paper Markdown artifact
- `source_map.json` for stable source anchors
- `translation_notes.md` for terminology, uncertainty, and layout notes
- `assets/` for extracted figures, tables, and equation crops when needed
- `reader.html` only when the user explicitly wants a browser preview

Do not hide missing information. If the source is incomplete, label the output as draft mode.

## Pre-response verification

Before final response, verify:

- `paper.md` contains `**Original:**` and `**中文:**` block pairs
- every image/table link used in `paper.md` exists under `assets/`
- every figure/table in `assets/` has a corresponding Markdown block and source pointer
- display equations render inside `$$...$$` (or a fenced `math` block), and inline equations render inside `$...$`
- mathematical content is unchanged across the bilingual explanation: only prose is translated, each display equation is shown once, and Chinese text never uses `(I_0)`-style pseudo-math
- no bare LaTeX commands such as `\\frac`, `\\sum`, or `\\begin{...}` appear as ordinary prose
- every display equation has an `E...` anchor and a matching equation entry in `source_map.json`
- every low-confidence or image-only equation points to an existing file under `assets/equations/`
- `source_map.json` parses as JSON and includes source block IDs
- `translation_notes.md` records skipped, uncertain, or draft-mode content

Run the deterministic math check before delivery:

```bash
python scripts/validate_reader_math.py paper.md --source-map source_map.json
```

Add `--strict` for a published or reusable artifact. The command checks delimiters, bare LaTeX, equation IDs, source-map linkage, and equation fallback paths.

## Tooling guidance

- If the input is a PDF, load the `pdf` skill first for extraction and OCR guidance.
- If the user asks for a richer browser view, use `web-artifacts-builder` or `frontend-design` only as a preview layer on top of the Markdown workflow.
- If the user wants citation-level grounding to original text, keep the source map explicit and do not lose the page or block IDs.
static/core/principles.md
# Core principles (reader)

Use this skill to turn a research paper into a complete Markdown reading artifact. The default output should read like a bilingual paper companion, not a summary dump:

- keep the extractable prose, paragraph structure, and section flow
- show original text and Chinese translation together at block level
- extract figures and tables as assets and place them at the first substantive mention or interpretation point
- render equations as Markdown math with stable equation anchors; never expose bare LaTeX commands as ordinary prose
- keep captions attached to figures/tables with English caption text and Chinese caption translation
- preserve stable page and block anchors for traceability
- write a complete `paper.md` by default, plus `source_map.json`, `translation_notes.md`, and `assets/`

This skill is for papers, preprints, and conference proceedings across disciplines. It is not limited to Nature-family journals. If the user only wants a summary, use a summarization skill instead. If the user only wants citation search, use a citation skill instead.

## Non-negotiable defaults

When the user asks for paper translation, reading, `nature-reader`, `中英文对照`, `原文对照`, `全文翻译`, or `翻译解读`, produce a paragraph-level bilingual reader by default.

Do not replace the reader with:

- a Chinese-only summary
- a paper review without original/translation alignment
- figure captions without figure/table crops
- a list of key points detached from source locations
- only the abstract, introduction, or selected highlights

If constraints prevent full processing, still create a draft reader and clearly label missing pages, missing figures/tables, untranslated blocks, or low-confidence OCR/crops in `translation_notes.md`.

## Core principle

Translate for meaning, not for style. Preserve the paper's structure, evidence, hedging, terminology, equations, units, and citation markers. Keep the output in prose paragraphs unless the source itself is tabular or list-like. Do not collapse the paper into keyword bullets or slide-style notes.

Equations are first-class source blocks. Give every display equation an `E001`, `E002`, ... anchor and put valid inline math inside `$...$` or display math inside `$$...$$`. If the expression cannot be transcribed confidently, crop the original formula into `assets/equations/`, show that image in `paper.md`, and label any best-effort LaTeX as low confidence. Never guess missing symbols or leave commands such as `\\frac` and `\\sum` outside a math delimiter.

The reading file should help a reader move between:

- original text
- translated text
- source location
- figure or table evidence

Each substantive source block should have a stable anchor and a visible bilingual pair:

```markdown
<a id="S001"></a>
**Source:** p.1 S001

**Original:** [source paragraph]

**中文:** [faithful Chinese translation]
```

## Copyright caution

For copyrighted publisher PDFs, keep chat responses short and point to the local artifact. In local `paper.md`, include the bilingual reader only for the user-provided source file or clearly lawful open-access content; avoid reproducing large copyrighted text directly in chat.

## Quality bar

Good output feels like a paper reader, not a machine translation dump. It should let a reader:

- read the paper in two languages
- see where a claim came from
- inspect the nearby figure or table
- read equations directly in the Markdown viewer or inspect the original crop when transcription is uncertain
- move through a complete Markdown file without losing source traceability
static/core/workflow.md
# Reading workflow

Run these six steps for any paper-reading job. Steps 1-2 build the source map, 3-5 produce the artifact, 6 covers follow-up questions.

## 1. Identify the source and paper type

The source-format fragment loaded for this job covers how to extract from the specific input. At a high level, also identify the paper type so you know how tightly to couple text, figures, and captions:

- discovery or mechanism paper
- methods or algorithm paper
- resource or dataset paper
- conference paper
- review or perspective

## 2. Build a full-document source map before translating

If the user provides a full paper, process the entire document. Do not stop at the abstract, introduction, or a few representative pages unless the user explicitly asks for a preview.

Create stable IDs for source blocks:

- `S001`, `S002`, ... for body text
- `C001`, `C002`, ... for captions
- `F001`, `F002`, ... for figures
- `T001`, `T002`, ... for tables
- `E001`, `E002`, ... for display equations

For each block, capture: page number, block type, original text, translation, reading-order index, nearby figure or table references, first substantive figure/table mention when applicable, and confidence level when extraction is uncertain. For equations, also capture the printed equation number (if any), normalized LaTeX, bounding box, and original-crop path when a visual fallback is needed.

Keep the source map stable so later questions can point back to the same IDs. For long papers, add a page index so the reader can jump across the whole document without losing location.

## 3. Translate conservatively

Translate every extractable substantive block with these rules:

- preserve technical terms unless a standard Chinese equivalent is clearly better
- keep gene names, protein names, formulas, model names, and symbols intact
- keep citations, superscripts, subscripts, and numeric values unchanged
- do not collapse methods details into vague prose
- keep paragraph order and section order unless the user asks for restructuring
- mark uncertain text instead of guessing when OCR or layout extraction is weak
- keep the source's paragraph form; do not convert dense prose into bullet-point keywords
- do not silently skip Methods, limitations, data availability, code availability, competing interests, or extended captions
- if the paper is too long for one pass, write `paper.md` incrementally by page/section and mark pending blocks rather than switching to summary mode

Render inline expressions with `$...$` and display equations with `$$...$$`. Do not put equations in ordinary code spans or code fences. For low-confidence OCR, custom macros that the target renderer cannot support, or image-only formulae, follow `references/equation-handling.md`: show the original crop and an explicitly labelled best-effort transcription rather than raw or guessed LaTeX.

If a sentence contains multiple claims, keep the translation readable but do not split away the original evidence chain. Build the Terminology Ledger (`../../../nature-shared/core/terminology-ledger.md`) as you translate so recurring terms stay consistent across the whole document.

## 4. Extract and place figures and tables near the relevant discussion

Crop each figure/table into `assets/` and place it near its first substantive mention, keeping the caption attached with both original and Chinese caption text. For the full placement and tight-crop rules, and the figure/table block shape, open `references/figure-extraction.md`.

## 5. Generate the Markdown file

Default output is a single full-paper `paper.md` file. It must include:

- metadata header
- a short page/section index
- page-level or section-level divisions for long papers
- paragraph-level original/Chinese pairs for all extractable substantive text
- figure and table blocks placed near the relevant discussion
- rendered equation blocks with stable `E...` anchors and visible source locations
- a compact equation index when the paper contains three or more display equations
- source anchors on every substantive text, figure, caption, and table block
- a terminology table for recurring technical terms (from the Terminology Ledger)
- a short `阅读提示` / `critical reading notes` section only after the bilingual body, not as a replacement for it
- short uncertainty notes only when extraction is weak

Do not add an interactive Q&A panel or follow-up widget in the Markdown deliverable. If a browser preview is explicitly requested, a companion `reader.html` can be generated as a secondary artifact, but the Markdown file remains the primary output.

Before delivery, run `scripts/validate_reader_math.py paper.md --source-map source_map.json`. Resolve every failure. Use `--strict` when preparing a reusable or published artifact.

## 6. Answer follow-up questions with source grounding

When the user asks a question after the file is created, answer from the paper, not from memory, and cite exact block IDs and page numbers. For the full grounding rules, open `references/grounding-rules.md`.
static/fragments/source/doi-arxiv.md
# Source: DOI or arXiv identifier

The user gave a bare DOI or arXiv id/link that must be resolved before reading.

- Resolve the identifier to the actual article first:
  - arXiv → the abstract page, then the PDF (and HTML/LaTeX source when available).
  - DOI → the publisher landing page, then the open-access PDF or HTML if lawfully available.
- After resolving, this becomes a `pdf-text`, `scanned-pdf`, or `html` job — load that fragment and follow it for extraction. This fragment only covers retrieval.
- Capture bibliographic metadata (title, authors, venue, year, DOI/arXiv id) for the `paper.md` metadata header.
- Prefer the open-access version (arXiv, author copy, PMC) when the version of record is paywalled. Note which version was read in `translation_notes.md`, since arXiv and published versions can differ.
- When lawful arXiv HTML or TeX source is available, prefer its equation source over PDF text extraction, but still verify numbering and symbols against the rendered paper.
- If the identifier cannot be resolved or only the abstract is reachable, build a draft reader from what is available and clearly mark the rest as not retrieved. Do not fabricate body text.
- Apply the copyright caution to the resolved artifact.
static/fragments/source/html.md
# Source: publisher or preprint HTML

The source is an HTML page (publisher site, preprint server, or similar).

- Extract the article body; strip site navigation, cookie banners, related-article rails, reference-manager widgets, and advertisements.
- Keep the section structure and paragraph order from the article markup.
- Figures and tables are usually separate image/HTML elements — capture each figure image and its caption, and place per `references/figure-extraction.md`. Reconstruct HTML tables faithfully rather than screenshotting them when the markup is clean.
- Preserve inline math (MathML/LaTeX/images), superscript citation markers, and links to the reference list. Prefer accessible MathML or publisher-provided LaTeX over OCR, normalize it to `$...$` or `$$...$$`, and retain image formulae as equation crops when no trustworthy machine-readable form exists.
- Respect the copyright caution: for paywalled or all-rights-reserved pages, keep chat output short and point to the local artifact. Reproduce full bilingual text only for clearly lawful open-access content.
- If the page is JavaScript-rendered and content is missing, note what could not be retrieved instead of inventing it.
static/fragments/source/pasted-text.md
# Source: pasted text or notes

The user pasted prose or notes directly, with no retrievable original layout or page images.

- Treat the pasted text as the source of truth. Build the source map and bilingual pairs from it.
- Page numbers may be unknown. Use sequential block IDs (`S001`, `S002`, ...) and, where the paste shows section headings, use section-level anchors instead of page anchors. Note in `translation_notes.md` that page anchors are unavailable.
- There are usually no figure/table images. Do not invent crops. If the text references figures/tables, keep the references and captions as text blocks and note that the visual assets were not provided.
- If the paste is clearly partial (for example abstract and intro only), build the reader for what was given and label it draft mode; do not backfill missing sections from memory.
- Preserve any citation markers, equations, and symbols exactly as pasted. Keep valid existing math delimiters; normalize unmistakable raw LaTeX into `$...$` or `$$...$$`, but mark ambiguous or incomplete expressions instead of guessing missing delimiters or symbols.
- Keep the bilingual reader format; do not collapse pasted prose into a summary just because layout metadata is missing.
static/fragments/source/pdf-text.md
# Source: selectable-text PDF

The PDF has an extractable text layer. Load the `pdf` skill first for extraction guidance.

- Extract the text layer directly; do not OCR text that is already selectable.
- Process the whole document, not just the first pages. Build the source map (step 2) across every page.
- Watch for multi-column layouts: recover natural reading order rather than top-to-bottom raw stream order.
- Keep ligatures, hyphenated line breaks, superscripts, subscripts, and math intact; rejoin words split across line breaks.
- Detect display equations as independent `E...` blocks. Plain-text extraction often destroys fractions, matrices, alignment, and symbol placement, so verify each equation against the rendered PDF page before writing LaTeX.
- Use native math objects or embedded LaTeX when available. If visual verification still leaves ambiguity, crop the original equation and use the low-confidence fallback in `references/equation-handling.md`.
- Figures and tables are images embedded in the page — crop them per `references/figure-extraction.md`; do not paste the page text of a table where the table image belongs.
- If some pages have a text layer and others are scanned, treat the scanned pages with the `scanned-pdf` rules and mark them with a confidence note.
static/fragments/source/scanned-pdf.md
# Source: scanned PDF (OCR required)

The PDF is image-only or has an unreliable text layer. Load the `pdf` skill first for OCR guidance.

- OCR every page; do not assume a usable text layer exists.
- Record a confidence level for each block in the source map, and mark low-confidence blocks explicitly in `translation_notes.md` rather than guessing.
- Preserve the original wording where OCR is confident; flag, do not silently "correct", garbled text.
- Be careful with numerals, units, symbols, gene/protein names, and chemical formulas — OCR errors here change meaning. Cross-check against context and mark uncertainty.
- Treat every display equation as an image-first `E...` block: crop the original formula before attempting math OCR. Show the crop in `paper.md`; include OCR-derived LaTeX only as a visibly low-confidence convenience layer until every symbol is verified.
- Never silently repair or guess a scanned equation from surrounding prose.
- Figures and tables are page regions: crop them per `references/figure-extraction.md`. For low-quality scans, a tight correct crop still beats a wide noisy one.
- If pages are skewed, rotated, or partly cut off, note the affected pages and translate only what is legible.