@fffangchenghao/dsh-latex-look 0.0.0-stage → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CASE-STUDY.md +91 -0
- package/CHANGELOG.md +67 -0
- package/LICENSE +21 -0
- package/README.md +269 -2
- package/README.zh-CN.md +99 -0
- package/SKILL.md +231 -0
- package/bin/latex-look.mjs +170 -0
- package/cordis.patch.yml +3 -0
- package/docs/before-after.png +0 -0
- package/examples/broken-layout/README.md +48 -0
- package/examples/broken-layout/broken.tex +40 -0
- package/examples/broken-layout/fixed.tex +43 -0
- package/icon.svg +14 -0
- package/index.js +287 -0
- package/lib/core.mjs +1575 -0
- package/lib/update.mjs +116 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +68 -4
package/CASE-STUDY.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Case study: a 215-page LaTeX book, read the way a reader reads it
|
|
2
|
+
|
|
3
|
+
This is the document the tool was built against, and the only test that matters:
|
|
4
|
+
**《神经网络与深度学习》中文版** (Michael Nielsen's *Neural Networks and Deep
|
|
5
|
+
Learning*, Chinese translation) — 215 pages, 6 chapters, 3 appendices, 163 figures,
|
|
6
|
+
129 numbered equations, a 63-entry table of contents, a glossary built with
|
|
7
|
+
`glossaries`, and a great deal of mathematics.
|
|
8
|
+
|
|
9
|
+
The document compiled with **zero errors** long before the work in this file
|
|
10
|
+
started. Every defect below was invisible to the compiler and visible to a reader.
|
|
11
|
+
|
|
12
|
+
## Why the test was run this way
|
|
13
|
+
|
|
14
|
+
Two fixed roles, one file:
|
|
15
|
+
|
|
16
|
+
- A **reader** who can only see the PDF. It gets page numbers, measures pixels,
|
|
17
|
+
and writes findings as sentences: *"印 52 最上面是图 3-1,章名被压到页面四分之一
|
|
18
|
+
处"*. It never sees `.tex`, never accepts "it looks fine to me", and does not
|
|
19
|
+
accept a fix it cannot verify on a re-rendered page.
|
|
20
|
+
- An **editor** who may only change the source, and must prove each change on the
|
|
21
|
+
rendered page before reporting it.
|
|
22
|
+
|
|
23
|
+
That split is what makes the numbers below meaningful: they are measurements taken
|
|
24
|
+
by something that does not know what the source *intends*.
|
|
25
|
+
|
|
26
|
+
## What was actually wrong
|
|
27
|
+
|
|
28
|
+
| Symptom, as a reader said it | What it was | Pages |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| "the table of contents has lines sticking out past the margin" | `book.cls` sets `\@pnumwidth` to `1.55em`, too narrow for bold three-digit page numbers | all 63 TOC entries |
|
|
31
|
+
| "the equation numbers are wrong, they say `(5)` instead of `(1-1)`" | 129 equations carried a hard-coded `\tag{N}`, which ignores any `\theequation` redefinition | 129 equations, 6 chapters |
|
|
32
|
+
| "the chapter header says 第0章 on page viii" | the front matter sets `\chapter*`, but `\chaptermark` still ran | 1 page |
|
|
33
|
+
| "there is a gap at the bottom, the text stopped early" | figures did not fit, so they moved to the next page and took the space with them | 9 pages, up to 170 mm |
|
|
34
|
+
| "the figure numbers run backwards: 图 3-4 on p63, then 图 3-3 on p64" | converting an inline figure to a float makes LaTeX place it *after* the inline figures that follow it in the source | 3 runs |
|
|
35
|
+
| "page 52 (a chapter opening) starts with a picture, the chapter title is a quarter down the page" | a float was allowed at the top of a chapter's first page | 1 page |
|
|
36
|
+
|
|
37
|
+
## What the tool caught that a careful person missed
|
|
38
|
+
|
|
39
|
+
**The floating–numbering interaction.** Making a figure a float to kill a page hole
|
|
40
|
+
is obviously right, and it silently inverted figure numbers across a chapter,
|
|
41
|
+
because LaTeX orders floats of the same class by source position among *floats*,
|
|
42
|
+
not among inline figures. On a per-page diff, every page was fine. Only a
|
|
43
|
+
reading-order pass over the whole document showed it:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
图 3-4 on p63, then 图 3-3 on p64
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`sequence` finds this; the fix is to float the entire numbered run, not one member
|
|
50
|
+
of it.
|
|
51
|
+
|
|
52
|
+
**Page furniture poisoning a whole-book measurement.** The first version of
|
|
53
|
+
`audit` reported that all 215 pages ended 40 pt too early. The cause was that the
|
|
54
|
+
running head, the head rule, and the page number are the *deepest ink* on a page
|
|
55
|
+
in some pages and the *highest* in others; taking a percentile over the whole book
|
|
56
|
+
made page numbers the reference. The fix is to detect furniture first — a row that
|
|
57
|
+
recurs on a large share of pages within the outer 8 % band — and mask the whole
|
|
58
|
+
band, not the individual rows.
|
|
59
|
+
|
|
60
|
+
**A baseline that outlived its document.** Comparing against "the previous audit"
|
|
61
|
+
is useless the moment the document repaginates: every finding moves, and everything
|
|
62
|
+
reports as new. The audit matches findings by the **text at the break**, so a hole
|
|
63
|
+
is still recognisably the same hole after the page numbers shift.
|
|
64
|
+
|
|
65
|
+
## Final state
|
|
66
|
+
|
|
67
|
+
Verified on the rendered PDF, not on the source:
|
|
68
|
+
|
|
69
|
+
| Measurement | Result |
|
|
70
|
+
|---|---|
|
|
71
|
+
| Compile errors | 0 |
|
|
72
|
+
| Pages | 215 (front matter i–ix, body 1–205) |
|
|
73
|
+
| Overfull `\hbox` | 0 |
|
|
74
|
+
| Numbered figures | 163 — contiguous 1..N in every chapter |
|
|
75
|
+
| Figures out of reading order | 0 |
|
|
76
|
+
| Numbered equations | 129 — `(1-1)` … `(6-1)`, contiguous per chapter |
|
|
77
|
+
| Pages with >60 mm of unexplained white space | 19, all of them chapter ends or front matter |
|
|
78
|
+
| The nine named holes | all ≤ 56 mm |
|
|
79
|
+
| Chapter opening pages | all six start at exactly 17.0 % of page height |
|
|
80
|
+
| Running heads | 197 of 215 pages (the rest are pages where a head is wrong) |
|
|
81
|
+
|
|
82
|
+
## What this case proves — and what it does not
|
|
83
|
+
|
|
84
|
+
It proves that an agent can be held to a *visual* acceptance bar on a document it
|
|
85
|
+
cannot see as a whole, and that the failures above are the kind that survive
|
|
86
|
+
compile-and-forget workflows.
|
|
87
|
+
|
|
88
|
+
It does not prove the tool knows what your document should look like. Every fix
|
|
89
|
+
came from a sentence a person wrote. The tool's job is to make "look at it and
|
|
90
|
+
tell me whether it is right" cheap enough that a person can afford to say it ten
|
|
91
|
+
times in a row.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.3
|
|
4
|
+
|
|
5
|
+
- **Moved to a scope: `@fffangchenghao/dsh-latex-look`.** The flat name
|
|
6
|
+
`dsh-latex-look` is deprecated and kept only as a signpost. A scope makes the
|
|
7
|
+
owner visible at a glance and removes the squatting risk that an unscoped name
|
|
8
|
+
carries. Install with `target: @fffangchenghao/dsh-latex-look`.
|
|
9
|
+
- The update check follows the new name; the plugin id (`dsh-latex-look`) does
|
|
10
|
+
not, because profile config and enable state key off it.
|
|
11
|
+
|
|
12
|
+
## 0.3.2
|
|
13
|
+
|
|
14
|
+
- **`--point` now says what is drawn there.** Asking "what is at (300,118)" on a
|
|
15
|
+
rule used to return `\end{document}`, because a rule drawn by `fancyhdr` has no
|
|
16
|
+
line of its own and inverse search returns the nearest box. The tool measures the
|
|
17
|
+
ink instead: it reports whether the point holds a rule, text or nothing, and when
|
|
18
|
+
the ink is a rule it either names the command that draws it
|
|
19
|
+
(`drawn by line 15: \rule{\linewidth}{0.4pt}`) or says plainly that no line of
|
|
20
|
+
the document draws it and the reader should look at `\pagestyle`/`\headrulewidth`.
|
|
21
|
+
- Point lookups return the queried coordinates instead of `x=undefined y=undefined`,
|
|
22
|
+
and synctex's `/tmp/./main.tex` spellings are normalised.
|
|
23
|
+
|
|
24
|
+
## 0.3.0
|
|
25
|
+
|
|
26
|
+
Everything below came out of driving the tool against a real 215-page book
|
|
27
|
+
(《神经网络与深度学习》中文版) for a reader who could only see the PDF.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`audit`** — measure every page in one `gs` pass and report only what the
|
|
32
|
+
current build broke, compared with the baseline the previous audit left
|
|
33
|
+
behind. Running heads, rules and page numbers are detected and excluded first,
|
|
34
|
+
or a page number becomes "the lowest ink in the book" and every page looks
|
|
35
|
+
broken. Findings are matched by the text at the break, not by page number, so
|
|
36
|
+
the comparison survives a repagination. When a page stops short and the next
|
|
37
|
+
page opens with a caption, the finding names that caption.
|
|
38
|
+
- **`sequence`** — reading order of numbered labels (`图 1-1`, `Figure 3.2`).
|
|
39
|
+
Floating one member of a numbered run makes the numbers go backwards, and no
|
|
40
|
+
per-page diff can show it.
|
|
41
|
+
- **Update check** — the plugin asks the npm registry once a day, in the
|
|
42
|
+
background, and appends one line to a tool reply when a newer release exists.
|
|
43
|
+
`latex-look --version` and `latex-look --check-update` for scripts.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- `pageTexts` returns one file per page: `txtwrite` does not reliably separate
|
|
48
|
+
pages with form feeds, so a single-file read reported every label on page 1.
|
|
49
|
+
- Anonymous `@` names in a preamble rewrite (`\nndl@chapter`) break the compile;
|
|
50
|
+
the skill now says to use a plain name.
|
|
51
|
+
|
|
52
|
+
### Skill
|
|
53
|
+
|
|
54
|
+
- New playbook entries: a top float on a chapter opening page needs
|
|
55
|
+
`\suppressfloats[t]` in `\chapter`; a figure that becomes a float must be
|
|
56
|
+
floated together with its numbered run; floating moves a hole, it does not
|
|
57
|
+
remove it.
|
|
58
|
+
|
|
59
|
+
## 0.2.0
|
|
60
|
+
|
|
61
|
+
- `build` cache keyed on the document's source signature.
|
|
62
|
+
- `locate` falls back to the PDF text layer when SyncTeX names the page a float
|
|
63
|
+
was built on.
|
|
64
|
+
|
|
65
|
+
## 0.1.0
|
|
66
|
+
|
|
67
|
+
- First release: compile, render, locate, diff.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fangchenghao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,270 @@
|
|
|
1
|
-
#
|
|
1
|
+
# latex-look — teach an agent to *see* your LaTeX
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@fffangchenghao/dsh-latex-look)
|
|
4
|
+
[](test/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**English** · [简体中文](README.zh-CN.md)
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
*Left: compiles with zero errors. Right: after five sentences a reader could say out
|
|
12
|
+
loud — "remove the rules around the title", "this table is a grid, make it a
|
|
13
|
+
booktabs table", "that link runs off the page", "the figure is too wide". Try it
|
|
14
|
+
yourself in [`examples/broken-layout`](examples/broken-layout).*
|
|
15
|
+
|
|
16
|
+
A **DeepSeek Harness plugin** that adds exactly one tool: `latex_look`.
|
|
17
|
+
|
|
18
|
+
An agent that only reads `.tex` source is blind: a document can compile with zero errors and still look wrong. `latex_look` compiles the document, renders the page as an image, maps any spot on that page back to the exact source line, and diffs what your last edit changed — in a single call.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────┐ latex_look ┌──────────────┐
|
|
22
|
+
│ agent │ ─────────────► │ pdf → png │
|
|
23
|
+
│ (edits .tex)│ │ synctex │
|
|
24
|
+
└─────────────┘ ◄───────────── │ pixel diff │
|
|
25
|
+
▲ image + line └──────────────┘
|
|
26
|
+
└─────────────── verification ───────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Read the case study:** [a 215-page LaTeX book, read the way a reader reads it](CASE-STUDY.md) — 163 figures, 129 equations, six classes of defect that compile cleanly.
|
|
30
|
+
|
|
31
|
+
## Why one tool, not three
|
|
32
|
+
|
|
33
|
+
Rendering, locating, and diffing are the *implementation*. The agent needs one coherent verb — **"look at this page"** — the same way a person does. `latex_look` compiles, renders, resolves, and diffs in a single call, so the loop is:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
look → diagnose → edit .tex → look again → confirm the change → report
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
Requires Node ≥ 18, a TeX engine (`xelatex`/`lualatex`/`pdflatex`/`latexmk` or tectonic), `synctex`, and `ghostscript`.
|
|
42
|
+
|
|
43
|
+
This repository **is** the bundle: `package.json` declares `dsh.bundle.patch`, and `cordis.patch.yml` inserts one row.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/fangchenghao/latex-look
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then install it into the current profile (in the Harness, via the plugin manager):
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
plugin_manager action: install_bundle target: /absolute/path/to/latex-look
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That adds the row `latex-look` and registers the `latex_look` tool. Replacing the sources of an already-installed package needs a Harness restart to load a fresh module generation.
|
|
56
|
+
|
|
57
|
+
Published on npm as [`@fffangchenghao/dsh-latex-look`](https://www.npmjs.com/package/@fffangchenghao/dsh-latex-look), so the same row also installs straight from the registry:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
plugin_manager action: install_bundle target: @fffangchenghao/dsh-latex-look
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Updating
|
|
64
|
+
|
|
65
|
+
The plugin checks the registry **once a day, in the background**, and appends a single line to a tool reply when a newer release exists — no repeated nagging, and an offline machine sees nothing at all. To upgrade, install the same bundle again:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
plugin_manager action: install_bundle target: @fffangchenghao/dsh-latex-look
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
On a live profile the upgrade applies immediately; the harness reloads the module generation on the next start, because `core.mjs` (unlike `index.js`) is not hot-reloaded.
|
|
72
|
+
|
|
73
|
+
From a checkout, the check is available directly:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx latex-look --version
|
|
77
|
+
npx latex-look --check-update
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Releasing
|
|
81
|
+
|
|
82
|
+
Tagging `v*` runs the test suite on Ubuntu with TeX and Ghostscript installed, verifies the tag matches `package.json`, and publishes to npm. The token comes from the `NPM_TOKEN` repository secret.
|
|
83
|
+
|
|
84
|
+
### Optional: the knowledge skill
|
|
85
|
+
|
|
86
|
+
The tool gives the agent eyes. [`SKILL.md`](SKILL.md) gives it the LaTeX knowledge to act on what it sees — a symptom → cause → fix playbook (a rule you want gone is usually `\pagestyle`/`fancyhdr`, not a stray `\hline`). Copy or symlink it into the skills root:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
mkdir -p ~/.dsh/skills/latex-look
|
|
90
|
+
ln -sf "$PWD/SKILL.md" ~/.dsh/skills/latex-look/SKILL.md
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### CLI, for humans and for testing
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
node bin/latex-look.mjs book.tex --page 37
|
|
97
|
+
node bin/latex-look.mjs book.tex --text "表3.1"
|
|
98
|
+
node bin/latex-look.mjs book.tex --page 12 --compare previous --json
|
|
99
|
+
node bin/latex-look.mjs book.tex --page 1 --audit # what did the last edit break, anywhere?
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The CLI runs the same engine as the plugin and needs no Harness, which makes it the fast way to check a change without reloading anything.
|
|
103
|
+
|
|
104
|
+
## Tests
|
|
105
|
+
|
|
106
|
+
Three suites run on a clean checkout — they create their own fixtures and compile
|
|
107
|
+
real documents, so expect a few minutes:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
node test/smoke.mjs # unit level: problem parsing, PPM, raster diff, aux detection
|
|
111
|
+
node test/scenarios.mjs # 52 end-to-end cases driven through index.js
|
|
112
|
+
node test/scenarios-deep.mjs # 29 cases: multi-file papers, beamer, twocolumn, images,
|
|
113
|
+
# engine switching, the build cache, hostile inputs
|
|
114
|
+
node test/book-scenarios.mjs # 15 cases against a real 208-page book (164 .tex files)
|
|
115
|
+
node test/book-tasks.mjs # 18 real editing tasks on the same book (see below)
|
|
116
|
+
LATEX_LOOK_BOOK=/pristine/copy node test/book-tasks.mjs # point it at an unedited copy
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The book suite needs a real book and is skipped unless you point it at one:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
LATEX_LOOK_BOOK=/path/to/book node test/book-scenarios.mjs
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
These two suites matter most, because a real book is root → `\input{main}` →
|
|
126
|
+
`\input{chap1}`, with a custom `.sty`, 181 figures, a glossary and `fancyhdr` page
|
|
127
|
+
furniture.
|
|
128
|
+
|
|
129
|
+
`book-scenarios` checks the machinery on that book. `book-tasks` checks the
|
|
130
|
+
*requests*: each case is a sentence a user would say, applied to the copy and then
|
|
131
|
+
verified the way an agent verifies it — look, diff against the previous build, and
|
|
132
|
+
confirm the change is where the edit acts:
|
|
133
|
+
|
|
134
|
+
| user says | measured |
|
|
135
|
+
|---|---|
|
|
136
|
+
| 把页眉那条横线去掉 | `0.09%`, bbox `y=52 h=1` (a thin band at the top) |
|
|
137
|
+
| 页眉的线太细,加粗到 2pt | `0.27%`, bbox `y=52 h=3` |
|
|
138
|
+
| 链接的蓝色去掉 | `0.29%`, bbox over the contents page |
|
|
139
|
+
| 行距调大一点 | `10.53%`, 208 → 248 pages |
|
|
140
|
+
| 表格行距加大 | `9.07%` |
|
|
141
|
+
| chap2 那个总结框太窄,放宽 | `7.39%` |
|
|
142
|
+
| chap1 那个图太小,放大 | `10.57%` |
|
|
143
|
+
| chap4 那组图对齐时右边空着 | `8.81%` |
|
|
144
|
+
| 段落首行不要缩进 | `14.07%` |
|
|
145
|
+
| 页边距太窄 | `13.32%`, 208 → 226 pages |
|
|
146
|
+
| 正文字号改成 12pt | `11.63%`, 208 → 226 pages |
|
|
147
|
+
| 目录只显示到章 | `2.19%`, 208 → 207 pages |
|
|
148
|
+
|
|
149
|
+
Nothing in these cases is asserted about how the change *looks* — that is what the
|
|
150
|
+
image is for. The assertions are: the build succeeded, a diff exists, and its
|
|
151
|
+
bounding box is in the region the edit acts on.
|
|
152
|
+
|
|
153
|
+
Both end-to-end suites stub only `@deepseek-ai/dsh-tools` (it exists solely inside
|
|
154
|
+
the Harness) and call the real `execute` with real arguments, so they exercise the
|
|
155
|
+
argument handling and the exact text the model reads — not just `lib/core.mjs`.
|
|
156
|
+
That matters: several bugs found this way lived only in the formatting layer.
|
|
157
|
+
|
|
158
|
+
## The page SyncTeX names is not always the page it is on
|
|
159
|
+
|
|
160
|
+
SyncTeX records the page a box was **built** on. A float that TeX moves to the next
|
|
161
|
+
page gets the previous page's number, so `text: "Version 0.5"` used to answer
|
|
162
|
+
"page 205" for a table that ships on 206 — and page 205 is a different chapter.
|
|
163
|
+
The tool now checks the PDF's own text layer (`gs -sDEVICE=txtwrite`, ~0.12 s a page)
|
|
164
|
+
and looks one and two pages either side; when it overrules SyncTeX it says so.
|
|
165
|
+
|
|
166
|
+
## One picture, not two
|
|
167
|
+
|
|
168
|
+
A `compare` call writes three images, but the one to show the user is
|
|
169
|
+
`images.sideBySide`: the page before on the left, the page after on the right, one
|
|
170
|
+
PNG. Finding the difference by eye across two separate files is the hardest part of
|
|
171
|
+
the job for someone who cannot read the source.
|
|
172
|
+
|
|
173
|
+
Writing a PNG needs no dependency — a signature, `IHDR`, one zlib `IDAT`, `IEND`, and
|
|
174
|
+
`node:zlib` supplies the compression. The composite costs about 0.3 s on an A4 page at
|
|
175
|
+
140 dpi, and only runs when there is something to compare.
|
|
176
|
+
|
|
177
|
+
## Performance
|
|
178
|
+
|
|
179
|
+
Verifying an edit always costs a real compile; there is no way around that. But
|
|
180
|
+
browsing pages of the same build should not:
|
|
181
|
+
|
|
182
|
+
| operation | 208-page book |
|
|
183
|
+
|---|---|
|
|
184
|
+
| first look (3 passes + glossary) | ~17 s |
|
|
185
|
+
| every look after that, sources unchanged | ~0.2 s |
|
|
186
|
+
|
|
187
|
+
A build is reused when the document, everything it `\input`/`\include`s (three
|
|
188
|
+
levels), its `\includegraphics` files and its aux files are unchanged (mtime and
|
|
189
|
+
size). A failing build is never reused. Pass `fresh: true` to bypass it.
|
|
190
|
+
|
|
191
|
+
Two consequences worth knowing:
|
|
192
|
+
|
|
193
|
+
- **The before-snapshot belongs to the build, not to the call.** After an edit, the
|
|
194
|
+
first look records what the page looked like before it; every later look at that
|
|
195
|
+
same build reuses the same snapshot, so an agent inspecting three pages of its
|
|
196
|
+
own edit still sees a diff on the second and third. (It used to be consumed by
|
|
197
|
+
the first call, which is exactly the wrong way round.)
|
|
198
|
+
- **One build per document at a time.** Two compiles of the same file share its
|
|
199
|
+
`.aux`/`.toc` and corrupt each other — racing two processes on the 208-page book,
|
|
200
|
+
one of them died with no error message at all. Builds are now serialised in-process
|
|
201
|
+
by a promise queue and across processes by a lock file that carries the owner's
|
|
202
|
+
PID, so a killed run cannot wedge the document.
|
|
203
|
+
|
|
204
|
+
`-draftmode` on the intermediate passes was measured and rejected: it saves about
|
|
205
|
+
2%, because the time is typesetting, not PDF writing. The ~16 s is TeX itself.
|
|
206
|
+
|
|
207
|
+
## The tool
|
|
208
|
+
|
|
209
|
+
```jsonc
|
|
210
|
+
latex_look({
|
|
211
|
+
"file": "book.tex", // main .tex (required)
|
|
212
|
+
"page": 37, // optional, defaults to 1
|
|
213
|
+
"text": "表3.1", // optional: locate this string, show its page
|
|
214
|
+
"pointX": 240, // optional, with pointY: PDF points → source line
|
|
215
|
+
"pointY": 610,
|
|
216
|
+
"compare": "previous.pdf",// optional: diff against an earlier PDF
|
|
217
|
+
"dpi": 200, // optional, 50-300
|
|
218
|
+
"engine": "xelatex", // optional
|
|
219
|
+
"maxPasses": 3, // optional
|
|
220
|
+
"timeoutSeconds": 300, // optional
|
|
221
|
+
"fresh": false, // optional: ignore the build cache
|
|
222
|
+
"audit": false, // optional: whole-document layout audit vs the last run
|
|
223
|
+
"auditBlankMm": 30 // optional: how deep a bottom hole must be to count,
|
|
224
|
+
"sequence": "图" // optional: check that numbered labels stay in reading order
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
|
|
230
|
+
- compile status, pass count, page count, and structured `problems` — each with `file`, `line`, `message`
|
|
231
|
+
- `warnings`: LaTeX/Package/Class warnings plus **Overfull/Underfull** boxes, the usual cause of an ugly page
|
|
232
|
+
- `toc`: whether the document declares `\tableofcontents` and how many entries it actually produced (an empty TOC is reported as a problem, not a success). The declaration is found across `\input`/`\include`, so a one-line root that wraps `\input{main}` still works
|
|
233
|
+
- `aux`: external tools it ran — `makeglossaries`, `makeindex`, `biber`, `bibtex` — chosen from the artifacts the first pass produced, not from the root file's text, so multi-file projects that keep `\makeglossaries` in an included preamble are handled too
|
|
234
|
+
- PNG path(s) for the rendered page — hand these to an image reader
|
|
235
|
+
- `located`: `{ file, line, page }` for the text anchor or point you asked about
|
|
236
|
+
- `diff`: `changedRatio` plus the bounding box of the change **in PDF points**, ready to feed back into `pointX`/`pointY`
|
|
237
|
+
- `sequence`: reading order of labels that start a line ("图 1-1", "Figure 3.2", …). A float is placed later than an inline object that follows it in the source, so floating one member of a numbered run makes the numbers go backwards — the one defect a per-page diff can never show
|
|
238
|
+
- `audit`: page-level layout findings — pages that stop short of the deepest text in the book, pages that start low, nearly empty pages — **compared with the baseline the previous audit left behind**, so `newFindings` is exactly what the latest build broke. Running heads, rules and page numbers are detected and excluded first; otherwise a page number becomes "the lowest ink in the book" and every page looks broken
|
|
239
|
+
|
|
240
|
+
## How the locating works
|
|
241
|
+
|
|
242
|
+
`latex_look` compiles with `-synctex=1`, so every glyph carries a link to its source line. Then:
|
|
243
|
+
|
|
244
|
+
- **point → source**: `synctex edit -o page:x:y:file.pdf`
|
|
245
|
+
- **text → source**: search the `.tex` sources for the string, then `synctex view` each hit to get its page and coordinates
|
|
246
|
+
- **diff bbox → source**: pass the bbox centre as `pointX`/`pointY`
|
|
247
|
+
|
|
248
|
+
Coordinates are PDF points (72/inch) with the origin at the **top-left** of the page.
|
|
249
|
+
|
|
250
|
+
## Honest limits
|
|
251
|
+
|
|
252
|
+
- **`page` is the PDF page index, not the number printed on the page.** In the test
|
|
253
|
+
book those differ by 10 (printed 130 = index 140), and the offset is different again
|
|
254
|
+
in the roman-numbered front matter. PDF page labels would let the tool translate, but
|
|
255
|
+
LaTeX only writes them on request, so an agent that takes a page number from a user
|
|
256
|
+
must confirm it against the rendered page first.
|
|
257
|
+
- SyncTeX resolves to the *nearest text position*. A decorative rule drawn by a page style (`\pagestyle{headings}`, `fancyhdr`) has no glyph of its own, so it resolves to nearby text. Combine the coordinates with LaTeX knowledge — that is what `SKILL.md` is for.
|
|
258
|
+
- Multi-pass convergence is checked by hashing `.aux`/`.toc`/`.out`/… — a document with `\tableofcontents` needs ≥ 2 passes, and `latex_look` reports a problem if the TOC comes out empty.
|
|
259
|
+
- Multi-file detection follows `\input`/`\include` three levels deep. `\import`/`\subfile`, unusual custom `.latexmkrc` recipes, and `\includeonly` are not understood.
|
|
260
|
+
- Cross-engine pixel parity is impossible: TeX vs Word, or two different TeX engines, have different line-breaking engines. This tool verifies *your* engine's output, not another format's.
|
|
261
|
+
- A `%` in a *file name* defeats the TeX engine itself (`! Emergency stop.`), so no tool can help there. A `%` in a directory name is fine.
|
|
262
|
+
- If the compile fails without writing a new PDF, the stale PDF on disk is **not** rendered and no diff is reported — the tool says so instead. Otherwise an unchanged file would read as "0% changed", i.e. "your edit did nothing".
|
|
263
|
+
- `engine` accepts only the engines in this tool's list (`auto` or a known name); it is never passed to `spawn` unchecked.
|
|
264
|
+
- Rendered PNGs stay under `TMPDIR/latex-look-<pid>/` because the agent opens them after the call returns. Nothing prunes that directory.
|
|
265
|
+
- The build cache tracks the document, its `\input`/`\include` targets, its `\includegraphics` files and its aux files. A `.sty` that changed, or a file pulled in through a macro rather than a literal `\input{...}`, is not tracked — pass `fresh: true` if you suspect a stale build.
|
|
266
|
+
- `maxPasses: 1` on a document with `\tableofcontents` is reported as an error when no `.toc` existed before, and as a warning otherwise: the `.toc` file is written *after* the TOC page is typeset, so one pass reliably produces a blank TOC page.
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# latex-look — 让 Agent 真正「看见」你的 LaTeX
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fffangchenghao/dsh-latex-look)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[English](README.md) · **简体中文**
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
*左边编译**零错误**:标题上下各一条横线、表格是带竖线的网格、长链接冲出版心、
|
|
10
|
+
图顶到了页边。右边是五句人话之后的同一页。可直接复现:[`examples/broken-layout`](examples/broken-layout)。*
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 它解决什么问题
|
|
15
|
+
|
|
16
|
+
一个只会读 `.tex` 源码的 Agent 是**瞎的**。
|
|
17
|
+
|
|
18
|
+
文档可以编译零错误、打开正常,但读者一眼就看出不对:这页最上面多了一条横线、这个表格丑、
|
|
19
|
+
这段文字压到了页边、图太大了、两页之间空了一大块、图号怎么从 3-4 跳回 3-3 了……
|
|
20
|
+
这些**没有一条是编译错误**,`grep` 也搜不到——那条横线根本不在源码里,它是宏包画的。
|
|
21
|
+
|
|
22
|
+
这类需求你只能这样表达:
|
|
23
|
+
|
|
24
|
+
> 「把这一页最上面的横线去掉」「这个表格改成三线表」「图小一点」
|
|
25
|
+
> 「这两页之间空太多了,填满」「图号顺序不对」
|
|
26
|
+
|
|
27
|
+
`latex_look` 就是让 Agent 接得住这些话:**编译 → 渲染成图 → 定位到源码行 → 改完再比一次**,
|
|
28
|
+
一个调用做完。Agent 于是能进入 `看 → 诊断 → 改 → 再看 → 确认` 的循环,而不是改完就说「应该好了」。
|
|
29
|
+
|
|
30
|
+
## 三个能力,一个动词
|
|
31
|
+
|
|
32
|
+
| 你说的话 | 它做什么 |
|
|
33
|
+
|---|---|
|
|
34
|
+
| 「这页最上方的横线去掉」 | 渲染该页,把那条线**定位到源码行**(`fancyhdr` 的 `\headrulewidth`,或某个 `\rule`) |
|
|
35
|
+
| 「表格改成三线表」 | 改完渲染,用**像素 diff** 告诉你只动了表格、没碰正文 |
|
|
36
|
+
| 「图号顺序不对」 | 扫全书**阅读顺序**,直接列出 `图 3-4 (p63) → 图 3-3 (p64)` |
|
|
37
|
+
| 「这次改动有没有弄坏别处」 | 全书 `audit`:只报**这一次**新造成的版面问题,并点名是哪个图把页面顶走的 |
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
latex-look main.tex --page 62 # 渲染第 62 页
|
|
41
|
+
latex-look main.tex --page 62 --at "图 3-1" # 定位到源码行
|
|
42
|
+
latex-look main.tex --page 62 --compare # 与上一版并排对比
|
|
43
|
+
latex-look main.tex --page 1 --sequence 图 # 检查图号阅读顺序
|
|
44
|
+
latex-look main.tex --page 1 --audit # 全书版面回归审计
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 这不是「AI 帮你写 LaTeX」
|
|
48
|
+
|
|
49
|
+
写 LaTeX 的工具已经很多了(生成、补全、编译服务)。这个工具管的是**验收**:
|
|
50
|
+
改完之后,那份 PDF 到底对不对。
|
|
51
|
+
|
|
52
|
+
这也是为什么它值得单独存在——**编译器只保证「不是错误」,不保证「好看」**。
|
|
53
|
+
|
|
54
|
+
## 真实战绩
|
|
55
|
+
|
|
56
|
+
用一本 215 页的中文技术书(6 章、163 张图、129 个公式、术语表、目录)当靶子,
|
|
57
|
+
读者只能看 PDF、逐像素量、拒绝「我觉得没问题」:
|
|
58
|
+
|
|
59
|
+
- 编译错误 **0**,Overfull hbox **0**
|
|
60
|
+
- 163 个图题全部按阅读顺序,**0 处倒序**
|
|
61
|
+
- 129 个公式编号分章连续 `(1-1)` … `(6-1)`
|
|
62
|
+
- 9 处「页面下方空一大块」全部压到 ≤56 mm
|
|
63
|
+
- 六章首页首行墨迹**全部 17.0%** 页高——没有任何一章被图占了章名位置
|
|
64
|
+
|
|
65
|
+
完整记录(含每个缺陷的成因与修法):[CASE-STUDY.md](CASE-STUDY.md)。
|
|
66
|
+
|
|
67
|
+
## 安装
|
|
68
|
+
|
|
69
|
+
需要 Node ≥ 18、TeX 引擎(`xelatex`/`lualatex`/`pdflatex`/`latexmk` 或 tectonic)、
|
|
70
|
+
`synctex`、`ghostscript`。
|
|
71
|
+
|
|
72
|
+
DeepSeek Harness 插件形态:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
plugin_manager action: install_bundle target: @fffangchenghao/dsh-latex-look
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
命令行形态:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm i -g @fffangchenghao/dsh-latex-look
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Agent 的「眼睛」由工具提供;[`SKILL.md`](SKILL.md) 提供「看到症状之后怎么改」的知识——
|
|
85
|
+
一份**症状 → 成因 → 修法**的手册,例如「读者说的那条横线通常是 `\pagestyle`/`fancyhdr`,
|
|
86
|
+
不是某个 `\hline`」。
|
|
87
|
+
|
|
88
|
+
## 更新
|
|
89
|
+
|
|
90
|
+
插件每天在后台查一次 npm,有新版本时在**一次**工具回复里加一行提示;离线时完全静默。
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
latex-look --version
|
|
94
|
+
latex-look --check-update
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 许可
|
|
98
|
+
|
|
99
|
+
MIT。
|