
Section Writing Agent
- 44 installs
- 628 repo stars
- Updated July 9, 2026
- ar9av/paperorchestra
section-writing-agent is a Claude skill that drafts a paper's remaining sections in one multimodal call and merges tables and figures into the template.
About
Step 4 of the PaperOrchestra pipeline: a single multimodal LLM call that drafts the remaining paper sections (Abstract, Methodology, Experiments, Conclusion), extracts numeric values from the experimental log into LaTeX booktabs tables, splices in the generated figures, and merges everything into the template that already holds the Introduction and Related Work. A developer runs it to fill in the body of a paper.
- Step 4 of PaperOrchestra: one multimodal call drafts sections
- Extracts experiment numbers into LaTeX booktabs tables
- Splices Step 2 figures and merges into the template
Section Writing Agent by the numbers
- 44 all-time installs (skills.sh)
- Ranked #7,851 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
section-writing-agent capabilities & compatibility
- Capabilities
- documentation · research · data analysis
- Use cases
- documentation · research · data analysis
What section-writing-agent says it does
ONE single multimodal LLM call that drafts the remaining paper sections (Abstract, Methodology, Experiments, Conclusion), extracts numeric values from experimental_log.md into LaTeX booktabs tables
Do NOT split this into per-section calls — the paper explicitly designs it as one comprehensive call so the model can maintain global coherence across sections.
npx skills add https://github.com/ar9av/paperorchestra --skill section-writing-agentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 44 |
|---|---|
| repo stars | ★ 628 |
| Last updated | July 9, 2026 |
| Repository | ar9av/paperorchestra ↗ |
What it does
Draft a paper's methodology, experiments, and conclusion in one multimodal call and merge tables and figures.
Who is it for?
Filling in the body sections of a paper with grounded tables and figure references
Skip if: Discovering citations or generating the figures themselves
When should I use this skill?
The orchestrator delegates Step 4, or the user asks to write the methodology and experiments sections or fill in the rest of the paper
What you get
Produces workspace/drafts/paper.tex, the complete LaTeX paper with all sections filled
- workspace/drafts/paper.tex
By the numbers
- 1 multimodal LLM call per run
- drafts 4 sections (Abstract, Methodology, Experiments, Conclusion)
- 3 deterministic post-write gates (orphan-cite, latex-sanity, anti-leakage)
Files
Section Writing Agent (Step 4)
Faithful implementation of the Section Writing Agent from PaperOrchestra (Song et al., 2026, arXiv:2604.05018, §4 Step 4, App. F.1 pp. 47–49).
Cost: ONE LLM call (App. B: "Section Writing Agent (1 call): A single, comprehensive multimodal call to draft and compile the complete LaTeX manuscript"). Do NOT split this into per-section calls — the paper explicitly designs it as one comprehensive call so the model can maintain global coherence across sections.
Inputs
workspace/outline.json— the master planworkspace/inputs/idea.md— technical detailsworkspace/inputs/experimental_log.md— raw data for tables and qualitative analysisworkspace/drafts/intro_relwork.tex— the template **with Intro + Related
Work already filled in by Step 3**. This is your starting point. The preamble, package list, style, and the two pre-filled sections must be preserved verbatim.
workspace/citation_pool.json— the citation map ({key, title, abstract}
for each verified paper)
workspace/refs.bib— the BibTeX fileworkspace/inputs/conference_guidelines.md— formatting rulesworkspace/figures/— the actual PNG files from Step 2 (used as
multimodal vision input!)
workspace/figures/captions.json— caption text per figure_idworkspace/tex_profile.json— TeX package availability flags (written by
check_tex_packages.py at Step 0). Read this before generating any LaTeX. It tells you which packages are installed so you select the right cross-reference pattern, font packages, etc. before you write — not after you try to compile.
Output
workspace/drafts/paper.tex— the complete LaTeX paper, with all sections
filled. The Step 5 Refinement Agent will iterate on this file.
How to do it
0.5. Read tex_profile.json and select LaTeX patterns
Before composing the prompt, read workspace/tex_profile.json and apply these rules to every LaTeX choice in the generated paper:
| Profile flag | True → use | False → use instead |
|---|---|---|
use_cleveref | \cref{fig:X}, \cref{tab:Y} | Figure~\ref{fig:X}, Table~\ref{tab:Y} |
use_nicefrac | \nicefrac{a}{b} | $a/b$ |
use_microtype | \usepackage{microtype} | omit the line |
use_t1_fontenc | \usepackage[T1]{fontenc} | omit the line |
If tex_profile.json does not exist (old workspace), default to the safe fallback column (no cleveref, no nicefrac, no microtype, no T1 fontenc).
1. Pre-extract metrics from the experimental log
Run the deterministic helper:
python skills/section-writing-agent/scripts/extract_metrics.py \
--log workspace/inputs/experimental_log.md \
--out workspace/metrics.jsonThis parses the ## 2. Raw Numeric Data section's markdown tables into structured JSON. The Section Writing Agent uses this to construct LaTeX booktabs tables without re-deriving values from raw text. Read references/latex-table-patterns.md for the booktabs conventions.
2. Compose the prompt and make ONE multimodal call
Load references/prompt.md (verbatim Section Writing Agent prompt from App. F.1). Prepend the Anti-Leakage Prompt from ../paper-orchestra/references/anti-leakage-prompt.md.
The user message contains:
outline.json— full contentidea.md— full contentexperimental_log.md— full content (tables AND prose)intro_relwork.tex— full content (this becomestemplate.texfor the prompt)citation_pool.json— full content (becomescitation_map.json)conference_guidelines.md— full contentfigures_list— array of{figure_id, filename, caption}from
captions.json and the file listing
- The actual figure PNGs as multimodal image inputs, so the model can
visually inspect them and write accurate descriptions / refer to them correctly in the prose.
If your host LLM has no vision input, fall back to text-only mode: pass the captions in captions.json as descriptions and tell the agent it cannot see the images directly. Quality drops noticeably (the paper notes that visual grounding measurably improves figure-text alignment), but the pipeline still completes.
3. Save the output
The agent's response is wrapped in \\\latex ... \\\` fences. Extract the LaTeX code and save to workspace/drafts/paper.tex`.
4. Run the deterministic gates
# Orphan citation gate: every \cite{KEY} must exist in refs.bib
python skills/section-writing-agent/scripts/orphan_cite_gate.py \
workspace/drafts/paper.tex workspace/refs.bib
# Latex sanity: matched braces, matched begin/end, no unescaped specials
python skills/section-writing-agent/scripts/latex_sanity.py \
workspace/drafts/paper.tex
# Anti-leakage post-check: no author names, emails, affiliations
python skills/paper-orchestra/scripts/anti_leakage_check.py \
workspace/drafts/paper.texIf any gate fails, re-prompt the writing call with the gate's error report appended to the user message and ask the agent to fix the specific issues. Do NOT try to fix the gate violations by hand — the model needs to see its own mistakes.
Critical rules from the prompt
These are excerpted from references/prompt.md (App. F.1, pp. 47-49). The host agent MUST honor them on the writing call:
Existing-content preservation
- DO NOT modify the text, style, or content of sections that are already
filled in intro_relwork.tex. Preserve Intro + Related Work verbatim.
- Keep the preamble (packages, document class, style) exactly as is.
- Come up with a good title if one is missing. Fill author names if missing
(but the Anti-Leakage Prompt says not to invent real ones — use a placeholder like "Anonymous Authors" for double-blind).
Data and tables
- Build LaTeX tables for the experimental results.
- Extract numeric values directly from
experimental_log.md. **Do not
hallucinate numbers** — use the exact values in the log.
- Use the
booktabspackage format:\toprule,\midrule,\bottomrule. - All tables must appear before the Conclusion section, unless they are
explicitly placed in an Appendix.
Citations
- The
outline.jsonprovides citation_hints per subsection. For each hint,
find the matching key in citation_pool.json (by title or content) and use that exact key in \cite{...}.
- Use ONLY keys from `refs.bib`. Inventing or guessing keys violates the
Lit Review Agent's verified pool.
- Read the abstract from
citation_pool.jsonfor the papers you cite.
Use the abstract context to write specific, accurate sentences about those works — not generic "[A, B] proposed methods for X".
Writing content
- Write the missing sections following
outline.json'ssection_plan
structure exactly. Hierarchy rule: if 4.1 exists, 4.2 must exist.
- Use formal mathematical equations, notations, and definitions where
appropriate AND directly supported by idea.md or experimental_log.md. Do not hallucinate math. Do not use complex math just for the sake of it.
- Always provide detailed ablation studies and qualitative analysis of the
experimental results: what worked, what does not, and why.
- Optional: discuss limitations and future work at the end.
- If you put anything in the Appendix, the Appendix section appears AFTER
the References section, on a fresh new page.
Figures and visual fidelity
- You are being given the actual image files of the figures. You MUST
describe them faithfully and accurately. Do NOT hallucinate interpretations that contradict the visual evidence in the plots.
- Use ALL of the figures provided in
figures/. Use the exact filenames
including extensions (e.g., .png) in your \includegraphics commands.
- DO NOT merge or group multiple figures into one display.
- If the paper is in a 2-column format, prefer single-column figures
(\begin{figure}) unless they are very wide.
- All figures must appear before the Conclusion section, unless explicitly
in the Appendix.
- Refine the captions if necessary, but they are already provided in
captions.json and should generally be used as-is.
- Do NOT include "Figure X" in the caption text — LaTeX handles numbering.
Style
- Adopt the tone of a top-tier ML conference paper: dense, objective,
technical.
- Match the indentation and spacing style of the original
template.tex.
Do not change the overall LaTeX style.
LaTeX integrity
- The output must compile flawlessly out-of-the-box.
- All
\begin{X}must match a\end{X}(e.g.,\begin{figure*}must be
closed with \end{figure*}, not \end{figure}).
- DO NOT change
\usepackage[capitalize]{cleveref}to
\usepackage[capitalize]{cleverref} — there is no cleverref.sty.
- Always emit `\clearpage` immediately before `\bibliographystyle{...}`.
Without it, figures deferred by LaTeX's float algorithm will appear inside or after the References section — a hard-to-spot layout defect that only shows up in the compiled PDF. \clearpage forces all pending floats to be output before the bibliography starts. See references/latex-table-patterns.md for details.
- Cross-references: prefer
Figure~\ref{fig:X}andTable~\ref{tab:Y}
over bare \ref{fig:X}. This is necessary when cleveref is unavailable and produces readable prose in all cases. Use \cref{...} only when cleveref.sty is confirmed present.
Output format
- Wrap the full updated
template.texin\\\latex ... \\\``. - The previously empty sections should now be filled.
- Previously filled sections (Intro, Related Work) should remain mostly
untouched; only adjust for consistency purposes.
Resources
references/prompt.md— verbatim Section Writing Agent prompt from App. F.1references/latex-table-patterns.md— booktabs rules + table-from-log examplesreferences/figure-integration.md—\includegraphics, 2-column handling, placementscripts/extract_metrics.py— markdown tables in experimental_log → JSONscripts/latex_sanity.py— unmatched braces, env mismatches, specialsscripts/orphan_cite_gate.py— every\cite{KEY}exists in refs.bib
Figure Integration
Conventions for including figures in the LaTeX paper, per the Section Writing Agent prompt (App. F.1 p.48, item 5 "Figures and Visual Fidelity").
Where the figures live
After Step 2 (Plotting Agent), figures are at:
workspace/figures/
├── fig_framework_overview.png
├── fig_main_results.png
├── fig_ablation_temperature.png
└── captions.jsonThe Section Writing Agent must reference them with the exact filenames, including the .png extension:
\begin{figure}[t]
\centering
\includegraphics[width=0.95\linewidth]{figures/fig_framework_overview.png}
\caption{Overview of the proposed pipeline. Raw video frames flow into the
frozen SAM encoder; aligned audio cues are projected through the temporal
modality fusion layer before being injected into the mask decoder.}
\label{fig:framework_overview}
\end{figure}Single-column vs full-width
The prompt is explicit: in 2-column conference templates, prefer \begin{figure} (single-column) unless the figure is very wide. Use \begin{figure*} only for cross-column figures.
| Figure aspect ratio | Recommended environment |
|---|---|
1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4 | figure (single column) |
16:9, 21:9, 4:1 | figure* (cross column) |
9:16, 1:4 | figure (very tall, hangs over multiple text lines) |
Caption placement
For figures, \caption goes AFTER \includegraphics:
\begin{figure}[t]
\centering
\includegraphics[width=0.95\linewidth]{figures/fig_main_results.png}
\caption{Comparison of three temporal-attention variants on the Ref-AVS
Seen split. Bars show Jaccard index; error bars are 95% confidence
intervals over five seeds.}
\label{fig:main_results}
\end{figure}(For tables, \caption goes BEFORE \begin{tabular}. Different conventions; both correct in their respective contexts.)
Caption text
- Pull from
workspace/figures/captions.jsonkeyed byfigure_id. - Do not include
Figure N:in the caption text. LaTeX adds the prefix
via \caption.
- Plain text only. No markdown bold/italic.
- 1-3 sentences. State what the figure shows AND why (the takeaway).
Reference in prose
Every figure must be referenced in the prose:
As shown in Figure~\ref{fig:main_results}, our method outperforms both
baselines across all five splits.Use ~ (non-breaking space) before \ref{...}. Use Figure capitalized when starting a sentence; lowercase figure mid-sentence.
DO NOT merge figures
The prompt forbids combining multiple figures into one display. Each figure_id from the outline corresponds to exactly one \begin{figure} environment.
All figures before Conclusion
Per the prompt: "all figures must appear before the Conclusion section, unless they are placed in an Appendix." If you have a figure that contextually belongs in the Appendix, move it there explicitly — do not leave it floating after the Conclusion in the main body.
Multi-panel figures
If a figure has multiple panels (a, b, c) — e.g., a 1×3 grid showing ablations across three settings — the panels are part of the same PNG file (rendered by Step 2). Use a single \includegraphics and reference sub-panels in the caption text:
\caption{Ablation study. (a) Effect of temperature. (b) Effect of dropout.
(c) Effect of layer count. Bars show validation accuracy on the Seen split.}If you really need separate sub-figure environments (subcaption package), that's allowed but adds complexity — prefer single-PNG multi-panel from Step 2.
LaTeX Table Patterns
Conventions for building LaTeX tables from experimental_log.md raw numeric data, per the Section Writing Agent prompt requirements (App. F.1 p.47, item 2 "Data & Tables").
Required: booktabs
Always use the booktabs package. The preamble in a typical conference template already includes it; if not, add:
\usepackage{booktabs}Three rules only
Booktabs uses only three horizontal rules: \toprule, \midrule, \bottomrule. No \hline. No vertical bars.
\begin{table}[t]
\centering
\caption{Comparison of methods on Dataset X.}
\label{tab:main_results}
\begin{tabular}{lccc}
\toprule
Method & Accuracy & F1 & Latency (ms) \\
\midrule
Baseline & 78.2 & 0.79 & 12.3 \\
\textbf{Ours} & \textbf{85.4} & \textbf{0.87} & \textbf{8.1} \\
\bottomrule
\end{tabular}
\end{table}From experimental_log markdown table → LaTeX
experimental_log.md contains tables in plain markdown:
## 2. Raw Numeric Data
### Table 1: Performance comparison on Dataset X
| Method | Accuracy | F1 | Latency (ms) |
|----------|----------|------|--------------|
| Baseline | 78.2 | 0.79 | 12.3 |
| Ours-S | 82.1 | 0.83 | 9.4 |
| Ours-L | 85.4 | 0.87 | 8.1 |The extract_metrics.py helper parses these into JSON:
{
"tables": [
{
"label": "Performance comparison on Dataset X",
"headers": ["Method", "Accuracy", "F1", "Latency (ms)"],
"rows": [
["Baseline", "78.2", "0.79", "12.3"],
["Ours-S", "82.1", "0.83", "9.4"],
["Ours-L", "85.4", "0.87", "8.1"]
]
}
]
}The Section Writing Agent then converts each entry to a table environment verbatim. Important rules from the prompt:
- Do not hallucinate numbers. Copy the exact values from
extract_metrics.py's output.
- Bold the best result in each column (the convention for top-tier ML
papers).
- Use `\multicolumn{N}{c}{...}` for grouped headers when the table has
metric families (e.g., "Seen (J%)", "Seen F", "Unseen (J%)", "Unseen F").
- Right-align numeric columns with
r, left-align text columns withl.
Use c only for narrow centered identifiers.
- Use `\textbf{...}` for bold, never
**...**(markdown).
Wide tables (2-column conference templates)
For tables that don't fit single-column width, use table* and tabular* or tabularx:
\begin{table*}[t]
\centering
\caption{Ablation across all 6 components on 4 splits.}
\label{tab:ablation}
\begin{tabular}{lcccccc}
\toprule
Variant & Seen J & Seen F & Unseen J & Unseen F & Mix J & Mix F \\
\midrule
Full & 43.43 & 0.568 & 54.58 & 0.664 & 49.01 & 0.616 \\
- TB & 33.05 & 0.507 & 50.48 & 0.657 & 41.77 & 0.582 \\
- TMFL & 40.35 & 0.579 & 45.54 & 0.627 & 42.95 & 0.603 \\
\bottomrule
\end{tabular}
\end{table*}The closing \end{table*} must match the opening \begin{table*}. The latex_sanity.py script catches mismatches.
Caption placement
\begin{table}[t]
\centering
\caption{Caption text here.} % BEFORE the tabular for tables
\label{tab:my_label}
\begin{tabular}{...}
...
\end{tabular}
\end{table}(For figures, \caption goes AFTER \includegraphics, not before. See figure-integration.md.)
Common pitfalls
| Issue | Fix |
|---|---|
\hline everywhere | Replace with \toprule (top), \midrule (between header and body), \bottomrule (bottom). |
| Column too wide, runs off page | Switch to table* + tabular*. |
| Vertical bars | Remove. Booktabs forbids vertical rules. |
| Misaligned decimals | Use S[table-format=2.2] from siunitx if available, else right-align with r. |
| Table after Conclusion | Move it before. The prompt mandates this. |
| Hallucinated values | Cross-check against extract_metrics.py output. |
Figures floating into or after the References section
This is the most common final-layout bug. When many figures appear in the Experiments section and the bibliography is near the end, LaTeX cannot place all floats in the text body and defers them past \bibliography.
Fix: always emit \clearpage immediately before \bibliographystyle{...}:
% Flush all pending floats before the bibliography
\clearpage
\bibliographystyle{plainnat}
\bibliography{refs}The \clearpage forces LaTeX to output every deferred float on their own pages before starting the reference list. Without it, figures that could not fit in the Experiments section will appear between the References heading and the reference entries, or after the last reference.
Cross-referencing without cleveref
When the conference template uses \usepackage[capitalize]{cleveref}, the Section Writing Agent should produce \cref{fig:X} and \cref{tab:Y}. However, if cleveref is stripped (e.g., due to a minimal TeX installation), bare \ref{} produces only the number with no "Figure" or "Table" prefix, which reads as isolated numbers in the prose.
Pattern to use when `cleveref` is absent:
Figure~\ref{fig:overview} % tilde prevents line break before number
Table~\ref{tab:main-results}Never write just \ref{fig:overview} without a prefix; readers will see "...see 3."
The host agent must check whether cleveref.sty is available in the TeX installation before choosing \cref{} vs Figure~\ref{}. A safe default is to always use the Figure~\ref{} form; it degrades gracefully and works everywhere.
Section Writing Agent — verbatim prompt
Source: arXiv:2604.05018, Appendix F.1, pages 47–49 (verbatim).
Use this as your system message for the single multimodal LLM call that drafts the remaining sections of the paper. The Anti-Leakage Prompt (../paper-orchestra/references/anti-leakage-prompt.md) MUST be prepended.
---
Role: Senior AI Researcher.
Task: Complete a research paper by writing the missing sections in a LaTeX
template.
You will be given a template.tex file where some sections (e.g.,
Introduction, Related Work) are already written, and others are empty or
missing. Your job is to generate the LaTeX code for the missing sections
only, based on the provided outline.json, and merge them into the final
document.
Inputs
- outline.json: Your MASTER PLAN. Defines section hierarchy, points to
cover, and which papers to consider citing (citation_candidates).
- idea.md: Technical details of the methodology.
- experimental_log.md: Raw data for tables and qualitative analysis for
text.
- citation_map.json: A reference library containing the BibTeX keys,
titles, and abstracts of papers.
- conference_guidelines.md: Formatting rules.
- figures_list: Available figure files.
Critical Instructions
1. Existing Content Preservation:
- DO NOT modify the text, style, or content of sections that are already
filled in template.tex.
- Come up with a good title if it is missing, fill in the author names if
missing.
- Keep the preamble (packages) exactly as is.
2. Data & Tables:
- You are responsible for creating LaTeX tables.
- Extract numerical data directly from experimental_log.md.
- Use the booktabs package format (\toprule, \midrule, \bottomrule).
- Do not hallucinate numbers. Use the exact values provided in the log.
- Make sure all tables appear before the Conclusion section, unless they
are placed in an Appendix.
3. Citations:
- The outline.json provides a list of citation_candidates for specific
subsections.
- You MUST use the exact keys found in citation_map.json (e.g.,
\cite{Hu2021LoraLowrank}).
- Content Enrichment: Read the abstract provided in citation_map.json
for the papers you are citing. Use this context to write accurate,
specific sentences about those works.
4. Writing Content:
- Write the missing sections following the outline.json structure.
- Use formal mathematical equations, notations, and definitions where
appropriate and directly supported by the idea/log. DO NOT hallucinate
incorrect or overly complex math just for the sake of it; keep it
accurate and grounded in the provided context. Avoid overly colloquial
summaries.
- Always provide detailed ablation studies and qualitative analysis of
the experimental results: what worked, what does not, and why.
- Nice to have: discuss the limitations and future work at the end.
- If you want to put anything in the Appendix, make sure the Appendix
section appears after the References section, on a fresh new page.
5. Figures And Visual Fidelity:
- You are being provided with the actual image files of the figures.
You MUST describe them faithfully and accurately. DO NOT hallucinate
interpretations that contradict the visual evidence in the plots.
- Make sure to use ALL of the figures provided in figures_list. Note:
figures are stored in the figures/ subdirectory. IMPORTANT: use the
exact filenames including their extensions (e.g., .png) in your
\includegraphics commands.
- DO NOT merge or group multiple figures into one for display.
- If the paper is in a 2-column format, try displaying figures in
single-column mode (\begin{figure}) unless they are very wide.
- Ensure that all figures are correctly referenced in the text.
- Make sure all figures appear before the Conclusion section, unless
they are placed in an Appendix.
- You can refine the captions if necessary.
- Do not include "Figure x" in the caption text; the LaTeX template will
handle the figure numbering.
6. Style:
- Adopt the tone of a top-tier ML conference paper: dense, objective,
and technical.
- Ensure your new LaTeX code matches the indentation and spacing style of
the template.tex. Do not change the given style.
Output Format
- Return the full code for the completed template.tex.
- The sections that were previously empty should now be filled.
- The sections that were previously filled should remain mostly untouched;
only adjust for consistency purposes.
- Wrap the code with ```latex content ```.
Important Note
DO NOT change \usepackage[capitalize]{{cleveref}} into
\usepackage[capitalize]{{cleverref}}, as there is no cleverref.sty.
Ensure the LaTeX code compiles without errors, e.g., all the begin and end
statements match correctly (e.g., \begin{{figure*}} must be closed with
\end{{figure*}}, not \end{{figure}}).---
Multimodal call — image inputs
This call should pass the actual figure PNGs as image content blocks alongside the text inputs above. The model uses them to (a) verify it isn't describing a chart that doesn't exist, (b) write factually-grounded captions, (c) accurately interpret what each plot shows in the prose. If your host LLM lacks vision, document the degradation in your run report and proceed text-only.
#!/usr/bin/env python3
"""
extract_metrics.py — Parse markdown tables out of experimental_log.md's
"## 2. Raw Numeric Data" section into structured JSON.
The Section Writing Agent uses this to construct LaTeX booktabs tables
without re-deriving numeric values from raw markdown text. Per the App. F.1
prompt, "do not hallucinate numbers; use the exact values provided in the
log" — this script makes that mechanical.
Output JSON shape:
{
"tables": [
{
"label": "Performance comparison on Dataset X",
"headers": ["Method", "Accuracy", "F1", "Latency (ms)"],
"rows": [
["Baseline", "78.2", "0.79", "12.3"],
...
]
},
...
]
}
Usage:
python extract_metrics.py --log experimental_log.md --out metrics.json
"""
import argparse
import json
import re
import sys
def find_raw_data_section(text: str) -> str:
"""Return the slice of text from '## 2. Raw Numeric Data' to the next H2."""
m = re.search(r"^##\s+2\.?\s*Raw Numeric Data\s*$", text, re.M)
if not m:
return ""
start = m.end()
next_h2 = re.search(r"^##\s+", text[start:], re.M)
end = start + next_h2.start() if next_h2 else len(text)
return text[start:end]
def parse_markdown_tables(section: str) -> list[dict]:
"""Walk the section, extracting markdown tables and their preceding labels."""
lines = section.split("\n")
tables: list[dict] = []
current_label: str | None = None
i = 0
while i < len(lines):
line = lines[i].strip()
# Track table labels: ### Table N: Foo / ### Table: Foo / **Table 1: Foo**
m = re.match(r"^#+\s*Table[^:]*:\s*(.+?)\s*$", line)
if m:
current_label = m.group(1).strip()
i += 1
continue
m = re.match(r"^\*\*Table[^:]*:\s*(.+?)\*\*\s*$", line)
if m:
current_label = m.group(1).strip()
i += 1
continue
# Detect table start: a header row followed by a separator row.
if "|" in line and i + 1 < len(lines):
sep = lines[i + 1].strip()
if re.fullmatch(r"\|?\s*[:\-]+\s*(\|\s*[:\-]+\s*)+\|?", sep):
headers = [c.strip() for c in line.strip("|").split("|")]
rows: list[list[str]] = []
j = i + 2
while j < len(lines) and "|" in lines[j].strip():
cells = [c.strip() for c in lines[j].strip().strip("|").split("|")]
if len(cells) >= 2:
rows.append(cells)
j += 1
tables.append({
"label": current_label or f"Table {len(tables) + 1}",
"headers": headers,
"rows": rows,
})
current_label = None
i = j
continue
i += 1
return tables
def main() -> int:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--log", required=True, help="experimental_log.md path")
p.add_argument("--out", required=True, help="metrics.json output path")
args = p.parse_args()
text = open(args.log).read()
section = find_raw_data_section(text)
if not section:
print("WARN: no '## 2. Raw Numeric Data' section found", file=sys.stderr)
with open(args.out, "w") as f:
json.dump({"tables": []}, f, indent=2)
return 0
tables = parse_markdown_tables(section)
out = {"tables": tables}
with open(args.out, "w") as f:
json.dump(out, f, indent=2, ensure_ascii=False)
print(f"OK: extracted {len(tables)} table(s) → {args.out}")
for t in tables:
print(f" - {t['label']}: {len(t['headers'])} cols × {len(t['rows'])} rows")
return 0
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python3
"""
latex_sanity.py — Deterministic structural checks on a generated LaTeX file.
Catches the most common ways the Section Writing Agent's output can fail to
compile, before invoking latexmk:
1. Unmatched braces (counts \\{ and \\} but ignores escaped ones)
2. Mismatched \\begin{X} / \\end{X} environments
3. Unescaped special characters (& % _ outside math/verbatim contexts)
— heuristic only; common false positives in tabular cells
4. Duplicate \\label{...}
5. Missing \\documentclass
Exit codes:
0 no errors found
1 one or more errors
Usage:
python latex_sanity.py path/to/paper.tex
"""
import re
import sys
def check_braces(text: str) -> list[str]:
# Strip escaped braces and comments
stripped = re.sub(r"%[^\n]*", "", text)
stripped = stripped.replace("\\{", "").replace("\\}", "")
n_open = stripped.count("{")
n_close = stripped.count("}")
if n_open != n_close:
return [f"unmatched braces: {{ × {n_open}, }} × {n_close} (delta {n_open - n_close})"]
return []
def check_environments(text: str) -> list[str]:
starred = lambda s: s.replace("*", r"\*") # noqa: E731
starts = re.findall(r"\\begin\{([^}]+)\}", text)
ends = re.findall(r"\\end\{([^}]+)\}", text)
errors: list[str] = []
stack: list[str] = []
pos = 0
# Walk in order, push starts, pop on ends
for m in re.finditer(r"\\(begin|end)\{([^}]+)\}", text):
kind, env = m.group(1), m.group(2)
if kind == "begin":
stack.append(env)
else:
if not stack:
errors.append(f"\\end{{{env}}} with no matching \\begin")
continue
top = stack.pop()
if top != env:
errors.append(f"\\begin{{{top}}} closed by \\end{{{env}}}")
if stack:
errors.append(f"unclosed environments: {stack}")
return errors
def check_documentclass(text: str) -> list[str]:
if not re.search(r"\\documentclass", text):
return ["missing \\documentclass — not a complete LaTeX document"]
return []
def check_duplicate_labels(text: str) -> list[str]:
labels = re.findall(r"\\label\{([^}]+)\}", text)
seen: dict[str, int] = {}
for l in labels:
seen[l] = seen.get(l, 0) + 1
dupes = [l for l, n in seen.items() if n > 1]
if dupes:
return [f"duplicate labels: {dupes}"]
return []
def check_unescaped_specials(text: str) -> list[str]:
"""Heuristic: look for & % _ that appear OUTSIDE tabular/math/verbatim
environments. False positives are common; we only emit WARNINGS, not errors."""
warnings: list[str] = []
# Strip math, tabular, verbatim, comments
s = re.sub(r"\\begin\{tabular\*?\}.*?\\end\{tabular\*?\}", "", text, flags=re.S)
s = re.sub(r"\\begin\{(equation|align|array|matrix|verbatim|lstlisting)\*?\}.*?\\end\{\1\*?\}", "", s, flags=re.S)
s = re.sub(r"\$[^$]*\$", "", s)
s = re.sub(r"%[^\n]*", "", s)
s = re.sub(r"\\[%&_#$]", "", s) # remove already-escaped
bad = re.findall(r"[%&_]", s)
if bad:
warnings.append(f"WARN: {len(bad)} potentially unescaped %, &, or _ outside math/tabular")
return warnings
def main() -> int:
if len(sys.argv) != 2:
print(__doc__, file=sys.stderr)
return 2
path = sys.argv[1]
text = open(path).read()
errors: list[str] = []
errors += check_documentclass(text)
errors += check_braces(text)
errors += check_environments(text)
errors += check_duplicate_labels(text)
warnings = check_unescaped_specials(text)
for w in warnings:
print(w)
if errors:
print(f"\nFAIL: {len(errors)} latex sanity error(s) in {path}", file=sys.stderr)
for e in errors:
print(f" - {e}", file=sys.stderr)
return 1
print(f"OK: {path} passes structural sanity checks")
return 0
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python3
"""
orphan_cite_gate.py — Verify every \\cite{KEY} in a LaTeX file resolves to
an entry in refs.bib.
The Section Writing Agent prompt mandates "use ONLY the keys found in
citation_map.json". This script enforces it deterministically.
Exit codes:
0 every cite key resolves
1 one or more orphan cite keys
Usage:
python orphan_cite_gate.py paper.tex refs.bib
"""
import re
import sys
CITE_RE = re.compile(
r"\\(?:cite|citep|citet|citeauthor|citeyear|autocite|parencite|textcite)"
r"(?:\[[^\]]*\])?"
r"\{([^}]+)\}"
)
BIB_KEY_RE = re.compile(r"^@\w+\{\s*([^,\s]+)", re.M)
def main() -> int:
if len(sys.argv) != 3:
print(__doc__, file=sys.stderr)
return 2
tex_path, bib_path = sys.argv[1], sys.argv[2]
tex = open(tex_path).read()
bib = open(bib_path).read()
bib_keys = set(BIB_KEY_RE.findall(bib))
if not bib_keys:
print(f"ERROR: no @entry keys found in {bib_path}", file=sys.stderr)
return 1
cite_keys: set[str] = set()
for m in CITE_RE.finditer(tex):
for k in m.group(1).split(","):
k = k.strip()
if k:
cite_keys.add(k)
orphans = sorted(cite_keys - bib_keys)
unused = sorted(bib_keys - cite_keys)
print(f"refs.bib has {len(bib_keys)} entries; {tex_path} cites {len(cite_keys)} unique keys")
if orphans:
print(f"\nFAIL: {len(orphans)} orphan \\cite key(s) (not in refs.bib):", file=sys.stderr)
for k in orphans:
print(f" - {k}", file=sys.stderr)
return 1
if unused:
# Just informational. The literature-review-agent's citation_coverage.py
# is the gate that enforces ≥90% integration.
print(f"INFO: {len(unused)} bib entries not yet cited (informational)")
print("OK: no orphan cite keys")
return 0
if __name__ == "__main__":
sys.exit(main())