feat: land superpaper v1 notes scaffold

Add the ledger schema, class router, lint codes, ingest/build
pipeline, and three work-tree examples: align derivation,
superfig delegation, and supertensor delegation.
This commit is contained in:
dela
2026-08-17 10:01:30 +08:00
parent c2bec6f55a
commit 3a322aa8dc
89 changed files with 3522 additions and 11 deletions
+8
View File
@@ -1,8 +1,16 @@
work/ work/
.venv/
__pycache__/
*.pyc
*.aux *.aux
*.log *.log
*.out *.out
*.fls *.fls
*.fdb_latexmk *.fdb_latexmk
*.synctex.gz *.synctex.gz
*.toc
.DS_Store .DS_Store
claims.md
**/notes/notes.pdf
**/notes/sections/symbols.tex
**/out/
+10 -3
View File
@@ -45,6 +45,13 @@ skills/supertensor -> superpaper/supertensor
## Status ## Status
The family contract lives in [`DESIGN.md`](DESIGN.md). Figure children v1 notes scaffold is in this repo: ledger schema, router, lint, ingest,
are usable now. The notes scaffold (`scripts/`, ledger, template) is and three `--work` examples (`excerpt-toy`, `pipeline-delegate`,
the SuperPaper v1 PR chain in that document — not in this commit. `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
```
+11 -8
View File
@@ -17,8 +17,8 @@ description: >-
`ledger.yaml` + 结构化中文笔记。图一律委派给子仓库,不要在笔记里 `ledger.yaml` + 结构化中文笔记。图一律委派给子仓库,不要在笔记里
`\usepackage{superfig}` / `supertensor`。 `\usepackage{superfig}` / `supertensor`。
本目录是家族母仓库。子仓库在 `superfig/` 与 `supertensor/`(git submodule)。 本目录是家族母仓库。子仓库在 `superfig/` 与 `supertensor/`。
完整合同见 `DESIGN.md`。笔记脚手架(`scripts/`、模板、schema)按 DESIGN 的 PR 1 落地;在此之前按下面路由手工委派。 契约细节按需加载 `references/`;schema 在 `assets/ledger.schema.yaml`。
## When to use ## When to use
@@ -43,12 +43,15 @@ video.
## Workflow ## Workflow
1. 读 `DESIGN.md` 的输入契约与 ledger schema。 1. `./scripts/preflight.sh`。0 = 全路径,1 = 降级(说出口),2 = 无 LaTeX。
2. 先写 `ledger.yaml`(claims / symbols / derivations / figure plan),再写笔记。 2. `./scripts/ingest.sh --work <dir> …`(`references/input.md`)。
3. 公式三拍:中文动机 → `\[` / `align` → 扁平符号表。 3. 从 `assets/ledger.example.yaml` 写 `ledger.yaml`,再写 `outline.md`,再写正文。
4. 每张计划图写一份自包含 `F*.request.md`,开一个 figure agent,**只**把对应子仓库的 `SKILL.md` 给它。 4. 公式三拍:中文动机 → `\[` / `align` → 扁平符号表(`references/pedagogy.md`)。
5. 笔记用 PDF `\includegraphics` 嵌入子仓库产物。禁止 `\input` standalone 源。 5. 按 `references/router.md` 填 figure plan。每张重绘图一份 `F*.request.md`,figure agent 只读它和对应子仓库 `SKILL.md`。
6. 改术语或改图:先改 ledger,再改那一处。 6. `./scripts/lint.py --work <dir>` 然后 `./scripts/build.sh --work <dir>`。
7. 改术语或改图:先改 ledger,再改那一处。
Worked trees: `examples/excerpt-toy/`(`align` 推导)、`examples/pipeline-delegate/`、`examples/tensor-delegate/`。
## Spawn recipe ## Spawn recipe
+24
View File
@@ -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: []
+210
View File
@@ -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}
+54
View File
@@ -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}{}
+43
View File
@@ -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}
+46
View File
@@ -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: []
+29
View File
@@ -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}
@@ -0,0 +1,3 @@
\section{这篇论文在问什么}
要把输入变成预测,最简单的机制是什么?损失要不要走在前向主路上?
这是摘录 fixture,不假装读完全文。
@@ -0,0 +1,3 @@
\section{主张与贡献}
\splabel{C1}
一次前向是 $x \to f_\theta \to \hat y$。损失 $L(\hat y,y)$ 在预测之后单独计算,不是主路上的一站。
@@ -0,0 +1,2 @@
\section{预备:定义、假设、符号}
预测器定义为 $\hat y = f_\theta(x)$。符号见附录。
@@ -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{本章小结}
前向只负责预测;缩放是改写,不是新算子。
@@ -0,0 +1,2 @@
\section{总结与延伸}
摘录只保留一条机制:一次前向加侧路损失。更长的论文用 ledger 把 claim 钉住,再按路由出图。
+17
View File
@@ -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 前)必须是「总结与延伸」
+8
View File
@@ -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$.
+47
View File
@@ -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: []
@@ -0,0 +1 @@
一次前向 $x\to f_\theta\to\hat y$。损失挂在预测下方,不是主路车站。
@@ -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)$"}
@@ -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}
Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 313 KiB

@@ -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}
@@ -0,0 +1,2 @@
\section{这篇论文在问什么}
预测怎么从输入算出来,损失该不该站在前向主路上?
@@ -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{本章小结}
损失不是前向的一站。
@@ -0,0 +1,2 @@
\section{总结与延伸}
这张图走 superfig,因为要画的是谁吃谁,不是轴长。
+14
View File
@@ -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 前)必须是「总结与延伸」
+47
View File
@@ -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: []
@@ -0,0 +1 @@
$S^{(i)}=Q^{(i)}K^{(i)\top}$。收缩维 $d_h$ 在两个操作数上同一边长;$K^\top$ 换面。
@@ -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}
@@ -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}
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 213 KiB

+14
View File
@@ -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}
@@ -0,0 +1,2 @@
\section{这篇论文在问什么}
每头的 $Q$ 和 $K$ 沿哪一条边收缩,$K^\top$ 要不要真的换面?
@@ -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。
@@ -0,0 +1,2 @@
\section{总结与延伸}
形状对齐的公式图只交给 supertensor。
+14
View File
@@ -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 前)必须是「总结与延伸」
+14
View File
@@ -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 收口。
```
+8
View File
@@ -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.
+14
View File
@@ -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`.
+9
View File
@@ -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.
+9
View File
@@ -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.
+11
View File
@@ -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 <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`.
+12
View File
@@ -0,0 +1,12 @@
# Input
`scripts/ingest.sh --work <dir> (--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.
+9
View File
@@ -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.
+15
View File
@@ -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 符号表 / 推导链一览 / 图表清单.
+14
View File
@@ -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.
+2
View File
@@ -0,0 +1,2 @@
jsonschema>=4.18
PyYAML>=6.0
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Lint, project the ledger, compile notes. Overfull is logged, not fatal.
# ./scripts/build.sh --work <dir>
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
+108
View File
@@ -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" <<EOF
kind: $KIND
$extra
EOF
}
case "$KIND" in
excerpt)
cp -- "$SRC" "$WORK/source/excerpt.md"
write_meta "excerpt: source/excerpt.md"
;;
tex)
mkdir -p "$WORK/source/tex"
if [[ -d "$SRC" ]]; then
cp -R -- "$SRC/." "$WORK/source/tex/"
else
cp -- "$SRC" "$WORK/source/tex/"
fi
write_meta "tex: source/tex"
;;
pdf)
cp -- "$SRC" "$WORK/source/paper.pdf"
pdftotext -layout "$WORK/source/paper.pdf" "$WORK/source/paper.txt" || true
pdftoppm -png -r 120 "$WORK/source/paper.pdf" "$WORK/source/pages/pg"
python3 - <<'PY' "$WORK/source/pages"
from pathlib import Path
import sys, re
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
pages=$(find "$WORK/source/pages" -name 'pg-*.png' | wc -l)
write_meta "local_pdf: source/paper.pdf
sha256: $(sha_of "$WORK/source/paper.pdf")
pages: $pages"
;;
arxiv)
id="$SRC"
[[ "$id" =~ $ARXIV_RE ]] || { echo "bad arxiv id: $id" >&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"
+243
View File
@@ -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())
+57
View File
@@ -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
Executable
+7
View File
@@ -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 "$@"
+67
View File
@@ -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 <dir>", 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())
+100
View File
@@ -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
+61
View File
@@ -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
+91
View File
@@ -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"
@@ -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: []
+28
View File
@@ -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: []
+28
View File
@@ -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: []
+21
View File
@@ -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: []
@@ -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: []
@@ -0,0 +1,6 @@
schema: superpaper.ledger/v1
paper:
title: Missing id
source: {kind: excerpt}
coverage:
mode: excerpt
+28
View File
@@ -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: []
@@ -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: []
+20
View File
@@ -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: []
@@ -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: []
+28
View File
@@ -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: []
@@ -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: []
+20
View File
@@ -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: []
+20
View File
@@ -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: []
+7
View File
@@ -0,0 +1,7 @@
\documentclass{article}
\usepackage{amsmath}
\begin{document}
\usepackage{superfig}
hello
\end{document}
+30
View File
@@ -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: []
@@ -0,0 +1 @@
toolkit: superfig
+5
View File
@@ -0,0 +1,5 @@
\documentclass{article}
\begin{document}
\splabel{C1}
ok
\end{document}
+29
View File
@@ -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: []
+5
View File
@@ -0,0 +1,5 @@
\documentclass{article}
\begin{document}
\splabel{C1}
ok
\end{document}
+21
View File
@@ -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: []
+14
View File
@@ -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}
+41
View File
@@ -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")
+20
View File
@@ -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: []
+7
View File
@@ -0,0 +1,7 @@
\documentclass{article}
\usepackage{amsmath}
\begin{document}
% no label
hello
\end{document}