ts-paper-latex — assemble & compile the paper PDF (template-driven)
Turn the finished sections/<id>.tex + refs.bib + blueprint.json into a compiled
main.pdf. This stage never authors or alters content — no renaming methods/datasets, no
filling result numbers (proposal = blank cells stay blank). It is format + assembly + compile only.
Template-driven: assemble_paper.py reads template.json from the workdir and threads it
through everything — section order + headings, table/figure caption position, keyword cap,
whether to merge \cite (numeric templates only), and the preamble (the template's own
main.tex.tmpl, filled by safe @@token@@ substitution) + which .sty/.cls + assets to copy.
The default template is ts_iieta (two-column IIETA); neurips (single-column author-year) is also
bundled and compiles through the same script — nothing here is hardcoded to TS. If no template.json
is in the workdir it falls back to bundled ts_iieta (backward-compatible).
🔴 HARD RULE — NEVER fabricate a venue template. A template's style files (.sty/.cls) must come
from exactly ONE of: (a) a user-provided template (the user drops a templates/<name>/ dir or
points at the official files), or (b) the venue's OFFICIAL style files, fetched verbatim from
the official source (the conference/journal style-file URL, e.g. NeurIPS media.neurips.cc/.../neurips_<year>.sty)
and copied UNCHANGED. Do NOT hand-author, approximate, or "make it look like" a venue — a self-invented
.sty gets margins/fonts/notice/line-numbers wrong and is unusable for submission. If a requested venue has
neither a user-provided nor an obtainable official template, STOP and ask the user for the official files —
never fabricate one. ⚠️ The currently-bundled ts_iieta and neurips styles are unofficial
approximations ("official": false in their template.json); treat them as demo-only and replace with
the venue's official .sty/.cls (or a user-provided template) before any real submission.
What's bundled (clean, copyright-safe assets you own)
assets/ts_iieta.sty — our own two-column IIETA style (masthead with logo + blue band, 10pt Times, ALL-CAPS numbered sections, Figure N./Table N. captions, numeric [n] cites with sort&compress).
assets/iieta_logo.png — the masthead logo (compiles fine if absent via \IfFileExists).
scripts/assemble_paper.py — the deterministic backbone.
Run it
python scripts/assemble_paper.py <workdir>
The script: reads blueprint.json (title, keywords, abstract, section_order, optional authors/doi/dates/journal); for each sections/<id>.tex applies the post-processes and prepends the canonical ALL-CAPS heading; builds main.tex from the TS template; copies ts_iieta.sty + iieta_logo.png into the workdir; runs latexmk -pdf. It prints a JSON status {ok, compiled, exit, error_count, error_tail, sections, order, assets, template, workdir}.
Deterministic post-processes it applies (no LLM)
- Table captions ABOVE the tabular (IIETA convention) — both inline minipage and floating
table/table*.
- Merge adjacent
\cite runs into one (\cite{a} \cite{b} → \cite{a,b}) so they render as [1, 2] / [3-5] with the style's sort&compress.
- Canonical section headings — strips any heading the writer added and forces
Introduction / Related Works / Methodology / Experimental Results / Discussion and Analysis / Conclusion, which the style renders as 1. INTRODUCTION …
- Strip heading numbers (
\subsection{3.1 Foo} → Foo), table-width safety net (wraps a bare \begin{tabular} in \adjustbox), single-paragraph abstract merge.
- Keyword formatting — dedup, cap 6, comma-join into the
\tsSetKeywords macro (empty → the line is omitted).
- The bundled
ts_iieta.sty also loads inputenc[utf8] as a Unicode compile-net.
Compile + fix loop (quality gate — do not skip)
- The agent owns the ≤3-try fix loop; the script compiles once per call and only renders the verdict (no script-side loop). The script reports
compiled (PDF exists AND error_count == 0 — it already trusts the log, not the exit code) and a structured error_count. Success also requires main.bbl resolved (no [?] citations). Treat error_count > 0 as a red gate that blocks done.
- Run with
--backup on the FIRST attempt only so sections/ is snapshotted to sections.bak/ (rollback if a fix makes it worse) — do not re-pass --backup on later attempts or you overwrite the good baseline.
- If
compiled:false or error_count > 0, read error_tail, then edit the offending sections/*.tex with minimal, syntax-only fixes (close unbalanced $/environments, escape stray & % # _ in text, fix mispaired \begin/\end, 1-10→1--10). Never change content, math semantics, citations, labels, or -- placeholders.
- Re-run. Bounded at ~3 attempts; if
error_count goes UP, restore from sections.bak/ and report rather than thrash.
- One figure-specific error class: a
File \figures/' not foundor a bad-bounding-box error is **not** a content bug — fix it by **re-running the figure stage** (regenerate the missing.pdf/.png`), never by editing section prose.
- Finally write
logs/7_latex.io.md (INPUT: section files + refs.bib; DECISIONS: each compile error + the minimal fix; OUTPUT: main.pdf page count, error_count=0), and logs/index.md linking every stage's log.
Figures embed as vector PDF (no extra wiring)
Figures are referenced extension-less — \includegraphics{figures/<label>} — and the figure stage
ships figures/<label>.pdf (editable vector) beside the kept figures/<label>.png. Both bundled .sty
already \RequirePackage{graphicx}, so under pdflatex/latexmk the .pdf is embedded (its extension is
preferred over .png) with no change to assemble_paper.py (it never parses \includegraphics or
figure extensions). The vector is pre-rendered by cairosvg (in ts-figure-optimize's hybrid export), so there is no
\includesvg/Inkscape/--shell-escape dependency. The run_gates.py all vector check confirms every
figure has its .pdf sibling — a raster-only figure is a red gate.
Front matter / metadata
Authors/affiliation/email/DOI/dates default to clearly-marked placeholders ([AUTHORS TBD], 10.18280/ts.XXXXXX) for a proposal — that is correct; fill real values only if the user provides them in blueprint.json (authors, doi, dates, journal).
Done check
Report: page count, sections included, that captions are above tables, cites merged, keywords present, and that the citation linter (ts-paper-cite/scripts/citations_lint.py) passes with zero stubs/orphans.
1---2name: ts-paper-latex3description: Stage 7 (final assemble + compile) of the ts-paper suite. Assemble drafted LaTeX section bodies + refs.bib + blueprint into a compilable paper in the active TEMPLATE and compile it to PDF. Copies the template's .sty/.cls + assets, runs the deterministic template-driven post-processes (caption position, merge adjacent \cite for numeric styles, canonical headings from the spec, keyword formatting), and compiles once per call; the agent drives a bounded error-fix loop around it. Use to build/compile the final PDF.4---56# ts-paper-latex — assemble & compile the paper PDF (template-driven)78Turn the finished `sections/<id>.tex` + `refs.bib` + `blueprint.json` into a compiled9`main.pdf`. **This stage never authors or alters content** — no renaming methods/datasets, no10filling result numbers (proposal = blank cells stay blank). It is format + assembly + compile only.1112**Template-driven:** `assemble_paper.py` reads `template.json` from the workdir and threads it13through everything — section order + headings, table/figure **caption position**, keyword **cap**,14whether to **merge `\cite`** (numeric templates only), and the **preamble** (the template's own15`main.tex.tmpl`, filled by safe `@@token@@` substitution) + which **`.sty`/`.cls` + assets** to copy.16The default template is `ts_iieta` (two-column IIETA); `neurips` (single-column author-year) is also17bundled and compiles through the *same* script — nothing here is hardcoded to TS. If no `template.json`18is in the workdir it falls back to bundled `ts_iieta` (backward-compatible).1920> **🔴 HARD RULE — NEVER fabricate a venue template.** A template's style files (`.sty`/`.cls`) must come21> from exactly ONE of: **(a)** a **user-provided** template (the user drops a `templates/<name>/` dir or22> points at the official files), or **(b)** the venue's **OFFICIAL** style files, fetched **verbatim** from23> the official source (the conference/journal style-file URL, e.g. NeurIPS `media.neurips.cc/.../neurips_<year>.sty`)24> and copied UNCHANGED. **Do NOT hand-author, approximate, or "make it look like" a venue** — a self-invented25> `.sty` gets margins/fonts/notice/line-numbers wrong and is unusable for submission. If a requested venue has26> neither a user-provided nor an obtainable official template, **STOP and ask the user for the official files —27> never fabricate one.** ⚠️ The currently-bundled `ts_iieta` and `neurips` styles are **unofficial28> approximations** (`"official": false` in their `template.json`); treat them as **demo-only** and **replace with29> the venue's official `.sty`/`.cls` (or a user-provided template) before any real submission.**3031## What's bundled (clean, copyright-safe assets you own)32- `assets/ts_iieta.sty` — our own two-column IIETA style (masthead with logo + blue band, 10pt Times, ALL-CAPS numbered sections, `Figure N.`/`Table N.` captions, numeric `[n]` cites with `sort&compress`).33- `assets/iieta_logo.png` — the masthead logo (compiles fine if absent via `\IfFileExists`).34- `scripts/assemble_paper.py` — the deterministic backbone.3536## Run it37```bash38python scripts/assemble_paper.py <workdir>39```40The script: reads `blueprint.json` (title, keywords, abstract, `section_order`, optional authors/doi/dates/journal); for each `sections/<id>.tex` applies the post-processes and prepends the canonical ALL-CAPS heading; builds `main.tex` from the TS template; copies `ts_iieta.sty` + `iieta_logo.png` into the workdir; runs `latexmk -pdf`. It prints a JSON status `{ok, compiled, exit, error_count, error_tail, sections, order, assets, template, workdir}`.4142### Deterministic post-processes it applies (no LLM)43- **Table captions ABOVE** the tabular (IIETA convention) — both inline minipage and floating `table`/`table*`.44- **Merge adjacent `\cite`** runs into one (`\cite{a} \cite{b}` → `\cite{a,b}`) so they render as `[1, 2]` / `[3-5]` with the style's `sort&compress`.45- **Canonical section headings** — strips any heading the writer added and forces `Introduction / Related Works / Methodology / Experimental Results / Discussion and Analysis / Conclusion`, which the style renders as `1. INTRODUCTION` …46- **Strip heading numbers** (`\subsection{3.1 Foo}` → `Foo`), **table-width safety net** (wraps a bare `\begin{tabular}` in `\adjustbox`), **single-paragraph abstract** merge.47- **Keyword formatting** — dedup, cap 6, comma-join into the `\tsSetKeywords` macro (empty → the line is omitted).48- The bundled `ts_iieta.sty` also loads `inputenc[utf8]` as a Unicode compile-net.4950## Compile + fix loop (quality gate — do not skip)51- The **agent owns the ≤3-try fix loop**; the script compiles once per call and only **renders the verdict** (no script-side loop). The script reports `compiled` (PDF exists AND `error_count == 0` — it already **trusts the log, not the exit code**) and a structured `error_count`. Success also requires `main.bbl` resolved (no `[?]` citations). Treat `error_count > 0` as a **red gate that blocks done**.52- Run with `--backup` on the **FIRST attempt only** so `sections/` is snapshotted to `sections.bak/` (rollback if a fix makes it worse) — do not re-pass `--backup` on later attempts or you overwrite the good baseline.53- If `compiled:false` or `error_count > 0`, read `error_tail`, then **edit the offending `sections/*.tex` with minimal, syntax-only fixes** (close unbalanced `$`/environments, escape stray `& % # _` in text, fix mispaired `\begin/\end`, `1-10`→`1--10`). **Never** change content, math semantics, citations, labels, or `--` placeholders.54- Re-run. **Bounded at ~3 attempts; if `error_count` goes UP, restore from `sections.bak/` and report** rather than thrash.55- One figure-specific error class: a `File \`figures/<label>' not found` or a bad-bounding-box error is **not** a content bug — fix it by **re-running the figure stage** (regenerate the missing `.pdf`/`.png`), never by editing section prose.56- Finally write **`logs/7_latex.io.md`** (INPUT: section files + refs.bib; DECISIONS: each compile error + the minimal fix; OUTPUT: main.pdf page count, error_count=0), and `logs/index.md` linking every stage's log.5758## Figures embed as vector PDF (no extra wiring)59Figures are referenced **extension-less** — `\includegraphics{figures/<label>}` — and the figure stage60ships `figures/<label>.pdf` (editable vector) beside the kept `figures/<label>.png`. Both bundled `.sty`61already `\RequirePackage{graphicx}`, so under pdflatex/latexmk the `.pdf` is embedded (its extension is62preferred over `.png`) with **no change to `assemble_paper.py`** (it never parses `\includegraphics` or63figure extensions). The vector is pre-rendered by cairosvg (in `ts-figure-optimize`'s hybrid export), so there is **no64`\includesvg`/Inkscape/`--shell-escape` dependency**. The `run_gates.py all` vector check confirms every65figure has its `.pdf` sibling — a raster-only figure is a red gate.6667## Front matter / metadata68Authors/affiliation/email/DOI/dates default to clearly-marked placeholders (`[AUTHORS TBD]`, `10.18280/ts.XXXXXX`) for a proposal — that is correct; fill real values only if the user provides them in `blueprint.json` (`authors`, `doi`, `dates`, `journal`).6970## Done check71Report: page count, sections included, that captions are above tables, cites merged, keywords present, and that the citation linter (`ts-paper-cite/scripts/citations_lint.py`) passes with zero stubs/orphans.