diff --git a/.gitignore b/.gitignore index f68865e..d685d85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,16 @@ work/ +.venv/ +__pycache__/ +*.pyc *.aux *.log *.out *.fls *.fdb_latexmk *.synctex.gz +*.toc .DS_Store +claims.md +**/notes/notes.pdf +**/notes/sections/symbols.tex +**/out/ diff --git a/README.md b/README.md index 069cc73..2998867 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,13 @@ skills/supertensor -> superpaper/supertensor ## Status -The family contract lives in [`DESIGN.md`](DESIGN.md). Figure children -are usable now. The notes scaffold (`scripts/`, ledger, template) is -the SuperPaper v1 PR chain in that document — not in this commit. +v1 notes scaffold is in this repo: ledger schema, router, lint, ingest, +and three `--work` examples (`excerpt-toy`, `pipeline-delegate`, +`tensor-delegate`). `superderive` is still phase 2. + +```bash +python3 -m venv .venv && .venv/bin/pip install -r requirements.txt +./scripts/preflight.sh +./scripts/test.sh +./scripts/build.sh --work examples/excerpt-toy +``` diff --git a/SKILL.md b/SKILL.md index 3015761..685f3cc 100644 --- a/SKILL.md +++ b/SKILL.md @@ -17,8 +17,8 @@ description: >- `ledger.yaml` + 结构化中文笔记。图一律委派给子仓库,不要在笔记里 `\usepackage{superfig}` / `supertensor`。 -本目录是家族母仓库。子仓库在 `superfig/` 与 `supertensor/`(git submodule)。 -完整合同见 `DESIGN.md`。笔记脚手架(`scripts/`、模板、schema)按 DESIGN 的 PR 1 落地;在此之前按下面路由手工委派。 +本目录是家族母仓库。子仓库在 `superfig/` 与 `supertensor/`。 +契约细节按需加载 `references/`;schema 在 `assets/ledger.schema.yaml`。 ## When to use @@ -43,12 +43,15 @@ video. ## Workflow -1. 读 `DESIGN.md` 的输入契约与 ledger schema。 -2. 先写 `ledger.yaml`(claims / symbols / derivations / figure plan),再写笔记。 -3. 公式三拍:中文动机 → `\[` / `align` → 扁平符号表。 -4. 每张计划图写一份自包含 `F*.request.md`,开一个 figure agent,**只**把对应子仓库的 `SKILL.md` 给它。 -5. 笔记用 PDF `\includegraphics` 嵌入子仓库产物。禁止 `\input` standalone 源。 -6. 改术语或改图:先改 ledger,再改那一处。 +1. `./scripts/preflight.sh`。0 = 全路径,1 = 降级(说出口),2 = 无 LaTeX。 +2. `./scripts/ingest.sh --work …`(`references/input.md`)。 +3. 从 `assets/ledger.example.yaml` 写 `ledger.yaml`,再写 `outline.md`,再写正文。 +4. 公式三拍:中文动机 → `\[` / `align` → 扁平符号表(`references/pedagogy.md`)。 +5. 按 `references/router.md` 填 figure plan。每张重绘图一份 `F*.request.md`,figure agent 只读它和对应子仓库 `SKILL.md`。 +6. `./scripts/lint.py --work ` 然后 `./scripts/build.sh --work `。 +7. 改术语或改图:先改 ledger,再改那一处。 + +Worked trees: `examples/excerpt-toy/`(`align` 推导)、`examples/pipeline-delegate/`、`examples/tensor-delegate/`。 ## Spawn recipe diff --git a/assets/ledger.example.yaml b/assets/ledger.example.yaml new file mode 100644 index 0000000..63c00d7 --- /dev/null +++ b/assets/ledger.example.yaml @@ -0,0 +1,24 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: "example" + title: "" + authors: [] + notes_language: zh + source: + kind: excerpt +coverage: + mode: excerpt + sections_in: [] + sections_skipped: [] +questions: [] +claims: [] +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: [] +evidence: [] +terms: [] +source_assets: [] diff --git a/assets/ledger.schema.yaml b/assets/ledger.schema.yaml new file mode 100644 index 0000000..22ada30 --- /dev/null +++ b/assets/ledger.schema.yaml @@ -0,0 +1,210 @@ +$schema: "http://json-schema.org/draft-07/schema#" +$id: "https://local/superpaper.ledger/v1" +type: object +required: [schema, paper, coverage] +additionalProperties: true +properties: + schema: {const: superpaper.ledger/v1} + retired_ids: + type: array + items: {type: string, pattern: "^(C|Q|D|A|L|E|DER|F|SA)[1-9][0-9]*$"} + paper: + type: object + required: [id, title, source] + additionalProperties: true + properties: + id: {type: string, minLength: 1} + title: {type: string} + authors: {type: array, items: {type: string}} + year: {type: integer} + venue: {type: string} + notes_language: {type: string, enum: [zh, en]} + degraded: + type: array + items: {type: string, enum: [scanned, no-eprint, no-pdftotext]} + source: + type: object + required: [kind] + additionalProperties: true + properties: + kind: {enum: [arxiv, pdf, tex, excerpt, markdown]} + arxiv: {type: string} + local_pdf: {type: string} + pages: {type: integer, minimum: 1} + language: {type: string} + coverage: + type: object + required: [mode] + additionalProperties: true + properties: + mode: {enum: [full, excerpt, body-only]} + sections_in: {type: array, items: {type: string}} + sections_skipped: {type: array, items: {type: string}} + skip_reasons: + type: array + items: + type: object + required: [section, reason] + properties: + section: {type: string} + reason: {type: string} + questions: + type: array + items: + type: object + required: [id, text] + properties: + id: {type: string, pattern: "^Q[1-9][0-9]*$"} + text: {type: string} + source: {type: string} + claims: + type: array + items: + type: object + required: [id, text, kind, status] + properties: + id: {type: string, pattern: "^C[1-9][0-9]*$"} + text: {type: string} + kind: {enum: [contribution, theoretical, empirical, methodological]} + status: {enum: [core, supporting, dropped]} + supports: {type: array, items: {type: string}} + depends_on: {type: array, items: {type: string}} + evidence: {type: array, items: {type: string}} + source: {type: string} + definitions: + type: array + items: + type: object + required: [id, name, text] + properties: + id: {type: string, pattern: "^D[1-9][0-9]*$"} + name: {type: string} + text: {type: string} + source: {type: string} + assumptions: + type: array + items: + type: object + required: [id, text] + properties: + id: {type: string, pattern: "^A[1-9][0-9]*$"} + text: {type: string} + source: {type: string} + used_by: {type: array, items: {type: string}} + lemmas: + type: array + items: + type: object + required: [id, text] + properties: + id: {type: string, pattern: "^L[1-9][0-9]*$"} + text: {type: string} + depends_on: {type: array, items: {type: string}} + used_by: {type: array, items: {type: string}} + source: {type: string} + symbols: + type: array + items: + type: object + required: [name, latex, meaning, kind] + properties: + name: {type: string} + latex: {type: string} + meaning: {type: string} + domain: {type: string} + kind: + enum: [value, score, probability, index, rank, count, id, mask, + permutation, "shape parameter", scalar, set] + introduced: {type: string} + used: {type: array, items: {type: string}} + aliases: {type: array, items: {type: string}} + derivations: + type: array + items: + type: object + required: [id, claim, title, expand] + properties: + id: {type: string, pattern: "^DER[1-9][0-9]*$"} + claim: {type: string} + title: {type: string} + source: {type: string} + expand: {type: boolean} + figure: {type: ["string", "null"]} + steps: + type: array + items: + type: object + required: [id, from, to, rule] + properties: + id: {type: string} + from: {type: string} + to: {type: string} + rule: + enum: [definition, substitute, cancel, factor, scale, + take-limit, approx, cite, rearrange, introduce] + cite: {type: string} + justify: {type: string} + figures: + type: array + items: + type: object + required: [id, claim, title, grammar, toolkit, signals, status] + properties: + id: {type: string, pattern: "^F[1-9][0-9]*$"} + claim: {type: string} + title: {type: string} + grammar: + enum: [architecture, pipeline, data-flow, state, time, dependency, + argument-map, tensor-face, derivation, screenshot, plot, + notation, none] + toolkit: + enum: [superfig, supertensor, superderive, screenshot, + matplotlib, none, align] + signals: {type: array, items: {type: string}} + source_fig: {type: string} + source_pages: {type: array, items: {type: integer, minimum: 1}} + crop_bbox: + type: ["array", "null"] + minItems: 4 + maxItems: 4 + items: {type: number} + request: {type: ["string", "null"]} + include: {type: ["string", "null"]} + status: {enum: [planned, delegated, built, included, dropped]} + drop_reason: {type: ["string", "null"]} + evidence: + type: array + items: + type: object + required: [id, kind, source, supports, handling] + properties: + id: {type: string, pattern: "^E[1-9][0-9]*$"} + kind: {enum: [table, plot, ablation, theorem, example]} + source: {type: string} + supports: {type: array, items: {type: string}} + handling: + enum: [redraw-superfig, redraw-supertensor, redraw-superderive, + screenshot, matplotlib, omit] + terms: + type: array + items: + type: object + required: [canonical] + properties: + canonical: {type: string} + aliases: {type: array, items: {type: string}} + first_defined: {type: string} + source_assets: + type: array + items: + type: object + required: [id, kind, handling] + properties: + id: {type: string, pattern: "^SA[1-9][0-9]*$"} + kind: {enum: [figure, table]} + paper_ref: {type: string} + handling: + enum: [redraw-superfig, redraw-supertensor, redraw-superderive, + screenshot, matplotlib, omit] + pages: {type: array, items: {type: integer}} + note: {type: string} diff --git a/assets/notes-macros.tex b/assets/notes-macros.tex new file mode 100644 index 0000000..093d046 --- /dev/null +++ b/assets/notes-macros.tex @@ -0,0 +1,54 @@ +% Shared notes preamble. TEXINPUTS must include superpaper/assets/. +\usepackage[fontset=fandol]{ctex} +\usepackage{amsmath,amssymb} +\usepackage{graphicx} +\usepackage[margin=2.5cm]{geometry} +\usepackage[most]{tcolorbox} +\usepackage{etoolbox} +\usepackage{listings} +\usepackage{booktabs} +\usepackage{subcaption} +\usepackage{float} +\usepackage{tikz} +\usepackage{hyperref} + +\newtcolorbox{knowledgebox}[1]{ + enhanced, colback=blue!5!white, colframe=blue!75!black, colbacktitle=blue!75!black, + coltitle=white, fonttitle=\bfseries, title=#1, + attach boxed title to top left={yshift=-2mm, xshift=2mm}, + boxrule=1pt, sharp corners +} +\newtcolorbox{importantbox}[1]{ + enhanced, colback=yellow!10!white, colframe=yellow!80!black, colbacktitle=yellow!80!black, + coltitle=black, fonttitle=\bfseries, title=#1, sharp corners +} +\newtcolorbox{warningbox}[1]{ + enhanced, colback=red!5!white, colframe=red!75!black, colbacktitle=red!75!black, + coltitle=white, fonttitle=\bfseries, title=#1, sharp corners +} +\newtcolorbox{quotebox}[1]{ + enhanced, breakable, + colback=black!3!white, colframe=black!55, colbacktitle=black!55, + coltitle=white, fonttitle=\bfseries, title=#1, sharp corners +} + +\newcommand{\splabel}[1]{\hypertarget{sp:#1}{}\label{sp:#1}} +\newcommand{\spref}[1]{\hyperlink{sp:#1}{\texttt{#1}}} +\newcommand{\spsource}[1]{\footnote{来源:#1}} +\newcommand{\spfig}[4][0.92\textwidth]{% + \begin{figure}[H]\centering + \includegraphics[width=#1]{figures/#2/build/#2.pdf}% + \caption{#3\protect\footnotemark}\end{figure} + \footnotetext{#4}} +\newcommand{\spscreenshot}[4][0.92\textwidth]{% + \begin{figure}[H]\centering + \includegraphics[width=#1]{figures/#2/orig.png}% + \caption{#3\protect\footnotemark}\end{figure} + \footnotetext{#4}} + +\newcommand{\notetitle}{论文笔记} +\newcommand{\noteauthors}{} +\newcommand{\notedate}{\today} +\newcommand{\notepaper}{} +\newcommand{\notevenue}{} +\newcommand{\notearxiv}{} diff --git a/assets/notes-template.tex b/assets/notes-template.tex new file mode 100644 index 0000000..1ba4928 --- /dev/null +++ b/assets/notes-template.tex @@ -0,0 +1,43 @@ +\documentclass[a4paper]{article} +\input{notes-macros} + +% Locked skeleton (see references/pedagogy.md): +% \section{这篇论文在问什么} +% \section{主张与贡献} % \splabel{C*} +% \section{预备:定义、假设、符号} +% % --- outline-chosen mechanism sections --- +% \section{实验与证据} % optional +% \section{总结与延伸} +% \appendix +% \section{符号表} % \input{sections/symbols.tex} +% \section{推导链一览} +% \section{图表清单} +% +% Formula: Chinese motive, then \[ / align, then a flat symbol list. +% Keep figures outside knowledgebox / importantbox / warningbox / quotebox. +% Do not \usepackage{superfig|supertensor|superderive}. + +\begin{document} + +\begin{titlepage} +\centering +\vspace{1.2cm} +{\huge\bfseries \notetitle\par} +\vspace{0.8cm} +{\large \noteauthors\par} +\vspace{0.3cm} +{\large \notedate\par} +\vspace{1.2cm} +\begin{tcolorbox}[width=0.9\textwidth, colback=black!2!white, colframe=black!60, sharp corners] +\textbf{论文}:\notepaper\par +\textbf{venue}:\notevenue\par +\textbf{arXiv}:\notearxiv\par +\end{tcolorbox} +\end{titlepage} + +\tableofcontents +\newpage + +\input{sections/sec-01.tex} + +\end{document} diff --git a/examples/excerpt-toy/ledger.yaml b/examples/excerpt-toy/ledger.yaml new file mode 100644 index 0000000..542c47b --- /dev/null +++ b/examples/excerpt-toy/ledger.yaml @@ -0,0 +1,46 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: excerpt-toy + title: A One-Step Predictor + authors: ["Fixture"] + year: 2026 + venue: Superpaper examples + notes_language: zh + source: {kind: excerpt} +coverage: + mode: excerpt + sections_in: ["1"] +questions: + - {id: Q1, text: "一次前向如何得到预测,损失该放在哪?", source: "excerpt"} +claims: + - id: C1 + text: "一次前向是 x → f_θ → ŷ;损失在预测之后单独计算" + kind: contribution + status: core + supports: [Q1] + source: "excerpt" +definitions: + - {id: D1, name: "one-step predictor", text: "ŷ = f_θ(x)", source: "excerpt"} +assumptions: [] +lemmas: [] +symbols: + - {name: x, latex: "x", meaning: "输入", kind: value, introduced: "excerpt"} + - {name: yhat, latex: "\\hat y", meaning: "预测", kind: value} + - {name: L, latex: "L", meaning: "损失", kind: scalar} + - {name: d, latex: "d", meaning: "特征维", kind: "shape parameter"} +derivations: + - id: DER1 + claim: C1 + title: "缩放来自方差" + source: "excerpt" + expand: true + figure: null + steps: + - {id: S1, from: "u^\\top v", to: "u^\\top v / \\sqrt{d}", rule: scale, + justify: "点积方差随 d 增长"} +figures: [] +evidence: [] +terms: + - {canonical: "one-step predictor", aliases: ["一次前向"]} +source_assets: [] diff --git a/examples/excerpt-toy/notes/notes.tex b/examples/excerpt-toy/notes/notes.tex new file mode 100644 index 0000000..b528fc6 --- /dev/null +++ b/examples/excerpt-toy/notes/notes.tex @@ -0,0 +1,29 @@ +\documentclass[a4paper]{article} +\input{notes-macros} +\renewcommand{\notetitle}{一次前向预测器} +\renewcommand{\noteauthors}{Superpaper fixture} +\renewcommand{\notepaper}{A One-Step Predictor} +\renewcommand{\notevenue}{examples/excerpt-toy} +\begin{document} +\begin{titlepage} +\centering +\vspace{2cm} +{\huge\bfseries \notetitle\par} +\vspace{1cm} +{\large \notepaper\par} +\vspace{0.5cm} +{\large \noteauthors\par} +\end{titlepage} +\tableofcontents +\newpage +\input{sections/sec-01.tex} +\input{sections/sec-02.tex} +\input{sections/sec-03.tex} +\input{sections/sec-04.tex} +\input{sections/sec-05.tex} +\appendix +\section{符号表} +\input{sections/symbols.tex} +\section{推导链一览} +DER1:缩放来自方差,见 \spref{C1}。 +\end{document} diff --git a/examples/excerpt-toy/notes/sections/sec-01.tex b/examples/excerpt-toy/notes/sections/sec-01.tex new file mode 100644 index 0000000..06abff0 --- /dev/null +++ b/examples/excerpt-toy/notes/sections/sec-01.tex @@ -0,0 +1,3 @@ +\section{这篇论文在问什么} +要把输入变成预测,最简单的机制是什么?损失要不要走在前向主路上? +这是摘录 fixture,不假装读完全文。 diff --git a/examples/excerpt-toy/notes/sections/sec-02.tex b/examples/excerpt-toy/notes/sections/sec-02.tex new file mode 100644 index 0000000..7170234 --- /dev/null +++ b/examples/excerpt-toy/notes/sections/sec-02.tex @@ -0,0 +1,3 @@ +\section{主张与贡献} +\splabel{C1} +一次前向是 $x \to f_\theta \to \hat y$。损失 $L(\hat y,y)$ 在预测之后单独计算,不是主路上的一站。 diff --git a/examples/excerpt-toy/notes/sections/sec-03.tex b/examples/excerpt-toy/notes/sections/sec-03.tex new file mode 100644 index 0000000..af57f2d --- /dev/null +++ b/examples/excerpt-toy/notes/sections/sec-03.tex @@ -0,0 +1,2 @@ +\section{预备:定义、假设、符号} +预测器定义为 $\hat y = f_\theta(x)$。符号见附录。 diff --git a/examples/excerpt-toy/notes/sections/sec-04.tex b/examples/excerpt-toy/notes/sections/sec-04.tex new file mode 100644 index 0000000..21127ed --- /dev/null +++ b/examples/excerpt-toy/notes/sections/sec-04.tex @@ -0,0 +1,18 @@ +\section{一次前向与损失} +内积的方差会随维数 $d$ 涨。为了不让后续非线性饱和,要把点积除掉 $\sqrt{d}$。 + +\begin{align} +u^\top v &\longrightarrow \frac{u^\top v}{\sqrt{d}}. +\end{align} + +\begin{itemize} +\item $u,v$ — 两个 $d$ 维向量 +\item $d$ — 特征维(shape parameter) +\end{itemize} + +\begin{importantbox}{主路与损失} +损失比较 $\hat y$ 与 $y$,梯度再回到 $\theta$。不要把 $L$ 画成前向的一站。 +\end{importantbox} + +\subsection{本章小结} +前向只负责预测;缩放是改写,不是新算子。 diff --git a/examples/excerpt-toy/notes/sections/sec-05.tex b/examples/excerpt-toy/notes/sections/sec-05.tex new file mode 100644 index 0000000..522cc36 --- /dev/null +++ b/examples/excerpt-toy/notes/sections/sec-05.tex @@ -0,0 +1,2 @@ +\section{总结与延伸} +摘录只保留一条机制:一次前向加侧路损失。更长的论文用 ledger 把 claim 钉住,再按路由出图。 diff --git a/examples/excerpt-toy/outline.md b/examples/excerpt-toy/outline.md new file mode 100644 index 0000000..f31e76c --- /dev/null +++ b/examples/excerpt-toy/outline.md @@ -0,0 +1,17 @@ +# Outline: A One-Step Predictor + +## Lecture map + +| file | lecture_title | paper_sections | ledger_ids | +|---|---|---|---| +| sec-01.tex | 这篇论文在问什么 | 1 | Q1 | +| sec-02.tex | 主张与贡献 | 1 | C1 | +| sec-03.tex | 预备:定义、假设、符号 | 1 | D1 | +| sec-04.tex | 一次前向与损失 | 1 | C1, DER1 | +| sec-05.tex | 总结与延伸 | 1 | C1 | +| sec-app-a.tex | 符号表 | — | | +| sec-app-b.tex | 推导链一览 | — | DER1 | + +## Locked +- 首节标题必须是「这篇论文在问什么」 +- 末节(appendix 前)必须是「总结与延伸」 diff --git a/examples/excerpt-toy/source/excerpt.md b/examples/excerpt-toy/source/excerpt.md new file mode 100644 index 0000000..0249128 --- /dev/null +++ b/examples/excerpt-toy/source/excerpt.md @@ -0,0 +1,8 @@ +# A One-Step Predictor (fixture) + +We predict $\hat y = f_\theta(x)$ in one forward pass. The loss +$L(\hat y, y)$ is computed after the prediction; it is not a station +on the forward path. + +The scale $1/\sqrt{d}$ is introduced so that the variance of the +inner product does not grow with $d$. diff --git a/examples/pipeline-delegate/ledger.yaml b/examples/pipeline-delegate/ledger.yaml new file mode 100644 index 0000000..06ad7a0 --- /dev/null +++ b/examples/pipeline-delegate/ledger.yaml @@ -0,0 +1,47 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: pipeline-delegate + title: A One-Step Predictor + authors: ["Fixture"] + notes_language: zh + source: {kind: excerpt} +coverage: + mode: excerpt + sections_in: ["1"] +questions: + - {id: Q1, text: "前向主路和损失如何分开?", source: "excerpt"} +claims: + - id: C1 + text: "一次前向是 x → f_θ → ŷ;损失不在主路上" + kind: contribution + status: core + supports: [Q1] +definitions: [] +assumptions: [] +lemmas: [] +symbols: + - {name: x, latex: "x", meaning: "输入", kind: value} + - {name: yhat, latex: "\\hat y", meaning: "预测", kind: value} + - {name: L, latex: "L", meaning: "损失", kind: scalar} +derivations: + - id: DER1 + claim: C1 + title: "前向定义" + expand: true + figure: null + steps: + - {id: S1, from: "x", to: "f_\\theta(x)=\\hat y", rule: definition} +figures: + - id: F1 + claim: C1 + title: "一次前向与侧路损失" + grammar: pipeline + toolkit: superfig + signals: [pipeline, what-eats-what] + request: figures/F1/F1.request.md + include: figures/F1/build/F1.pdf + status: included +evidence: [] +terms: [] +source_assets: [] diff --git a/examples/pipeline-delegate/notes/figures/F1/F1-mechanism.md b/examples/pipeline-delegate/notes/figures/F1/F1-mechanism.md new file mode 100644 index 0000000..929b88e --- /dev/null +++ b/examples/pipeline-delegate/notes/figures/F1/F1-mechanism.md @@ -0,0 +1 @@ +一次前向 $x\to f_\theta\to\hat y$。损失挂在预测下方,不是主路车站。 diff --git a/examples/pipeline-delegate/notes/figures/F1/F1.request.md b/examples/pipeline-delegate/notes/figures/F1/F1.request.md new file mode 100644 index 0000000..2e38e98 --- /dev/null +++ b/examples/pipeline-delegate/notes/figures/F1/F1.request.md @@ -0,0 +1,27 @@ +# Figure request F1 +toolkit: superfig +language: cjk +claim: 一次前向是 x → f_θ → ŷ;损失不在主路上。 +grammar: pipeline +work_rel_dir: figures/F1 + +## Roles +- {role: input, color: sfTeal} +- {role: model, color: sfOrange} +- {role: loss, color: sfCoral} +- {role: output, color: sfViolet} + +## Flow (cursor). \sfconn 没有 endpoints。 +stage: {name: SA, text: "推理流程:一次前向"} +row: {name: R1, height: 16mm} +in_row: + - {macro: sfnode, name: x, role: input, label: "输入 $x$", w: 16mm, h: 12mm} + - {macro: sfconn, name: e1, label: 预处理} + - {macro: sfnode, name: f, role: model, label: "模型 $f_\\theta$", w: 18mm, h: 12mm} + - {macro: sfconn, name: e2, label: logits} + - {macro: sfnode, name: y, role: output, label: "预测 $\\hat y$", w: 16mm, h: 12mm} + +## Fixed topology +- {macro: sfnode, name: s, role: loss, label: "损失 $L$", w: 14mm, h: 12mm, + at: "($(y.south)+(0,-22mm)$)"} +- {macro: sfarrowlabel, from: "y.south", to: "s.north", label: "$L(\\hat y, y)$"} diff --git a/examples/pipeline-delegate/notes/figures/F1/F1.tex b/examples/pipeline-delegate/notes/figures/F1/F1.tex new file mode 100644 index 0000000..955b096 --- /dev/null +++ b/examples/pipeline-delegate/notes/figures/F1/F1.tex @@ -0,0 +1,47 @@ +% superfig golden example 1 -- one horizontal paper-figure pipeline. +% Main path is input -> model -> prediction. Loss is a side object, not a +% station on the forward path. +% ../scripts/build.sh pipeline.tex +\documentclass[border=10pt]{standalone} +\usepackage[cjk]{superfig} + +\sfsetrole{input}{sfTeal} +\sfsetrole{model}{sfOrange} +\sfsetrole{loss}{sfCoral} +\sfsetrole{output}{sfViolet} + +\begin{document} +\begin{tikzpicture} + +\sfstage{SA}{推理流程:一次前向} +\sfrow{R1}{16mm} + \sfnode[role=input]{x}{输入 $x$}{16mm}{12mm} + \sfconn{e1}{预处理} + \sfnode[role=model]{f}{模型 $f_\theta$}{18mm}{12mm} + \sfconn{e2}{logits} + \sfnode[role=output]{y}{预测 $\hat y$}{16mm}{12mm} +\sfrowend + +% Loss compares the prediction with the target; it is not on the main path. +% Hang it below ŷ with enough shaft that no caption sits on the arrow. +\sfnode[role=loss, at={($(y.south)+(0,-22mm)$)}]{s}{损失 $L$}{14mm}{12mm} +\sfarrowlabel{y.south}{s.north}{$L(\hat y,y)$} + +\sflane{R1} +\sfcaption{x}{$x$}{原始输入} +\sfcaption{f}{$f_\theta$}{可学习参数} +\sfnolane +\sfcaption{s}{$L$}{与真值比较} + +\sfbbox{all} +\sftopformula{F}{% + $x \;\xrightarrow{\;f_\theta\;}\; \hat y,\qquad + \min_\theta\; L\bigl(f_\theta(x),\,y\bigr)$} +\sfmeaningbox{mb}{96mm}{all} + {一次从输入到预测的前向;损失在预测之后单独计算} + {数据、模型参数、预测、损失} + {预处理后送入模型;模型产生 logits 得到预测;损失比较预测与真值,梯度再回到参数} +\sfsignature{推理流程示意}{mb} + +\end{tikzpicture} +\end{document} diff --git a/examples/pipeline-delegate/notes/figures/F1/build/F1-alpha.png b/examples/pipeline-delegate/notes/figures/F1/build/F1-alpha.png new file mode 100644 index 0000000..a6596e3 Binary files /dev/null and b/examples/pipeline-delegate/notes/figures/F1/build/F1-alpha.png differ diff --git a/examples/pipeline-delegate/notes/figures/F1/build/F1-thumb.png b/examples/pipeline-delegate/notes/figures/F1/build/F1-thumb.png new file mode 100644 index 0000000..cb3580b Binary files /dev/null and b/examples/pipeline-delegate/notes/figures/F1/build/F1-thumb.png differ diff --git a/examples/pipeline-delegate/notes/figures/F1/build/F1.pdf b/examples/pipeline-delegate/notes/figures/F1/build/F1.pdf new file mode 100644 index 0000000..0845129 Binary files /dev/null and b/examples/pipeline-delegate/notes/figures/F1/build/F1.pdf differ diff --git a/examples/pipeline-delegate/notes/figures/F1/build/F1.png b/examples/pipeline-delegate/notes/figures/F1/build/F1.png new file mode 100644 index 0000000..496171a Binary files /dev/null and b/examples/pipeline-delegate/notes/figures/F1/build/F1.png differ diff --git a/examples/pipeline-delegate/notes/figures/F1/build/F1.svg b/examples/pipeline-delegate/notes/figures/F1/build/F1.svg new file mode 100644 index 0000000..04a3663 --- /dev/null +++ b/examples/pipeline-delegate/notes/figures/F1/build/F1.svg @@ -0,0 +1,674 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/pipeline-delegate/notes/notes.tex b/examples/pipeline-delegate/notes/notes.tex new file mode 100644 index 0000000..2daaf4e --- /dev/null +++ b/examples/pipeline-delegate/notes/notes.tex @@ -0,0 +1,14 @@ +\documentclass[a4paper]{article} +\input{notes-macros} +\renewcommand{\notetitle}{一次前向与侧路损失} +\renewcommand{\notepaper}{A One-Step Predictor} +\begin{document} +\tableofcontents +\newpage +\input{sections/sec-01.tex} +\input{sections/sec-02.tex} +\input{sections/sec-03.tex} +\appendix +\section{符号表} +\input{sections/symbols.tex} +\end{document} diff --git a/examples/pipeline-delegate/notes/sections/sec-01.tex b/examples/pipeline-delegate/notes/sections/sec-01.tex new file mode 100644 index 0000000..e6be679 --- /dev/null +++ b/examples/pipeline-delegate/notes/sections/sec-01.tex @@ -0,0 +1,2 @@ +\section{这篇论文在问什么} +预测怎么从输入算出来,损失该不该站在前向主路上? diff --git a/examples/pipeline-delegate/notes/sections/sec-02.tex b/examples/pipeline-delegate/notes/sections/sec-02.tex new file mode 100644 index 0000000..825a213 --- /dev/null +++ b/examples/pipeline-delegate/notes/sections/sec-02.tex @@ -0,0 +1,8 @@ +\section{主张与贡献} +\splabel{C1} +一次前向是 $x\to f_\theta\to\hat y$。损失在预测之后单独比较。 + +\spfig{F1}{一次前向与侧路损失。}{重绘自 fixture Figure~1;toolkit: \texttt{superfig};ledger id: F1。} + +\subsection{本章小结} +损失不是前向的一站。 diff --git a/examples/pipeline-delegate/notes/sections/sec-03.tex b/examples/pipeline-delegate/notes/sections/sec-03.tex new file mode 100644 index 0000000..c8fd1b8 --- /dev/null +++ b/examples/pipeline-delegate/notes/sections/sec-03.tex @@ -0,0 +1,2 @@ +\section{总结与延伸} +这张图走 superfig,因为要画的是谁吃谁,不是轴长。 diff --git a/examples/pipeline-delegate/outline.md b/examples/pipeline-delegate/outline.md new file mode 100644 index 0000000..6c7df88 --- /dev/null +++ b/examples/pipeline-delegate/outline.md @@ -0,0 +1,14 @@ +# Outline: A One-Step Predictor + +## Lecture map + +| file | lecture_title | paper_sections | ledger_ids | +|---|---|---|---| +| sec-01.tex | 这篇论文在问什么 | 1 | Q1 | +| sec-02.tex | 主张与贡献 | 1 | C1, F1 | +| sec-03.tex | 总结与延伸 | 1 | C1 | +| sec-app-a.tex | 符号表 | — | | + +## Locked +- 首节标题必须是「这篇论文在问什么」 +- 末节(appendix 前)必须是「总结与延伸」 diff --git a/examples/tensor-delegate/ledger.yaml b/examples/tensor-delegate/ledger.yaml new file mode 100644 index 0000000..1c15b45 --- /dev/null +++ b/examples/tensor-delegate/ledger.yaml @@ -0,0 +1,47 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: tensor-delegate + title: Head scores + authors: ["Fixture"] + notes_language: zh + source: {kind: excerpt} +coverage: + mode: excerpt +questions: + - {id: Q1, text: "每头打分沿哪一维收缩?", source: "excerpt"} +claims: + - id: C1 + text: "每头打分沿 d_h 收缩;K^T 必须物理换面" + kind: theoretical + status: core + supports: [Q1] +definitions: [] +assumptions: [] +lemmas: [] +symbols: + - {name: Q, latex: "Q", meaning: "query", kind: value} + - {name: K, latex: "K", meaning: "key", kind: value} + - {name: S, latex: "S", meaning: "score", kind: score} + - {name: dh, latex: "d_h", meaning: "头维", kind: "shape parameter"} +derivations: + - id: DER1 + claim: C1 + title: "打分" + expand: true + figure: null + steps: + - {id: S1, from: "Q K", to: "Q K^{\\top}", rule: rearrange} +figures: + - id: F2 + claim: C1 + title: "每头打分" + grammar: tensor-face + toolkit: supertensor + signals: [axis, shape, transpose, contraction, face] + request: figures/F2/F2.request.md + include: figures/F2/build/F2.pdf + status: included +evidence: [] +terms: [] +source_assets: [] diff --git a/examples/tensor-delegate/notes/figures/F2/F2-mechanism.md b/examples/tensor-delegate/notes/figures/F2/F2-mechanism.md new file mode 100644 index 0000000..751aa97 --- /dev/null +++ b/examples/tensor-delegate/notes/figures/F2/F2-mechanism.md @@ -0,0 +1 @@ +$S^{(i)}=Q^{(i)}K^{(i)\top}$。收缩维 $d_h$ 在两个操作数上同一边长;$K^\top$ 换面。 diff --git a/examples/tensor-delegate/notes/figures/F2/F2.request.md b/examples/tensor-delegate/notes/figures/F2/F2.request.md new file mode 100644 index 0000000..c9dc50e --- /dev/null +++ b/examples/tensor-delegate/notes/figures/F2/F2.request.md @@ -0,0 +1,25 @@ +# Figure request F2 +toolkit: supertensor +language: cjk +claim: 每头打分沿 d_h 收缩;K^T 必须物理换面。 +grammar: tensor-face +work_rel_dir: figures/F2 + +## Roles +- {role: q, color: stTeal} +- {role: k, color: stOrange} +- {role: s, color: stCoral} + +## Geometry +- {axis: T, cells: 6} +- {axis: dh, cells: 3} + +## Flow +stage: {name: SA, text: "每头打分:沿 $d_h$ 收缩"} +row: {name: rowA, height: T} +in_row: + - {macro: ststack, name: Q, role: q, coord: "", rows: T, cols: dh, sheets: 3, bracket: true} + - {macro: stglyph, name: mA, coord: "", glyph: "$\\times$"} + - {macro: ststack, name: KT, role: k, coord: "", rows: dh, cols: T, sheets: 3, bracket: true} + - {macro: stglyph, name: eA, coord: "", glyph: "$=$"} + - {macro: ststack, name: S, role: s, coord: "", rows: T, cols: T, sheets: 3} diff --git a/examples/tensor-delegate/notes/figures/F2/F2.tex b/examples/tensor-delegate/notes/figures/F2/F2.tex new file mode 100644 index 0000000..00605c4 --- /dev/null +++ b/examples/tensor-delegate/notes/figures/F2/F2.tex @@ -0,0 +1,31 @@ +\documentclass[border=10pt]{standalone} +\usepackage[cjk]{supertensor} + +\stsetrole{q}{stTeal} +\stsetrole{k}{stOrange} +\stsetrole{s}{stCoral} + +\stdim{T}{6} +\stdim{dh}{3} + +\begin{document} +\begin{tikzpicture} +\ststage{SA}{每头打分:沿 $d_h$ 收缩} +\strow{rowA}{T} + \ststack[role=q, bracket=true]{Q}{}{T}{dh}{3} + \stglyph{mA}{$\times$} + \ststack[role=k, bracket=true]{KT}{}{dh}{T}{3} + \stglyph{eA}{$=$} + \ststack[role=s]{S}{}{T}{T}{3} +\strowend +\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$} +\stcaption{KT}{$\mathbf K^{(i)\top}$}{$h\times d_h\times T$} +\stcaption{S}{$\mathbf S^{(i)}$}{$h\times T\times T$} +\sttopformula{F}{$S^{(i)}=Q^{(i)}K^{(i)\top}$} +\stbbox{all} +\stmeaningbox{mb}{120mm}{all} + {$T$ 时间;$d_h$ 头维;$h$ 头数画成 stack 深度} + {$Q/K$ 是 value;$S$ 是 score,不是 mask} + {沿 $d_h$ 收缩;$K^\top$ 换面,收缩边等长} +\end{tikzpicture} +\end{document} diff --git a/examples/tensor-delegate/notes/figures/F2/build/F2-alpha.png b/examples/tensor-delegate/notes/figures/F2/build/F2-alpha.png new file mode 100644 index 0000000..b634795 Binary files /dev/null and b/examples/tensor-delegate/notes/figures/F2/build/F2-alpha.png differ diff --git a/examples/tensor-delegate/notes/figures/F2/build/F2-thumb.png b/examples/tensor-delegate/notes/figures/F2/build/F2-thumb.png new file mode 100644 index 0000000..ab2efaf Binary files /dev/null and b/examples/tensor-delegate/notes/figures/F2/build/F2-thumb.png differ diff --git a/examples/tensor-delegate/notes/figures/F2/build/F2.pdf b/examples/tensor-delegate/notes/figures/F2/build/F2.pdf new file mode 100644 index 0000000..3a80f94 Binary files /dev/null and b/examples/tensor-delegate/notes/figures/F2/build/F2.pdf differ diff --git a/examples/tensor-delegate/notes/figures/F2/build/F2.png b/examples/tensor-delegate/notes/figures/F2/build/F2.png new file mode 100644 index 0000000..a63ea1a Binary files /dev/null and b/examples/tensor-delegate/notes/figures/F2/build/F2.png differ diff --git a/examples/tensor-delegate/notes/figures/F2/build/F2.svg b/examples/tensor-delegate/notes/figures/F2/build/F2.svg new file mode 100644 index 0000000..da7d30f --- /dev/null +++ b/examples/tensor-delegate/notes/figures/F2/build/F2.svg @@ -0,0 +1,607 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/tensor-delegate/notes/notes.tex b/examples/tensor-delegate/notes/notes.tex new file mode 100644 index 0000000..15df6b4 --- /dev/null +++ b/examples/tensor-delegate/notes/notes.tex @@ -0,0 +1,14 @@ +\documentclass[a4paper]{article} +\input{notes-macros} +\renewcommand{\notetitle}{每头打分沿 $d_h$ 收缩} +\renewcommand{\notepaper}{Head scores} +\begin{document} +\tableofcontents +\newpage +\input{sections/sec-01.tex} +\input{sections/sec-02.tex} +\input{sections/sec-03.tex} +\appendix +\section{符号表} +\input{sections/symbols.tex} +\end{document} diff --git a/examples/tensor-delegate/notes/sections/sec-01.tex b/examples/tensor-delegate/notes/sections/sec-01.tex new file mode 100644 index 0000000..1ded60b --- /dev/null +++ b/examples/tensor-delegate/notes/sections/sec-01.tex @@ -0,0 +1,2 @@ +\section{这篇论文在问什么} +每头的 $Q$ 和 $K$ 沿哪一条边收缩,$K^\top$ 要不要真的换面? diff --git a/examples/tensor-delegate/notes/sections/sec-02.tex b/examples/tensor-delegate/notes/sections/sec-02.tex new file mode 100644 index 0000000..b316723 --- /dev/null +++ b/examples/tensor-delegate/notes/sections/sec-02.tex @@ -0,0 +1,20 @@ +\section{主张与贡献} +\splabel{C1} +打分是 $(T\times d_h)(d_h\times T)\to(T\times T)$。$K^\top$ 必须物理换面,收缩边等长。 + +先用中文说完,再写式子: + +\[ +S^{(i)}=Q^{(i)}K^{(i)\top}. +\] + +\begin{itemize} +\item $Q^{(i)}$ — 第 $i$ 头 query,$T\times d_h$ +\item $K^{(i)\top}$ — 转置后的 key,$d_h\times T$ +\item $S^{(i)}$ — 分数,不是 mask +\end{itemize} + +\spfig{F2}{每头打分沿 $d_h$ 收缩。}{重绘自 fixture;toolkit: \texttt{supertensor};ledger id: F2。} + +\subsection{本章小结} +轴长是这张图的主张,所以走 supertensor,不走 superfig。 diff --git a/examples/tensor-delegate/notes/sections/sec-03.tex b/examples/tensor-delegate/notes/sections/sec-03.tex new file mode 100644 index 0000000..4390f1d --- /dev/null +++ b/examples/tensor-delegate/notes/sections/sec-03.tex @@ -0,0 +1,2 @@ +\section{总结与延伸} +形状对齐的公式图只交给 supertensor。 diff --git a/examples/tensor-delegate/outline.md b/examples/tensor-delegate/outline.md new file mode 100644 index 0000000..34640c3 --- /dev/null +++ b/examples/tensor-delegate/outline.md @@ -0,0 +1,14 @@ +# Outline: Head scores + +## Lecture map + +| file | lecture_title | paper_sections | ledger_ids | +|---|---|---|---| +| sec-01.tex | 这篇论文在问什么 | 1 | Q1 | +| sec-02.tex | 主张与贡献 | 1 | C1, F2 | +| sec-03.tex | 总结与延伸 | 1 | C1 | +| sec-app-a.tex | 符号表 | — | | + +## Locked +- 首节标题必须是「这篇论文在问什么」 +- 末节(appendix 前)必须是「总结与延伸」 diff --git a/references/agents.md b/references/agents.md new file mode 100644 index 0000000..a049465 --- /dev/null +++ b/references/agents.md @@ -0,0 +1,14 @@ +# Agents + +Must split when pages > 12 **or** top sections > 4 **or** redraws ≥ 2 **or** the user asks to spawn. Otherwise one agent (including 9–12 pages with ≤4 tops and ≤1 redraw). + +Writer jobs are lecture rows in `outline.md`, not `coverage.sections_in`. Outline writes `ledger.yaml`, `outline.md`, `notes.tex` inputs, and `F*.request.md`. Writers only touch their `sec-XX.tex`. Figure agents only see the request. + +``` +$superpaper <源> 请 spawn 多 sub agents,隔离上下文: + - 1 个 outline agent:ledger.yaml + outline.md + - N 个 writer agents:sections/sec-XX.tex + - 每个计划重绘图 1 个 figure agent:F*.request.md + sibling skill + - 1 个 consistency agent:符号、术语、claim 覆盖 +完成后用 scripts/lint.py 与 build.sh 收口。 +``` diff --git a/references/antipatterns.md b/references/antipatterns.md new file mode 100644 index 0000000..0fd758f --- /dev/null +++ b/references/antipatterns.md @@ -0,0 +1,8 @@ +# Antipatterns + +- Drawing a rewrite as `\sfnode` boxes (use `align`). +- Sending a tensor-shape claim to superfig, or an architecture to supertensor. +- Loading a figure `.sty` in the notes so sibling warnings leak into the article log. +- `\input` of a standalone TikZ source instead of `\spfig`. +- Reusing a `dropped` / retired id for a new object. +- A decorative group or arrow that is not in the ledger. diff --git a/references/api.md b/references/api.md new file mode 100644 index 0000000..c0fcc43 --- /dev/null +++ b/references/api.md @@ -0,0 +1,14 @@ +# Notes macros + +Defined in `assets/notes-macros.tex` (loaded by `assets/notes-template.tex`). + +| macro | args | +|---|---| +| `\splabel{C1}` | hypertarget + label | +| `\spref{C1}` | clickable id | +| `\spsource{§3.2}` | source footnote | +| `\spfig[w]{F1}{caption}{provenance}` | `figures/F1/build/F1.pdf` | +| `\spscreenshot[w]{F2}{caption}{provenance}` | `figures/F2/orig.png` | +| `quotebox` | short quotation | + +Scripts: `ingest.sh`, `lint.py`, `render_ledger.py`, `build.sh`, `screenshot.sh` all take `--work`. diff --git a/references/checklist.md b/references/checklist.md new file mode 100644 index 0000000..0858f23 --- /dev/null +++ b/references/checklist.md @@ -0,0 +1,9 @@ +# Delivery checklist + +- Ledger written first; every core claim has `\splabel`. +- Formula three-beat present wherever display math appears. +- Each figure toolkit matches `scripts/router.py`; mixed-class rows were split. +- Notes do not `\usepackage{superfig|supertensor|superderive}`. +- Vector figures are PDF includes; screenshots are `orig.png` with a source footnote. +- `scripts/lint.py --work` and `scripts/build.sh --work` are green. +- Overfull in the notes log is allowed; missing glyphs and undefined refs are not. diff --git a/references/fallback.md b/references/fallback.md new file mode 100644 index 0000000..ce56ba5 --- /dev/null +++ b/references/fallback.md @@ -0,0 +1,9 @@ +# Fallback + +`preflight.sh`: 0 full, 1 degraded, 2 no XeLaTeX/`article.cls`. + +- Exit 2: deliver `ledger.yaml` + Markdown; say there is no PDF. +- No `pdftotext` / `pdftoppm`: only `--tex` / `--excerpt`. +- No `jsonschema`: create `.venv` (`python3 -m venv .venv && .venv/bin/pip install -r requirements.txt`). +- Sibling `build.sh` fails: drop that figure to `align` or screenshot, keep the notes compiling. +- No `magick`: `screenshot.sh` must fail if `crop_bbox` is set; do not silently ship the full page. diff --git a/references/figure-request.md b/references/figure-request.md new file mode 100644 index 0000000..ba20b10 --- /dev/null +++ b/references/figure-request.md @@ -0,0 +1,11 @@ +# Figure request + +Sibling figure agents see only `notes/figures/F*/F*.request.md` plus that sibling `SKILL.md`. They do not read `ledger.yaml`. + +- superfig: `\sfnode` keys `role, level, gap, bracket, at=`. `\sfconn` has no endpoints. `at` must be TikZ calc with `($…$)`. +- supertensor: every `ststack` / `stface` / `stglyph` has `coord: ""`. +- `allow-raw-tikz` must also appear as `% superfig-lint: allow-raw-tikz` in the `.tex`. +- Build: `superfig/scripts/build.sh F1.tex F1/build` (second arg is the outdir). +- Screenshots: `scripts/screenshot.sh --work --id F2` (`pdftoppm -r 200`, then optional `crop_bbox`). + +Worked files: `examples/pipeline-delegate/notes/figures/F1/F1.request.md`, `examples/tensor-delegate/notes/figures/F2/F2.request.md`. diff --git a/references/input.md b/references/input.md new file mode 100644 index 0000000..4c24884 --- /dev/null +++ b/references/input.md @@ -0,0 +1,12 @@ +# Input + +`scripts/ingest.sh --work (--arxiv ID|--pdf FILE|--tex FILE|--excerpt FILE)` + +| kind | ingest | +|---|---| +| `tex` | copy into `source/tex/`; do not compile | +| `arxiv` | id regex `^(ar[Xx]iv:)?(\d{4}\.\d{4,5}(v\d+)?|[a-z-]+/\d{7})$`; PDF then optional e-print tar (read only) | +| `pdf` | copy `source/paper.pdf`; `pdftotext`; pages `pg-%03d.png` at 120 dpi | +| `excerpt` / `markdown` | `source/excerpt.md`; `coverage.mode=excerpt` | + +Do not OCR as the main path. Architecture figures: redraw with superfig. Numeric plots: screenshot or matplotlib. See `scripts/screenshot.sh` for 200 dpi page crops. diff --git a/references/ledger.md b/references/ledger.md new file mode 100644 index 0000000..ccaa657 --- /dev/null +++ b/references/ledger.md @@ -0,0 +1,9 @@ +# Ledger + +SSOT is `ledger.yaml`. Schema: `assets/ledger.schema.yaml`. Empty template: `assets/ledger.example.yaml`. + +Write the ledger before any `sections/*.tex`. Ids are `C1`, `Q1`, `D1`, `A1`, `L1`, `E1`, `DER1`, `F1`, `SA1` — no `F1a`. Retired ids go in `retired_ids`. + +`symbols[].kind` aliases (`activation` → `value`, `shape-parameter` → `shape parameter`, …) are normalized in `scripts/lint.py` **before** `SP001`. Canonical list is the schema enum. + +v1 derivations use `figure: null` and `align` in the notes. Do not invent a figure for a four-step rewrite. diff --git a/references/pedagogy.md b/references/pedagogy.md new file mode 100644 index 0000000..fd0d744 --- /dev/null +++ b/references/pedagogy.md @@ -0,0 +1,15 @@ +# Pedagogy + +Reuse `youtube-render-pdf` teaching order: motive → idea → mechanism → evidence → takeaway. + +Paper-side changes: + +- Cite `§` / `Eq.(n)` / Figure / Table / page, not timestamps. +- Front page is a bibliography card, not a PDF cover screenshot. +- Math is `\[` or `align`, never `$$`. +- Formula three-beat: Chinese motive, display math, flat symbol list. +- `quotebox` for a short quotation with a source; no long PDF paste. +- End major sections with `\subsection{本章小结}`; end the notes with `\section{总结与延伸}`. +- Figures stay outside boxes. + +Locked first/last titles: 「这篇论文在问什么」…「总结与延伸」, then appendix 符号表 / 推导链一览 / 图表清单. diff --git a/references/router.md b/references/router.md new file mode 100644 index 0000000..7747568 --- /dev/null +++ b/references/router.md @@ -0,0 +1,14 @@ +# Router + +Authority: `scripts/router.py`. `suggest()` returns a **class**, not a toolkit. + +| class | signals (any hit) | accepted toolkits | +|---|---|---| +| `numeric` | loss-curve, bar, scatter, histogram, numeric-plot | screenshot, matplotlib | +| `raster` | table, photo, apparatus, ui | screenshot | +| `tensor` | axis, shape, transpose, broadcast, gather, shard, contraction, face | supertensor | +| `fig` | architecture, pipeline, data-flow, state, time, dependency, what-eats-what, argument-map | superfig | +| `derive` | rewrite-figure, cancel-visual, subst-visual | v1: align | +| `none` | otherwise | none | + +Mixed classes on one row → `SP011`. Split with the next integer ids. Default: do not draw. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d86d7ac --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +jsonschema>=4.18 +PyYAML>=6.0 diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..f9cc802 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Lint, project the ledger, compile notes. Overfull is logged, not fatal. +# ./scripts/build.sh --work +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORK="" +while [[ $# -gt 0 ]]; do + case "$1" in + --work|--out) WORK="$2"; shift 2 ;; + *) echo "usage: build.sh --work DIR" >&2; exit 2 ;; + esac +done +[[ -n "$WORK" ]] || { echo "usage: build.sh --work DIR" >&2; exit 2; } +WORK="$(cd "$WORK" && pwd)" +NOTES="$WORK/notes" +[[ -f "$WORK/ledger.yaml" ]] || { echo "no ledger.yaml in $WORK" >&2; exit 2; } +[[ -f "$NOTES/notes.tex" ]] || { echo "no notes/notes.tex in $WORK" >&2; exit 2; } + +echo "==> lint" +"$ROOT/scripts/python" "$ROOT/scripts/lint.py" --work "$WORK" + +echo "==> render ledger" +"$ROOT/scripts/python" "$ROOT/scripts/render_ledger.py" --work "$WORK" + +mkdir -p "$WORK/out" +echo "==> xelatex" +( + cd "$NOTES" + export TEXINPUTS="$ROOT/assets:$NOTES:" + xelatex -halt-on-error -interaction=nonstopmode notes.tex >/dev/null + xelatex -halt-on-error -interaction=nonstopmode notes.tex >/dev/null +) + +LOG="$NOTES/notes.log" +status=0 +if grep -q "Missing character" "$LOG"; then + echo "!! missing glyphs:" >&2 + grep -m5 "Missing character" "$LOG" >&2 + status=1 +fi +if grep -qE "LaTeX Warning: (Reference|Citation|There were undefined references|Label\(s\) may have changed)" "$LOG"; then + if grep -q "undefined" "$LOG"; then + echo "!! undefined references:" >&2 + grep -m5 "undefined" "$LOG" >&2 || true + status=1 + fi +fi +if grep -q "multiply-defined" "$LOG"; then + echo "!! multiply-defined labels:" >&2 + grep -m5 "multiply-defined" "$LOG" >&2 + status=1 +fi +if grep -qE "^(Overfull|Underfull) \\\\[hv]box" "$LOG"; then + echo "==> overfull/underfull (recorded, not fatal)" + grep -m5 -E "^(Overfull|Underfull) \\\\[hv]box" "$LOG" || true +fi + +cp -f "$NOTES/notes.pdf" "$WORK/out/notes.pdf" +echo "==> $WORK/out/notes.pdf" +exit $status diff --git a/scripts/ingest.sh b/scripts/ingest.sh new file mode 100755 index 0000000..f859ed2 --- /dev/null +++ b/scripts/ingest.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Ingest a paper source into a --work tree. Never compiles e-print TeX. +set -euo pipefail + +WORK="" +KIND="" +SRC="" +while [[ $# -gt 0 ]]; do + case "$1" in + --work|--out) WORK="$2"; shift 2 ;; + --arxiv) KIND=arxiv; SRC="$2"; shift 2 ;; + --pdf) KIND=pdf; SRC="$2"; shift 2 ;; + --tex) KIND=tex; SRC="$2"; shift 2 ;; + --excerpt|--markdown) KIND=excerpt; SRC="$2"; shift 2 ;; + *) echo "usage: ingest.sh --work DIR (--arxiv ID|--pdf FILE|--tex FILE|--excerpt FILE)" >&2; exit 2 ;; + esac +done +[[ -n "$WORK" && -n "$KIND" ]] || { echo "need --work and a source" >&2; exit 2; } + +ARXIV_RE='^(ar[Xx]iv:)?([0-9]{4}\.[0-9]{4,5}(v[0-9]+)?|[a-z-]+/[0-9]{7})$' + +mkdir -p "$WORK/source/pages" "$WORK/notes/sections" "$WORK/out" +meta="$WORK/source/meta.yaml" + +sha_of() { sha256sum "$1" | awk '{print $1}'; } + +write_meta() { + local extra="$1" + cat > "$meta" <&2; exit 2; } + id="${id#arxiv:}"; id="${id#arXiv:}" + mkdir -p "$WORK/source" + pdf="$WORK/source/paper.pdf" + if [[ ! -f "$pdf" ]]; then + curl -fsSL "https://arxiv.org/pdf/${id}.pdf" -o "$pdf" \ + || curl -fsSL "https://export.arxiv.org/pdf/${id}.pdf" -o "$pdf" + fi + pdftotext -layout "$pdf" "$WORK/source/paper.txt" || true + pdftoppm -png -r 120 "$pdf" "$WORK/source/pages/pg" + python3 - <<'PY' "$WORK/source/pages" +from pathlib import Path +import sys +d = Path(sys.argv[1]) +files = sorted(d.glob("pg*.png")) +for i, p in enumerate(files, 1): + dest = d / f"pg-{i:03d}.png" + if p.resolve() != dest.resolve(): + p.rename(dest) +PY + # best-effort e-print; never compile + if [[ ! -d "$WORK/source/eprint" ]]; then + tmp=$(mktemp) + if curl -fsSL "https://arxiv.org/e-print/${id}" -o "$tmp"; then + mkdir -p "$WORK/source/eprint" + tar -xf "$tmp" -C "$WORK/source/eprint" --max-size=50M 2>/dev/null \ + || tar -xf "$tmp" -C "$WORK/source/eprint" || true + fi + rm -f "$tmp" + fi + pages=$(find "$WORK/source/pages" -name 'pg-*.png' | wc -l) + write_meta "arxiv: $id +local_pdf: source/paper.pdf +sha256: $(sha_of "$pdf") +pages: $pages" + ;; +esac + +echo "ingested $KIND -> $WORK" diff --git a/scripts/lint.py b/scripts/lint.py new file mode 100755 index 0000000..406ae14 --- /dev/null +++ b/scripts/lint.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 +"""Ledger and notes lint for superpaper. Codes live in DESIGN.md §10.""" + +from __future__ import annotations + +import argparse +import os +import re +import sys +from copy import deepcopy +from pathlib import Path + +import yaml +from jsonschema import Draft7Validator + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "scripts")) +from router import SIBLING, check_row, phase_from_env # noqa: E402 + +SCHEMA_PATH = ROOT / "assets" / "ledger.schema.yaml" +KNOWN_TOP = { + "schema", "retired_ids", "paper", "coverage", "questions", "claims", + "definitions", "assumptions", "lemmas", "symbols", "derivations", + "figures", "evidence", "terms", "source_assets", +} +ID_GROUPS = ( + "questions", "claims", "definitions", "assumptions", "lemmas", + "derivations", "figures", "evidence", "source_assets", +) +KIND_ALIASES = { + "activation": "value", + "value / activation": "value", + "logit": "score", + "score / logit": "score", + "coordinate": "index", + "index / coordinate": "index", + "order": "rank", + "rank / order": "rank", + "support": "mask", + "mask / support": "mask", + "shape-parameter": "shape parameter", + "shape_parameter": "shape parameter", +} +STY_RE = re.compile(r"\\usepackage(?:\[[^\]]*\])?\{(superfig|supertensor|superderive)\}") +LABEL_RE = re.compile(r"\\splabel\{(C[1-9][0-9]*)\}") + + +def emit(path: Path, messages: list[str]) -> None: + print(f"!! {path}", file=sys.stderr) + for msg in messages: + print(f" {msg}", file=sys.stderr) + + +def normalize_kinds(data: dict) -> dict: + out = deepcopy(data) + for sym in out.get("symbols") or []: + if isinstance(sym, dict) and "kind" in sym: + kind = sym["kind"] + if kind in KIND_ALIASES: + sym["kind"] = KIND_ALIASES[kind] + return out + + +def collect_ids(data: dict) -> list[str]: + ids: list[str] = [] + for key in ID_GROUPS: + for row in data.get(key) or []: + if isinstance(row, dict) and "id" in row: + ids.append(str(row["id"])) + return ids + + +def notes_text(notes_paths: list[Path]) -> str: + chunks: list[str] = [] + for path in notes_paths: + if path.is_file(): + chunks.append(path.read_text(encoding="utf-8")) + return "\n".join(chunks) + + +def find_notes(work: Path | None, notes: Path | None) -> list[Path]: + found: list[Path] = [] + if notes is not None: + found.append(notes) + if work is not None: + nd = work / "notes" + if (nd / "notes.tex").is_file(): + found.append(nd / "notes.tex") + found.extend(sorted(nd.glob("sections/*.tex"))) + # unique, keep order + seen: set[Path] = set() + uniq: list[Path] = [] + for p in found: + rp = p.resolve() + if rp not in seen: + seen.add(rp) + uniq.append(p) + return uniq + + +def lint( + ledger_path: Path, + *, + work: Path | None, + extra_notes: Path | None, + disk: bool, + notes_scan: bool, +) -> tuple[list[str], list[str]]: + errors: list[str] = [] + warnings: list[str] = [] + raw = yaml.safe_load(ledger_path.read_text(encoding="utf-8")) + if not isinstance(raw, dict): + return ["SP001 ledger is not a mapping"], warnings + + data = normalize_kinds(raw) + schema = yaml.safe_load(SCHEMA_PATH.read_text(encoding="utf-8")) + validator = Draft7Validator(schema) + for err in validator.iter_errors(data): + loc = ".".join(str(p) for p in err.absolute_path) or "$" + errors.append(f"SP001 {loc}: {err.message}") + if errors: + return errors, warnings + + for key in data: + if key not in KNOWN_TOP: + warnings.append(f"unknown top-level key {key!r}") + + coverage = data.get("coverage") or {} + if coverage.get("mode") == "full" and not coverage.get("sections_in"): + warnings.append("coverage.mode=full and sections_in is empty") + + ids = collect_ids(data) + seen: set[str] = set() + for i in ids: + if i in seen: + errors.append(f"SP002 duplicate id {i}") + seen.add(i) + + retired = set(data.get("retired_ids") or []) + for row_key in ID_GROUPS: + for row in data.get(row_key) or []: + if not isinstance(row, dict): + continue + rid = row.get("id") + if rid in retired and row.get("status") != "dropped": + errors.append(f"SP023 {rid} is in retired_ids") + + claim_ids = {c["id"] for c in (data.get("claims") or []) if "id" in c} + phase = phase_from_env() + notes_paths = find_notes(work, extra_notes) + body = notes_text(notes_paths) if notes_scan else "" + + if notes_scan: + for path in notes_paths: + text = path.read_text(encoding="utf-8") + if STY_RE.search(text): + errors.append(f"SP003 {path.name} loads a figure package") + + labels = set(LABEL_RE.findall(body)) if notes_scan else set() + + for fig in data.get("figures") or []: + fid = fig.get("id", "F?") + signals = set(fig.get("signals") or []) + toolkit = fig.get("toolkit") + status = fig.get("status", "planned") + include = fig.get("include", None) + code = check_row( + signals, toolkit, status=status, include=include, + fig_id=fid, phase=phase, + ) + if code: + errors.append(f"{code} {fid}") + if toolkit == "superderive" and phase < 2: + errors.append(f"SP022 {fid} toolkit superderive is v2; use align") + if toolkit in SIBLING and status != "dropped" and disk: + req = fig.get("request") + req_path = (work / "notes" / req) if (work and req) else None + if not req or req_path is None or not req_path.is_file(): + errors.append(f"SP012 {fid} missing request file") + if include and disk and work is not None: + on_disk = work / "notes" / include + if not on_disk.is_file(): + errors.append(f"SP021 {fid} include not on disk: {include}") + claim = fig.get("claim") + if claim and claim not in claim_ids: + errors.append(f"SP024 {fid} claim {claim} is not a known C*") + + for der in data.get("derivations") or []: + claim = der.get("claim") + if claim and claim not in claim_ids: + errors.append(f"SP024 {der.get('id')} claim {claim} is not a known C*") + + if notes_scan: + for claim in data.get("claims") or []: + if claim.get("status") == "core" and claim.get("id") not in labels: + errors.append(f"SP020 core {claim['id']} has no \\splabel") + for sym in data.get("symbols") or []: + name = str(sym.get("name", "")) + latex = str(sym.get("latex", "")) + if name and name not in body and latex and latex not in body: + warnings.append(f"symbol {name} never appears in notes") + + return errors, warnings + + +def main() -> int: + parser = argparse.ArgumentParser(description="lint a superpaper ledger / notes tree") + parser.add_argument("--work", help="work tree root (ledger.yaml + notes/)") + parser.add_argument("--out", dest="work_alias", help="alias of --work") + parser.add_argument("--ledger", help="ledger.yaml (skips disk/notes codes unless --notes)") + parser.add_argument("--notes", help="extra notes.tex for SP003/SP020") + args = parser.parse_args() + + work = Path(args.work or args.work_alias).resolve() if (args.work or args.work_alias) else None + if work is not None: + ledger = work / "ledger.yaml" + disk = True + notes_scan = True + elif args.ledger: + ledger = Path(args.ledger).resolve() + disk = False + notes_scan = args.notes is not None + else: + parser.error("need --work or --ledger") + return 2 + + extra = Path(args.notes).resolve() if args.notes else None + if not ledger.is_file(): + print(f"!! {ledger}", file=sys.stderr) + print(" SP000 no such ledger", file=sys.stderr) + return 2 + + errors, warnings = lint(ledger, work=work, extra_notes=extra, disk=disk, notes_scan=notes_scan) + if errors: + emit(ledger, errors) + return 1 + for warn in warnings: + print(f"warning: {warn}", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/preflight.sh b/scripts/preflight.sh new file mode 100755 index 0000000..90a3f9e --- /dev/null +++ b/scripts/preflight.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Notes toolchain preflight. 0 = full, 1 = degraded, 2 = no LaTeX engine. +set -uo pipefail + +QUIET=0 +[[ "${1:-}" == "--quiet" ]] && QUIET=1 +say() { [[ $QUIET -eq 1 ]] || echo -e "$*"; } + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ok=0; warn=0; fail=0 +check() { + local name="$1"; shift + if "$@" >/dev/null 2>&1; then say " ok $name"; ok=$((ok+1)); return 0 + else say " MISS $name"; return 1; fi +} + +say "superpaper preflight" +say "--- engine ---" +check "xelatex" command -v xelatex || fail=$((fail+1)) +check "article.cls" kpsewhich article.cls || fail=$((fail+1)) + +say "--- template / CJK ---" +cjk=0 +for pkg in ctex.sty FandolSong-Regular.otf amsmath.sty amssymb.sty \ + tcolorbox.sty graphicx.sty hyperref.sty geometry.sty \ + listings.sty booktabs.sty subcaption.sty float.sty tikz.sty etoolbox.sty; do + check "$pkg" kpsewhich "$pkg" || { warn=$((warn+1)); cjk=1; } +done + +say "--- python ---" +PY="$ROOT/scripts/python" +check "python3" command -v python3 || warn=$((warn+1)) +check "PyYAML" "$PY" -c "import yaml" || warn=$((warn+1)) +check "jsonschema" "$PY" -c "import jsonschema" || warn=$((warn+1)) + +say "--- ingest ---" +check "pdftotext" command -v pdftotext || warn=$((warn+1)) +check "pdftoppm" command -v pdftoppm || warn=$((warn+1)) + +say "--- optional ---" +check "magick" command -v magick || true +check "pdftocairo" command -v pdftocairo || true + +if [[ $fail -gt 0 ]]; then + say "" + say "RESULT: no LaTeX. Deliver ledger + Markdown; no PDF." + exit 2 +fi +if [[ $warn -gt 0 ]]; then + say "" + say "RESULT: degraded." + [[ $cjk -eq 1 ]] && say " - missing CJK/template package -> English notes or later Missing character." + exit 1 +fi +say "" +say "RESULT: full notes path (XeLaTeX + CJK + lint)." +exit 0 diff --git a/scripts/python b/scripts/python new file mode 100755 index 0000000..11a6ec2 --- /dev/null +++ b/scripts/python @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Prefer the local venv so jsonschema is available without a system install. +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +if [[ -x "$ROOT/.venv/bin/python" ]]; then + exec "$ROOT/.venv/bin/python" "$@" +fi +exec python3 "$@" diff --git a/scripts/render_ledger.py b/scripts/render_ledger.py new file mode 100755 index 0000000..a322af2 --- /dev/null +++ b/scripts/render_ledger.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Project ledger.yaml into notes/sections/symbols.tex and optional claims.md.""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +import yaml + + +def tex_escape(text: str) -> str: + return ( + text.replace("\\", "\\textbackslash{}") + .replace("&", "\\&") + .replace("%", "\\%") + .replace("#", "\\#") + .replace("_", "\\_") + ) + + +def render(work: Path) -> None: + ledger = yaml.safe_load((work / "ledger.yaml").read_text(encoding="utf-8")) + symbols = ledger.get("symbols") or [] + claims = ledger.get("claims") or [] + sec = work / "notes" / "sections" + sec.mkdir(parents=True, exist_ok=True) + lines = [ + "% generated by render_ledger.py — do not edit", + "\\begin{itemize}", + ] + if not symbols: + lines.append("\\item (无符号)") + for sym in symbols: + name = tex_escape(str(sym.get("name", ""))) + meaning = tex_escape(str(sym.get("meaning", ""))) + kind = tex_escape(str(sym.get("kind", ""))) + latex = sym.get("latex") or name + lines.append(f"\\item ${latex}$ — {meaning} ({kind};{name})") + lines.append("\\end{itemize}") + (sec / "symbols.tex").write_text("\n".join(lines) + "\n", encoding="utf-8") + + md = ["# Claims", ""] + for c in claims: + md.append(f"- `{c.get('id')}` ({c.get('status')}): {c.get('text')}") + (work / "claims.md").write_text("\n".join(md) + "\n", encoding="utf-8") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--work", required=False) + parser.add_argument("--out", dest="work") + args = parser.parse_args() + if not args.work: + print("usage: render_ledger.py --work ", file=sys.stderr) + return 2 + work = Path(args.work).resolve() + if not (work / "ledger.yaml").is_file(): + print(f"no ledger at {work / 'ledger.yaml'}", file=sys.stderr) + return 2 + render(work) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/router.py b/scripts/router.py new file mode 100755 index 0000000..e33495d --- /dev/null +++ b/scripts/router.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Figure-class router. suggest() returns a class, never a toolkit.""" + +from __future__ import annotations + +NUMERIC = frozenset({"loss-curve", "bar", "scatter", "histogram", "numeric-plot"}) +RASTER = frozenset({"table", "photo", "apparatus", "ui"}) +TENSOR = frozenset({ + "axis", "shape", "transpose", "broadcast", "gather", + "shard", "contraction", "face", +}) +DERIVE = frozenset({"rewrite-figure", "cancel-visual", "subst-visual"}) +FIG = frozenset({ + "architecture", "pipeline", "data-flow", "state", "time", + "dependency", "what-eats-what", "argument-map", +}) + +CLASS_SIGNALS = { + "numeric": NUMERIC, + "raster": RASTER, + "tensor": TENSOR, + "derive": DERIVE, + "fig": FIG, +} + +VECTOR = frozenset({"superfig", "supertensor", "superderive", "matplotlib"}) +SIBLING = frozenset({"superfig", "supertensor", "superderive"}) + + +def classify(signals: set[str]) -> list[str]: + s = set(signals) + hit = [c for c, vocab in CLASS_SIGNALS.items() if s & vocab] + return hit or ["none"] + + +def suggest(signals: set[str], *, phase: int = 1) -> str: + """Return a class name, or 'SPLIT:a+b+...' in CLASS_SIGNALS order.""" + classes = classify(signals) + if len(classes) > 1: + return "SPLIT:" + "+".join(classes) + return classes[0] + + +def accepted(cls: str, *, phase: int = 1) -> frozenset[str]: + if cls == "numeric": + return frozenset({"screenshot", "matplotlib"}) + if cls == "raster": + return frozenset({"screenshot"}) + if cls == "tensor": + return frozenset({"supertensor"}) + if cls == "fig": + return frozenset({"superfig"}) + if cls == "derive": + return frozenset({"align", "superderive"} if phase >= 2 else {"align"}) + if cls == "none": + return frozenset({"none"}) + raise KeyError(cls) + + +def include_pdf(fig_id: str) -> str: + return f"figures/{fig_id}/build/{fig_id}.pdf" + + +def include_png(fig_id: str) -> str: + return f"figures/{fig_id}/orig.png" + + +def check_row( + signals: set[str], + toolkit: str, + *, + status: str = "planned", + include: str | None = None, + fig_id: str = "F1", + phase: int = 1, +) -> str | None: + """First matching code, or None. SP010 is not an include/status rule.""" + classes = classify(signals) + if len(classes) > 1: + return "SP011" + if toolkit not in accepted(classes[0], phase=phase): + return "SP010" + if toolkit == "none" and status != "dropped": + return "SP013" + if toolkit in {"align", "none"} and include is not None: + return "SP014" + if status == "included" and toolkit in VECTOR and include != include_pdf(fig_id): + return "SP015" + if status == "included" and toolkit == "screenshot" and include != include_png(fig_id): + return "SP016" + return None + + +def phase_from_env() -> int: + import os + raw = os.environ.get("SUPERPAPER_PHASE", "1") + try: + return int(raw) + except ValueError: + return 1 diff --git a/scripts/screenshot.sh b/scripts/screenshot.sh new file mode 100755 index 0000000..c1fd3b4 --- /dev/null +++ b/scripts/screenshot.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Raster one ledger figure from paper.pdf via pdftoppm. crop_bbox is 200 dpi page pixels. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORK="" +FID="" +while [[ $# -gt 0 ]]; do + case "$1" in + --work|--out) WORK="$2"; shift 2 ;; + --id) FID="$2"; shift 2 ;; + *) echo "usage: screenshot.sh --work DIR --id F2" >&2; exit 2 ;; + esac +done +[[ -n "$WORK" && -n "$FID" ]] || { echo "need --work and --id" >&2; exit 2; } + +"$ROOT/scripts/python" - "$WORK" "$FID" <<'PY' +import sys, tempfile, subprocess, shutil +from pathlib import Path +import yaml + +work = Path(sys.argv[1]) +fid = sys.argv[2] +ledger = yaml.safe_load((work / "ledger.yaml").read_text()) +fig = next((f for f in (ledger.get("figures") or []) if f.get("id") == fid), None) +if fig is None: + sys.exit(f"no figure {fid}") +pages = fig.get("source_pages") or [] +if not pages: + sys.exit("source_pages required") +n = int(pages[0]) +pdf = work / "source" / "paper.pdf" +if not pdf.is_file(): + sys.exit(f"missing {pdf}") +dest_dir = work / "notes" / "figures" / fid +dest_dir.mkdir(parents=True, exist_ok=True) +dest = dest_dir / "orig.png" +bbox = fig.get("crop_bbox") +with tempfile.TemporaryDirectory() as td: + prefix = Path(td) / "pg" + subprocess.run( + ["pdftoppm", "-png", "-r", "200", "-f", str(n), "-l", str(n), str(pdf), str(prefix)], + check=True, + ) + produced = sorted(Path(td).glob("pg*.png")) + if not produced: + sys.exit("pdftoppm produced no page") + page = produced[0] + if bbox is None: + shutil.copy2(page, dest) + else: + if shutil.which("magick") is None: + sys.exit("crop_bbox set but magick is missing") + x0, y0, x1, y1 = bbox + w, h = int(x1 - x0), int(y1 - y0) + subprocess.run( + ["magick", str(page), "-crop", f"{w}x{h}+{int(x0)}+{int(y0)}", "+repage", str(dest)], + check=True, + ) +print(dest) +PY diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..8c47b1c --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Superpaper v1 checks: router unit tests, ledger codes, work-tree builds. +set -uo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PY="$ROOT/scripts/python" +LINT=("$PY" "$ROOT/scripts/lint.py") +fail=0 + +ok() { echo " ok $*"; } +bad() { echo " FAIL $*"; fail=$((fail+1)); } + +echo "==> router_test" +if "$PY" "$ROOT/tests/router_test.py"; then ok router_test.py; else bad router_test.py; fi + +echo "==> ledger-valid" +if "${LINT[@]}" --ledger "$ROOT/tests/ledger-valid.yaml" >/dev/null 2>&1; then + ok ledger-valid.yaml +else + bad ledger-valid.yaml +fi + +expect_ledger() { + local file="$1" code="$2" + local out + out=$("${LINT[@]}" --ledger "$file" 2>&1) || true + if echo "$out" | grep -q "$code"; then + ok "$(basename "$file") ($code)" + else + bad "$(basename "$file") (wanted $code)" + echo "$out" | sed 's/^/ /' + fi +} + +echo "==> ledger-invalid" +for spec in \ + "missing-paper-id.yaml:SP001" \ + "bad-toolkit.yaml:SP001" \ + "duplicate-id.yaml:SP002" \ + "mixed-signals.yaml:SP011" \ + "toolkit-mismatch.yaml:SP010" \ + "none-not-dropped.yaml:SP013" \ + "align-has-include.yaml:SP014" \ + "included-null-include.yaml:SP015" \ + "screenshot-bad-include.yaml:SP016" \ + "superderive-v1.yaml:SP022" \ + "retired-reuse.yaml:SP023" \ + "dangling-claim.yaml:SP024" +do + f="${spec%%:*}"; c="${spec##*:}" + expect_ledger "$ROOT/tests/ledger-invalid/$f" "$c" +done + +expect_work() { + local dir="$1" code="$2" + local out + out=$("${LINT[@]}" --work "$dir" 2>&1) || true + if echo "$out" | grep -q "$code"; then + ok "$(basename "$dir") ($code)" + else + bad "$(basename "$dir") (wanted $code)" + echo "$out" | sed 's/^/ /' + fi +} + +echo "==> work-tree lint" +expect_work "$ROOT/tests/loads-sty" SP003 +expect_work "$ROOT/tests/missing-request" SP012 +expect_work "$ROOT/tests/unlabeled-core" SP020 +expect_work "$ROOT/tests/missing-include" SP021 + +echo "==> compile" +for dir in \ + "$ROOT/tests/notes-smoke" \ + "$ROOT/examples/excerpt-toy" \ + "$ROOT/examples/pipeline-delegate" \ + "$ROOT/examples/tensor-delegate" +do + name="$(basename "$dir")" + if "$ROOT/scripts/build.sh" --work "$dir" >/dev/null 2>&1; then + ok "$name (build)" + else + bad "$name (build)" + "$ROOT/scripts/build.sh" --work "$dir" || true + fi +done + +if [[ $fail -gt 0 ]]; then + echo "$fail failing check(s)" >&2 + exit 1 +fi +echo "all v1 checks passed" diff --git a/tests/ledger-invalid/align-has-include.yaml b/tests/ledger-invalid/align-has-include.yaml new file mode 100644 index 0000000..3866c19 --- /dev/null +++ b/tests/ledger-invalid/align-has-include.yaml @@ -0,0 +1,29 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: align + grammar: derivation + toolkit: align + signals: [rewrite-figure] + include: figures/F1/build/F1.pdf + status: included + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/bad-toolkit.yaml b/tests/ledger-invalid/bad-toolkit.yaml new file mode 100644 index 0000000..5006deb --- /dev/null +++ b/tests/ledger-invalid/bad-toolkit.yaml @@ -0,0 +1,28 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: bad + grammar: pipeline + toolkit: not-a-toolkit + signals: [pipeline] + status: planned + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/dangling-claim.yaml b/tests/ledger-invalid/dangling-claim.yaml new file mode 100644 index 0000000..045bcb1 --- /dev/null +++ b/tests/ledger-invalid/dangling-claim.yaml @@ -0,0 +1,28 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C99 + title: dangling + grammar: pipeline + toolkit: superfig + signals: [pipeline] + status: planned + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/duplicate-id.yaml b/tests/ledger-invalid/duplicate-id.yaml new file mode 100644 index 0000000..156691a --- /dev/null +++ b/tests/ledger-invalid/duplicate-id.yaml @@ -0,0 +1,21 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: one, kind: contribution, status: core} + - {id: C1, text: two, kind: contribution, status: supporting} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: [] +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/included-null-include.yaml b/tests/ledger-invalid/included-null-include.yaml new file mode 100644 index 0000000..0a4a3c8 --- /dev/null +++ b/tests/ledger-invalid/included-null-include.yaml @@ -0,0 +1,30 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: vector + grammar: pipeline + toolkit: superfig + signals: [pipeline] + include: null + request: figures/F1/F1.request.md + status: included + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/missing-paper-id.yaml b/tests/ledger-invalid/missing-paper-id.yaml new file mode 100644 index 0000000..0c8c7c0 --- /dev/null +++ b/tests/ledger-invalid/missing-paper-id.yaml @@ -0,0 +1,6 @@ +schema: superpaper.ledger/v1 +paper: + title: Missing id + source: {kind: excerpt} +coverage: + mode: excerpt diff --git a/tests/ledger-invalid/mixed-signals.yaml b/tests/ledger-invalid/mixed-signals.yaml new file mode 100644 index 0000000..6226e89 --- /dev/null +++ b/tests/ledger-invalid/mixed-signals.yaml @@ -0,0 +1,28 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: mixed + grammar: architecture + toolkit: superfig + signals: [axis, architecture] + status: planned + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/none-not-dropped.yaml b/tests/ledger-invalid/none-not-dropped.yaml new file mode 100644 index 0000000..1727f3b --- /dev/null +++ b/tests/ledger-invalid/none-not-dropped.yaml @@ -0,0 +1,28 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: none + grammar: none + toolkit: none + signals: [] + status: planned + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/retired-reuse.yaml b/tests/ledger-invalid/retired-reuse.yaml new file mode 100644 index 0000000..a1f0da7 --- /dev/null +++ b/tests/ledger-invalid/retired-reuse.yaml @@ -0,0 +1,20 @@ +schema: superpaper.ledger/v1 +retired_ids: [C1] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: [] +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/screenshot-bad-include.yaml b/tests/ledger-invalid/screenshot-bad-include.yaml new file mode 100644 index 0000000..029b39c --- /dev/null +++ b/tests/ledger-invalid/screenshot-bad-include.yaml @@ -0,0 +1,29 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F2 + claim: C1 + title: shot + grammar: screenshot + toolkit: screenshot + signals: [table] + include: figures/F2/wrong.png + status: included + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/superderive-v1.yaml b/tests/ledger-invalid/superderive-v1.yaml new file mode 100644 index 0000000..424cb0e --- /dev/null +++ b/tests/ledger-invalid/superderive-v1.yaml @@ -0,0 +1,28 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: derive + grammar: derivation + toolkit: superderive + signals: [rewrite-figure] + status: planned + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-invalid/toolkit-mismatch.yaml b/tests/ledger-invalid/toolkit-mismatch.yaml new file mode 100644 index 0000000..a92be9d --- /dev/null +++ b/tests/ledger-invalid/toolkit-mismatch.yaml @@ -0,0 +1,28 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: mismatch + grammar: screenshot + toolkit: superfig + signals: [loss-curve] + status: planned + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/ledger-valid.yaml b/tests/ledger-valid.yaml new file mode 100644 index 0000000..1c25654 --- /dev/null +++ b/tests/ledger-valid.yaml @@ -0,0 +1,20 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: [] +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/loads-sty/ledger.yaml b/tests/loads-sty/ledger.yaml new file mode 100644 index 0000000..1c25654 --- /dev/null +++ b/tests/loads-sty/ledger.yaml @@ -0,0 +1,20 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: [] +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/loads-sty/notes/notes.tex b/tests/loads-sty/notes/notes.tex new file mode 100644 index 0000000..c8e1324 --- /dev/null +++ b/tests/loads-sty/notes/notes.tex @@ -0,0 +1,7 @@ +\documentclass{article} +\usepackage{amsmath} +\begin{document} +\usepackage{superfig} + +hello +\end{document} diff --git a/tests/missing-include/ledger.yaml b/tests/missing-include/ledger.yaml new file mode 100644 index 0000000..f8d58c2 --- /dev/null +++ b/tests/missing-include/ledger.yaml @@ -0,0 +1,30 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: missing pdf + grammar: pipeline + toolkit: superfig + signals: [pipeline] + request: figures/F1/F1.request.md + include: figures/F1/build/F1.pdf + status: included + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/missing-include/notes/figures/F1/F1.request.md b/tests/missing-include/notes/figures/F1/F1.request.md new file mode 100644 index 0000000..5283e83 --- /dev/null +++ b/tests/missing-include/notes/figures/F1/F1.request.md @@ -0,0 +1 @@ +toolkit: superfig diff --git a/tests/missing-include/notes/notes.tex b/tests/missing-include/notes/notes.tex new file mode 100644 index 0000000..b22803b --- /dev/null +++ b/tests/missing-include/notes/notes.tex @@ -0,0 +1,5 @@ +\documentclass{article} +\begin{document} +\splabel{C1} +ok +\end{document} diff --git a/tests/missing-request/ledger.yaml b/tests/missing-request/ledger.yaml new file mode 100644 index 0000000..5524d39 --- /dev/null +++ b/tests/missing-request/ledger.yaml @@ -0,0 +1,29 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: + - id: F1 + claim: C1 + title: missing req + grammar: pipeline + toolkit: superfig + signals: [pipeline] + request: figures/F1/F1.request.md + status: planned + +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/missing-request/notes/notes.tex b/tests/missing-request/notes/notes.tex new file mode 100644 index 0000000..b22803b --- /dev/null +++ b/tests/missing-request/notes/notes.tex @@ -0,0 +1,5 @@ +\documentclass{article} +\begin{document} +\splabel{C1} +ok +\end{document} diff --git a/tests/notes-smoke/ledger.yaml b/tests/notes-smoke/ledger.yaml new file mode 100644 index 0000000..41dfbd1 --- /dev/null +++ b/tests/notes-smoke/ledger.yaml @@ -0,0 +1,21 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: smoke + title: Smoke + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: smoke, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: + - {name: x, latex: "x", meaning: "input", kind: value} +derivations: [] +figures: [] +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/notes-smoke/notes/notes.tex b/tests/notes-smoke/notes/notes.tex new file mode 100644 index 0000000..e76ab90 --- /dev/null +++ b/tests/notes-smoke/notes/notes.tex @@ -0,0 +1,14 @@ +\documentclass[a4paper]{article} +\input{notes-macros} +\renewcommand{\notetitle}{Smoke} +\begin{document} +\section{这篇论文在问什么} +一段动机。 +\section{主张与贡献} +\splabel{C1} 这是核心主张。 +\section{总结与延伸} +结束。 +\appendix +\section{符号表} +\input{sections/symbols.tex} +\end{document} diff --git a/tests/router_test.py b/tests/router_test.py new file mode 100755 index 0000000..203250c --- /dev/null +++ b/tests/router_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) +from router import accepted, check_row, suggest + + +def expect(cond, msg): + if not cond: + raise SystemExit(f"FAIL: {msg}") + + +expect(suggest({"loss-curve"}) == "numeric", "loss-curve class") +expect(accepted("numeric") == frozenset({"screenshot", "matplotlib"}), "numeric accepted") +expect(suggest({"table"}) == "raster", "table class") +expect("matplotlib" not in accepted("raster"), "raster rejects matplotlib") +expect(suggest({"axis", "architecture"}) == "SPLIT:tensor+fig", "split order") +expect( + check_row({"axis", "architecture"}, "superfig") == "SP011", + "mixed row", +) +expect(check_row({"rewrite-figure"}, "align", phase=1) is None, "align ok") +expect(check_row({"rewrite-figure"}, "superderive", phase=1) == "SP010", "derive v1") +expect(suggest(set()) == "none", "empty") +expect(suggest({"notation"}) == "none", "notation") +expect(check_row(set(), "none", status="included") == "SP013", "none included") +expect( + check_row({"rewrite-figure"}, "align", include="figures/F1/build/F1.pdf") == "SP014", + "align include", +) +expect( + check_row({"architecture"}, "superfig", status="included", include=None) == "SP015", + "vector null include", +) +expect( + check_row({"table"}, "screenshot", status="included", include="nope.png", fig_id="F2") + == "SP016", + "screenshot path", +) +print("router_test ok") diff --git a/tests/unlabeled-core/ledger.yaml b/tests/unlabeled-core/ledger.yaml new file mode 100644 index 0000000..1c25654 --- /dev/null +++ b/tests/unlabeled-core/ledger.yaml @@ -0,0 +1,20 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: toy + title: Toy + source: {kind: excerpt} +coverage: + mode: excerpt +questions: [] +claims: + - {id: C1, text: core claim, kind: contribution, status: core} +definitions: [] +assumptions: [] +lemmas: [] +symbols: [] +derivations: [] +figures: [] +evidence: [] +terms: [] +source_assets: [] diff --git a/tests/unlabeled-core/notes/notes.tex b/tests/unlabeled-core/notes/notes.tex new file mode 100644 index 0000000..d74a5a8 --- /dev/null +++ b/tests/unlabeled-core/notes/notes.tex @@ -0,0 +1,7 @@ +\documentclass{article} +\usepackage{amsmath} +\begin{document} +% no label + +hello +\end{document}