paperlint 11.1.2 → 12.0.0

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.
Files changed (38) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +18 -8
  3. package/dist/build.js.map +1 -1
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +2 -2
  6. package/dist/cli.js.map +1 -1
  7. package/dist/latex-log.d.ts +61 -26
  8. package/dist/latex-log.d.ts.map +1 -1
  9. package/dist/latex-log.js +59 -6
  10. package/dist/latex-log.js.map +1 -1
  11. package/dist/latex-loop.d.ts +21 -6
  12. package/dist/latex-loop.d.ts.map +1 -1
  13. package/dist/latex-loop.js +32 -12
  14. package/dist/latex-loop.js.map +1 -1
  15. package/dist/submission-confirm-hook.d.ts +27 -19
  16. package/dist/submission-confirm-hook.d.ts.map +1 -1
  17. package/dist/submission-confirm-hook.js +302 -95
  18. package/dist/submission-confirm-hook.js.map +1 -1
  19. package/docs/configuration.md +30 -1
  20. package/docs/e2e.md +1 -0
  21. package/docs/layout.md +22 -22
  22. package/docs/submission.md +32 -8
  23. package/fixtures/build-e2e/lost-character/PIPELINE-STATUS.md +3 -0
  24. package/fixtures/build-e2e/lost-character/expect.json +15 -0
  25. package/fixtures/build-e2e/lost-character/paper.tex +8 -0
  26. package/package.json +1 -1
  27. package/skills/submission-portal/SKILL.md +18 -12
  28. package/skills/submission-portal/SKILL.md.spec.ts +17 -11
  29. package/src/build.test.ts +46 -3
  30. package/src/build.ts +20 -8
  31. package/src/cli.test.ts +3 -3
  32. package/src/cli.ts +2 -2
  33. package/src/latex-log.harness.ts +145 -14
  34. package/src/latex-log.ts +105 -16
  35. package/src/latex-loop.harness.ts +70 -34
  36. package/src/latex-loop.ts +51 -30
  37. package/src/submission-confirm-hook.test.ts +493 -60
  38. package/src/submission-confirm-hook.ts +448 -116
@@ -1,59 +1,77 @@
1
1
  /**
2
- * The `submission-confirm` hook — no send to a venue's portal without the person's own yes.
2
+ * The `submission-confirm` hook — no send to a venue's portal without the person's own yes to it.
3
3
  * PreToolUse on `Bash` (`plugin/hooks/hooks.json`): a command that sends a submission — `paperlint
4
- * submission new|update` with `--save` or `--submit`, or a request straight at a HotCRP site — gets
5
- * `"permissionDecision": "ask"`, and Claude Code puts the question to the person, with the reason
6
- * below, in its permission prompt. Anything else: no output.
4
+ * submission new|update` with `--save` or `--submit` — runs only when the person has said yes, in an
5
+ * `AskUserQuestion` asked since the last send, to a question naming the paper folder (its full path)
6
+ * and the sha256 of the PDF it sends.
7
+ * A command that names a portal's host runs only after a yes to a question naming that host and the
8
+ * command's first 60 characters. Without the yes the command is refused (`"permissionDecision":
9
+ * "deny"`), and the reason tells the agent exactly what to ask. Anything else: no output.
7
10
  *
8
- * ── WHY "ask", AND WHY NOTHING TURNS IT OFF ──────────────────────────────────────
9
- * The hooks reference (code.claude.com/docs/en/hooks): "`ask` prompts the user to confirm"; "A
10
- * hook's `ask` also forces a permission prompt in auto mode"; the reason is "shown to the user in
11
- * the permission prompt"; and decisions combine deny > defer > ask > allow, so an `allow` rule in
12
- * the settings does not outvote it. An agent cannot answer the prompt, and no flag or variable of
13
- * this package skips it — that is the point: the send is the person's decision, taken on the dry
14
- * run's venue check (paperlint#264). The hook never denies (the person may send over a limit) and
15
- * never allows (that would skip the person).
11
+ * ── WHY A QUESTION, NOT CLAUDE CODE'S PERMISSION PROMPT ──────────────────────────
12
+ * It used to answer `ask` (paperlint#283). The Claude mobile app's permission card shows only the
13
+ * command line, not the hook's reason, so the person approved a shell line without seeing which
14
+ * paper or PDF it sends (paperlint#291). `AskUserQuestion` shows the whole question on every
15
+ * client, and a yes to a question that names the folder and the sha256 is a yes to those bytes in that
16
+ * folder: a stale build or a wrong PDF has another sha, and identical bytes in another folder (the
17
+ * same manuscript for another venue) have another path, so their yes does not count.
18
+ *
19
+ * ── WHERE THE YES IS READ ─────────────────────────────────────────────────────────
20
+ * The session's transcript (`transcript_path` in the hook's input, one JSON entry per line). A
21
+ * question is the assistant's `tool_use` named `AskUserQuestion`; its answer is the user entry that
22
+ * carries the matching `tool_result`, whose `toolUseResult.answers` maps each question's text to the
23
+ * option chosen. An answer is a yes when its first word is "yes". Every earlier `Bash` call that
24
+ * sends or names a portal — refused or not, but not this one (`tool_use_id`) — uses up the questions
25
+ * before it, so one yes lets one command through. Within one command each send takes an answer of
26
+ * its own: a yes that names two shas, or the same PDF sent twice, covers one send. When several
27
+ * questions name the same thing, the latest decides.
16
28
  *
17
29
  * ── WHAT IT SEES, AND WHAT IT IS NOT ──────────────────────────────────────────────
18
30
  * A send is judged on the command as the shell reads it (`shellLeaves`: each simple command across
19
31
  * `&&`, `;`, `|`, words unquoted, the commands inside a `bash -c "…"` string too), never on the raw
20
- * text: `submission`, `new|update` and `--save|--submit` in one command's words, or a portal's host
21
- * in any word (a HotCRP-hosted site, or a host a venue preset declares), whatever program it runs.
22
- * A false ask costs one click; a missed send cannot be taken back. NOT SEEN: an upload from a script
23
- * file that names the host only inside the file, or words built at run time (`$(…)`).
24
- *
25
- * The hook catches an agent that runs a send without asking; it is not a security boundary against
26
- * an agent that deliberately disguises a send.
32
+ * text: `submission`, `new|update` and `--save|--submit` in one command's words; or a portal's host
33
+ * in any word (a HotCRP-hosted site, or a host a venue preset declares), whatever program it runs —
34
+ * such a command may send around paperlint's venue check, and has no PDF to name, so the question
35
+ * names the host and the command instead. When the presets cannot be read, only HotCRP-hosted sites
36
+ * and paperlint sends are recognised, and a refusal says so: one bad preset does not stop the shell.
37
+ * NOT SEEN: an upload from a script file that names the host only inside the file, or words built
38
+ * at run time (`$(…)`).
27
39
  *
28
- * The reason is built from the same parse and the CLI's own argument parser (`readSend`), one block
29
- * per send; a send whose PDF cannot be told for certain is still asked about, and says so.
40
+ * The agent writes the question, so a deliberately misleading question still passes. The hook
41
+ * catches an agent that sends without asking, or asks about other bytes; it is not a security
42
+ * boundary against an agent that deliberately disguises a send.
30
43
  *
31
- * ── IT FAILS TOWARD THE QUESTION ─────────────────────────────────────────────────
32
- * The reason is built from files (the PDF, the build's facts); whatever goes wrong there, the ask
33
- * still goes out, saying what could not be read. A payload that is not JSON is asked about.
44
+ * ── IT FAILS CLOSED ───────────────────────────────────────────────────────────────
45
+ * A send whose PDF cannot be told for certain, a PDF that is not there, no transcript, a payload
46
+ * that is not JSON, a crash: refused, with what went wrong. A transcript line that is not JSON is
47
+ * not a reason to refuse every send for the rest of the session — real transcripts have them (an
48
+ * entry cut short and run into the next, measured in a live one) — so it counts as a send when its
49
+ * text may hold one, and a yes before it no longer counts. Only lines that may hold a question, an
50
+ * answer or a send are parsed, and the shell parser runs only on commands that say `submission` or
51
+ * a portal's host:
52
+ * a transcript of hundreds of megabytes is read in seconds.
34
53
  *
35
54
  * ── WHY IN-PROCESS, NOT A VIGILES PROGRAM ────────────────────────────────────────
36
55
  * It reads the build's facts with this package's own parser and hashes the PDF; a shipped vigiles
37
56
  * hook may import nothing but `vigiles/hook` (`src/folder-layout-hook.ts` says the rest). So
38
57
  * `paperlint hook submission-confirm` runs it in-process (`runHook`, src/cli.ts).
39
58
  */
40
- import { basename, isAbsolute, join, relative, resolve } from "node:path";
59
+ import { isAbsolute, join, relative, resolve } from "node:path";
41
60
  import { callerPath } from "./caller-path.js";
42
61
  import { bodyEnd } from "./domain/body-pages.js";
43
- import { fieldOf } from "./domain/record.js";
62
+ import { fieldOf, isRecord } from "./domain/record.js";
44
63
  import { sha256Hex } from "./domain/sha256.js";
45
64
  import { messageOf } from "./domain/text.js";
46
65
  import { factsPath, parseFactsText } from "./facts-file.js";
47
66
  import { paperPreset } from "./presets.js";
48
67
  import { paperPdf } from "./submission.js";
49
- const ask = (reason) => ({
68
+ const deny = (reason) => ({
50
69
  hookSpecificOutput: {
51
70
  hookEventName: "PreToolUse",
52
- permissionDecision: "ask",
71
+ permissionDecision: "deny",
53
72
  permissionDecisionReason: reason,
54
73
  },
55
74
  });
56
- const LAST = "Allow only if you have read the dry run's venue check.";
57
75
  /** The domain of HotCRP's hosted sites; a self-hosted portal is known by the host its preset declares. */
58
76
  const HOTCRP_HOSTED = "hotcrp.com";
59
77
  /**
@@ -92,25 +110,21 @@ function sendsIn(leaves, readSend) {
92
110
  return [moved ? null : readSend(l.args.slice(at + 1))];
93
111
  });
94
112
  }
113
+ /** The words of the commands that may name a host: those with a dot, lowercased. */
114
+ const dottedWords = (leaves) => leaves
115
+ .flatMap((l) => [l.head, ...l.args])
116
+ .map((w) => w.toLowerCase())
117
+ .filter((w) => w.includes("."));
118
+ /** The portal hosts known: HotCRP's hosted sites always, then those `hosts` names; lowercased. */
119
+ const knownHosts = (hosts) => [HOTCRP_HOSTED, ...hosts].map((h) => h.toLowerCase()).filter((h) => h !== "");
95
120
  /**
96
- * What the commands send: a submission through paperlint, anything that names a venue's portal — a
97
- * HotCRP-hosted site, or a host a venue preset declares (`hosts`, read only when a word has a dot),
98
- * whatever program it runs, as a plain case-insensitive substring of a word — or nothing. Judged on
99
- * the shell's words only, never on the raw text.
121
+ * The portal host a command names — a HotCRP-hosted site, or a host a venue preset declares
122
+ * (`hosts`), whatever program it runs, as a plain case-insensitive substring of a word — or null.
123
+ * Judged on the shell's words only, never on the raw text.
100
124
  */
101
- export function sendKind(leaves, hosts) {
102
- if (leaves.some(isSend))
103
- return "paperlint";
104
- const words = leaves
105
- .flatMap((l) => [l.head, ...l.args])
106
- .map((w) => w.toLowerCase())
107
- .filter((w) => w.includes("."));
108
- if (words.length === 0)
109
- return null;
110
- const known = [HOTCRP_HOSTED, ...hosts()]
111
- .map((h) => h.toLowerCase())
112
- .filter((h) => h !== "");
113
- return words.some((w) => known.some((h) => w.includes(h))) ? "direct" : null;
125
+ function portalHost(leaves, hosts) {
126
+ const words = dottedWords(leaves);
127
+ return (knownHosts(hosts).find((h) => words.some((w) => w.includes(h))) ?? null);
114
128
  }
115
129
  /** The body pages the build counted: up to the references or appendix, or banal's count. */
116
130
  function bodyPages(f) {
@@ -144,43 +158,238 @@ function factsLines(paperDir, sha, files) {
144
158
  return [...stale, bodyPages(f.value)];
145
159
  }
146
160
  /** One send, described: the paper, the PDF as the command resolves it, what the build saw. */
147
- function sendLines(send, cwd, deps) {
161
+ function describe(send, cwd, deps) {
148
162
  const paperDir = resolve(cwd, send.paper ?? ".");
149
163
  const p = paperPreset(paperDir, deps);
150
164
  const own = paperPdf(paperDir, p.kind === "settings-problem" ? null : p.settings);
151
165
  const pdf = send.pdf === null ? own : resolve(cwd, send.pdf);
152
166
  const bytes = deps.files.readBytes(callerPath(pdf));
153
167
  const sha = bytes === null ? null : sha256Hex(bytes);
154
- return [
155
- `paper folder: ${send.paper ?? `${basename(paperDir)} (the current folder)`}`,
156
- `PDF: ${send.pdf ?? join(send.paper ?? ".", relative(paperDir, own))}${sha === null ? " — NOT FOUND" : `, sha256 ${sha.slice(0, 12)}`}`,
157
- ...factsLines(paperDir, sha, deps.files),
158
- ];
168
+ return {
169
+ named: sha === null ? null : { folder: paperDir, sha: sha.slice(0, SHA_SHOWN) },
170
+ lines: [
171
+ `paper folder: ${paperDir}`,
172
+ `PDF: ${send.pdf ?? join(send.paper ?? ".", relative(paperDir, own))}${sha === null ? " — NOT FOUND" : `, sha256 ${sha.slice(0, SHA_SHOWN)}`}`,
173
+ ...factsLines(paperDir, sha, deps.files),
174
+ ],
175
+ };
159
176
  }
177
+ /** How much of the sha256 the reason shows, and the question must contain. */
178
+ const SHA_SHOWN = 12;
160
179
  /** A send whose PDF cannot be told for certain. */
161
180
  const UNRESOLVED = "could not resolve which PDF this send uploads — check the dry run";
181
+ const NOT_SENT = "Not run: this sends the paper to the venue's portal";
182
+ const NAMES_PORTAL = "Not run: this command names a venue's submission portal";
183
+ /** What to ask, once every send's PDF is known. */
184
+ const HOW_TO_ASK = [
185
+ "Ask the person with AskUserQuestion first, then run this command again. In the question, give:",
186
+ "- the paper folder (the full path, as written above), the PDF and its sha256 as written above (the folder path and the sha256 must both be in the question text),",
187
+ "- what the lines above say about the build (the body pages, or that it is stale or missing),",
188
+ "- the dry run's venue check, quoted word for word (its `venue rules at their default options` line and every error under it).",
189
+ 'Options: "Yes, send it" and "No". One yes lets one send through: ask one question per send, each naming its own folder and sha256.',
190
+ ].join("\n");
191
+ /** How much of a command naming a portal the question must quote. */
192
+ const COMMAND_SHOWN = 60;
193
+ /** What to ask before a command that names a portal. */
194
+ const HOW_TO_ASK_PORTAL = [
195
+ "It may send to the portal around paperlint's venue check. Ask the person with AskUserQuestion first, then run this command again: the question must contain the portal host and the command start above, exactly as written, and say what the command does.",
196
+ 'Options: "Yes, run it" and "No". One yes lets one command through. To send a paper, use `paperlint submission new|update --save` instead.',
197
+ ].join("\n");
198
+ /** Each send's block: alone, or numbered when the command has several. */
199
+ function blocks(described) {
200
+ return described.length === 1
201
+ ? described.flatMap((d) => d.lines)
202
+ : described.flatMap((d, n) => [
203
+ `send ${String(n + 1)} of ${String(described.length)}:`,
204
+ ...d.lines,
205
+ ]);
206
+ }
207
+ /** A transcript line that may hold a question, an answer or a send; no other line is parsed. */
208
+ const MAY_MATTER = /"tool_use"|"toolUseResult"/u;
209
+ /** Nothing asked or answered: the start, and the state right after a send. */
210
+ const CLEARED = { asked: new Set(), answers: [] };
211
+ /** A transcript line that is not JSON. */
212
+ const BROKEN = Symbol("broken");
162
213
  /**
163
- * Why the person is asked, for `paperlint submission` sends: each send's paper, PDF and build — every
164
- * send in the command, one block each. Words the shell would expand (`$…`, backticks) are not
165
- * guessed at: such a command is described as unresolved.
214
+ * The person's answers to `AskUserQuestion` since the last send in the transcript, oldest first.
215
+ * A line that may matter and is not JSON (real transcripts have them: an entry cut short, run into
216
+ * the next) counts as a send when it may hold one, so a yes before it is used up — never wrongly
217
+ * kept; otherwise it is skipped, and a yes lost in it only means asking again.
166
218
  */
167
- export function paperlintReason(command, leaves, cwd, deps) {
168
- const sends = /[`$]/u.test(command) ? [] : sendsIn(leaves, deps.readSend);
169
- const one = (x) => x === null ? [UNRESOLVED] : sendLines(x, cwd, deps);
170
- const blocks = sends.length === 0
171
- ? [UNRESOLVED]
172
- : sends.length === 1
173
- ? sends.flatMap(one)
174
- : sends.flatMap((x, n) => [
175
- `send ${String(n + 1)} of ${String(sends.length)}:`,
176
- ...one(x),
177
- ]);
178
- return [SENDS, ...blocks, LAST].join("\n");
179
- }
180
- /** The ask when the hook's input cannot be read at all: it cannot tell, so the person decides. */
181
- export const UNREADABLE_INPUT = ask(`paperlint could not read this command, so it cannot tell whether it sends a paper to a venue's portal. ${LAST}`);
182
- const SENDS = "This sends the paper to the venue's portal (paperlint submission with --save or --submit).";
183
- const DIRECT = `This command names a venue's submission portal, so it may send to it outside paperlint and its venue check. ${LAST}`;
219
+ export function answersSinceLastSend(transcript, earlier) {
220
+ return transcript.split("\n").reduce((since, line) => {
221
+ if (!MAY_MATTER.test(line))
222
+ return since;
223
+ const entry = parsedLine(line);
224
+ if (entry === BROKEN)
225
+ return earlier.mayHold(line) ? CLEARED : since;
226
+ const content = fieldOf(fieldOf(entry, "message"), "content");
227
+ return (Array.isArray(content) ? content : []).reduce((s, block) => fieldOf(block, "type") === "tool_result"
228
+ ? toolResult(s, block, entry)
229
+ : toolUse(s, block, earlier), since);
230
+ }, CLEARED).answers;
231
+ }
232
+ /** One transcript line, parsed; BROKEN when it is not JSON. */
233
+ function parsedLine(line) {
234
+ try {
235
+ return JSON.parse(line);
236
+ }
237
+ catch {
238
+ return BROKEN;
239
+ }
240
+ }
241
+ /** A block the assistant wrote: a question is noted; an earlier send clears what was asked before it. */
242
+ function toolUse(s, block, earlier) {
243
+ const id = fieldOf(block, "id");
244
+ if (fieldOf(block, "type") !== "tool_use" || typeof id !== "string")
245
+ return s;
246
+ const name = fieldOf(block, "name");
247
+ if (name === "AskUserQuestion")
248
+ return { ...s, asked: new Set([...s.asked, id]) };
249
+ const command = fieldOf(fieldOf(block, "input"), "command");
250
+ const send = name === "Bash" &&
251
+ id !== earlier.current &&
252
+ typeof command === "string" &&
253
+ earlier.mayHold(command) &&
254
+ earlier.sends(command);
255
+ return send ? CLEARED : s;
256
+ }
257
+ /** The person's reply to a question asked since the last send: its answers, kept in order. */
258
+ function toolResult(s, block, entry) {
259
+ const id = fieldOf(block, "tool_use_id");
260
+ if (typeof id !== "string" || !s.asked.has(id))
261
+ return s;
262
+ const answers = fieldOf(fieldOf(entry, "toolUseResult"), "answers");
263
+ const given = Object.entries(isRecord(answers) ? answers : {}).flatMap(([question, answer]) => typeof answer === "string" ? [{ question, answer }] : []);
264
+ return { ...s, answers: [...s.answers, ...given] };
265
+ }
266
+ /** Whether an answer is a yes: its first word, in any case. */
267
+ const isYes = (a) => /^yes\b/iu.test(a.answer.trim());
268
+ /** Whether `text` contains every one of `keys`, in any case. */
269
+ const hasAll = (text, keys) => keys.every((k) => text.toLowerCase().includes(k.toLowerCase()));
270
+ /** `text` as a literal inside a regular expression. */
271
+ const literal = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
272
+ /**
273
+ * Whether a question names the folder `folder` and not a longer path that starts with it
274
+ * (`/w/paper` is not `/w/paper2` or `/w/paper.old`), nor one it ends: the whole path, case-sensitive.
275
+ */
276
+ const namesFolder = (question, folder) => new RegExp(`(?<![\\w.-])${literal(folder)}(?![\\w.-])`, "u").test(question);
277
+ /**
278
+ * The answers used up after one more need: it takes the latest answer not yet taken whose question
279
+ * `names` it, and that answer must be a yes; null when there is none, or it is not.
280
+ */
281
+ function takeYes(answers, taken, names) {
282
+ if (taken === null)
283
+ return null;
284
+ const at = answers.findLastIndex((a, n) => !taken.has(n) && names(a.question));
285
+ const found = answers[at];
286
+ return found !== undefined && isYes(found) ? new Set([...taken, at]) : null;
287
+ }
288
+ /** What a yes to one send must name: its sha256 (any case) and its folder (the whole path, as written). */
289
+ const namesSend = (s) => (question) => hasAll(question, [s.sha]) && namesFolder(question, s.folder);
290
+ /** What a yes to a command that names a portal must name: the host (any case) and the command's start, literally. */
291
+ const namesPortal = (host, start) => (question) => hasAll(question, [host]) && question.includes(start);
292
+ /**
293
+ * Whether every send and the portal command (null for none) has a yes of its own. Each takes the
294
+ * latest answer not yet taken, in turn, from one shared set: a yes used up by a send cannot also
295
+ * approve the portal, one that names two shas covers one send, and the same PDF sent twice needs
296
+ * two. (A yes that two needs could share is given to the first; the second is refused and the
297
+ * person is asked again: it fails closed.)
298
+ */
299
+ const everyNeedHasItsYes = (answers, sends, portal) => [...sends.map(namesSend), ...(portal === null ? [] : [portal])].reduce((taken, names) => takeYes(answers, taken, names), new Set()) !== null;
300
+ /** Every send in the command, described; an unresolved one has no sha. */
301
+ function describedSends(command, cwd, deps) {
302
+ const sends = /[`$]/u.test(command)
303
+ ? []
304
+ : sendsIn(deps.shellLeaves(command), deps.readSend);
305
+ return (sends.length === 0 ? [null] : sends).map((x) => x === null ? { named: null, lines: [UNRESOLVED] } : describe(x, cwd, deps));
306
+ }
307
+ /** The refusal of a send whose PDF is not known or not there: there is no sha256 to ask about. */
308
+ const noPdf = (described) => [
309
+ `${NOT_SENT}, and paperlint cannot find the PDF it uploads, so there is no sha256 to ask the person about.`,
310
+ ...blocks(described),
311
+ "Name the paper folder and the PDF plainly (no cd, no $…), build the PDF, run the dry run, and ask then.",
312
+ ];
313
+ /** The presets' hosts; when they cannot be read, none, and why. */
314
+ function readHosts(cwd, deps) {
315
+ try {
316
+ return { hosts: deps.portalHosts(cwd), failed: null };
317
+ }
318
+ catch (e) {
319
+ return { hosts: [], failed: messageOf(e) };
320
+ }
321
+ }
322
+ /** The line a refusal ends with when the presets could not be read; none when they could. */
323
+ const fallbackLines = (h) => h.failed === null
324
+ ? []
325
+ : [
326
+ `The venue presets could not be read (${h.failed}), so only HotCRP-hosted sites and paperlint sends are recognised until they are fixed.`,
327
+ ];
328
+ /**
329
+ * A command that sends: let through (null) when the person said yes to it since the last send —
330
+ * each paperlint send by its paper folder and its PDF's sha256, a yes of its own for each, a portal
331
+ * named by its host and the command's start —
332
+ * else the lines of the refusal, with what to ask.
333
+ */
334
+ function decide(c, payload, deps) {
335
+ const described = c.paperlint ? describedSends(c.text, c.cwd, deps) : [];
336
+ const named = described.flatMap((d) => (d.named === null ? [] : [d.named]));
337
+ if (named.length < described.length)
338
+ return noPdf(described);
339
+ const base = c.paperlint ? NOT_SENT : NAMES_PORTAL;
340
+ const read = transcriptOf(payload, deps.files);
341
+ if (typeof read !== "string")
342
+ return [
343
+ `${base}, and ${read.failed}, so paperlint cannot see the person's yes.`,
344
+ ];
345
+ const start = c.text.trim().slice(0, COMMAND_SHOWN);
346
+ const answers = answersSinceLastSend(read, earlierSend(c, payload, deps));
347
+ const portal = c.host === null ? null : namesPortal(c.host, start);
348
+ if (everyNeedHasItsYes(answers, named, portal))
349
+ return null;
350
+ return [
351
+ `${base}, and the person has not said yes to it since the last send.`,
352
+ ...blocks(described),
353
+ ...(c.paperlint ? [HOW_TO_ASK] : []),
354
+ ...portalLines(c.host, start),
355
+ ];
356
+ }
357
+ /** What a refusal says about a portal the command names: the host, the start, what to ask; none for none. */
358
+ const portalLines = (host, start) => host === null
359
+ ? []
360
+ : [`portal host: ${host}`, `command start: ${start}`, HOW_TO_ASK_PORTAL];
361
+ /** How an earlier send is told in the transcript: by the same matching as this command. */
362
+ function earlierSend(c, payload, deps) {
363
+ const known = knownHosts(c.hosts.hosts);
364
+ return {
365
+ current: stringField(payload, "tool_use_id"),
366
+ mayHold: (text) => {
367
+ const plain = text.replace(/["'\\]/gu, "").toLowerCase();
368
+ return (plain.includes("submission") || known.some((h) => plain.includes(h)));
369
+ },
370
+ sends: (command) => {
371
+ const leaves = allLeaves(command, deps.shellLeaves);
372
+ return leaves.some(isSend) || portalHost(leaves, known) !== null;
373
+ },
374
+ };
375
+ }
376
+ /** A string field of the payload; null for none. */
377
+ function stringField(payload, key) {
378
+ const v = fieldOf(payload, key);
379
+ return typeof v === "string" ? v : null;
380
+ }
381
+ /** The session's transcript as text, or why it could not be read. */
382
+ function transcriptOf(payload, files) {
383
+ const path = stringField(payload, "transcript_path");
384
+ if (path === null || !isAbsolute(path))
385
+ return { failed: "the hook was given no transcript_path" };
386
+ const bytes = files.readBytes(callerPath(path));
387
+ return bytes === null
388
+ ? { failed: `the transcript ${path} could not be read` }
389
+ : new TextDecoder().decode(bytes);
390
+ }
391
+ /** The refusal when the hook's input cannot be read at all: it cannot tell, so it does not let it run. */
392
+ export const UNREADABLE_INPUT = deny("Not run: paperlint could not read this command, so it cannot tell whether it sends a paper to a venue's portal.");
184
393
  /** The payload, parsed; null when it is not JSON. */
185
394
  function parsed(stdin) {
186
395
  try {
@@ -190,41 +399,39 @@ function parsed(stdin) {
190
399
  return null;
191
400
  }
192
401
  }
402
+ /** One command, judged: null to let it run, or the refusal. */
403
+ function judge(text, cwd, payload, deps) {
404
+ const leaves = allLeaves(text, deps.shellLeaves);
405
+ const paperlint = leaves.some(isSend);
406
+ // The presets are read only for a command that may send: anything else pays nothing.
407
+ if (!paperlint && dottedWords(leaves).length === 0)
408
+ return null;
409
+ const hosts = readHosts(cwd, deps);
410
+ const host = portalHost(leaves, hosts.hosts);
411
+ if (!paperlint && host === null)
412
+ return null;
413
+ const lines = decide({ text, cwd, paperlint, host, hosts }, payload, deps);
414
+ return lines === null
415
+ ? null
416
+ : deny([...lines, ...fallbackLines(hosts)].join("\n"));
417
+ }
193
418
  /**
194
- * The hook on one payload, as Claude Code sends it on stdin: the ask, or null for a command that
195
- * sends nothing. Never throws: what it cannot read, it asks about.
419
+ * The hook on one payload, as Claude Code sends it on stdin: a refusal, or null to let the command
420
+ * run (it sends nothing, or every send in it has the person's yes). Never throws: a crash refuses.
196
421
  */
197
422
  export function submissionConfirm(stdin, deps) {
198
423
  const payload = parsed(stdin);
199
424
  if (payload === null)
200
425
  return UNREADABLE_INPUT;
201
426
  const raw = fieldOf(fieldOf(payload, "tool_input"), "command");
202
- const command = typeof raw === "string" ? raw : "";
427
+ const text = typeof raw === "string" ? raw : "";
203
428
  const given = fieldOf(payload, "cwd");
204
429
  const cwd = typeof given === "string" && isAbsolute(given) ? given : resolve(".");
205
- const kind = kindOf(command, cwd, deps);
206
- if (kind === null)
207
- return null;
208
- if (kind !== "paperlint" && kind !== "direct")
209
- return ask(kind.failed);
210
- if (kind === "direct")
211
- return ask(DIRECT);
212
- try {
213
- return ask(paperlintReason(command, deps.shellLeaves(command), cwd, deps));
214
- }
215
- catch (e) {
216
- return ask(`${SENDS} paperlint could not say what will be sent: ${messageOf(e)}. ${LAST}`);
217
- }
218
- }
219
- /** What the command sends, or — when reading it failed — the reason to ask anyway. */
220
- function kindOf(command, cwd, deps) {
221
430
  try {
222
- return sendKind(allLeaves(command, deps.shellLeaves), () => deps.portalHosts(cwd));
431
+ return judge(text, cwd, payload, deps);
223
432
  }
224
433
  catch (e) {
225
- return {
226
- failed: `paperlint could not tell what this command sends (${messageOf(e)}). ${LAST}`,
227
- };
434
+ return deny(`Not run: paperlint could not check whether this command sends a paper: ${messageOf(e)}.`);
228
435
  }
229
436
  }
230
437
  //# sourceMappingURL=submission-confirm-hook.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"submission-confirm-hook.js","sourceRoot":"","sources":["../src/submission-confirm-hook.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"submission-confirm-hook.js","sourceRoot":"","sources":["../src/submission-confirm-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAkB,MAAM,iBAAiB,CAAC;AAE5E,OAAO,EAAE,WAAW,EAAmB,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAoC3C,MAAM,IAAI,GAAG,CAAC,MAAc,EAAc,EAAE,CAAC,CAAC;IAC5C,kBAAkB,EAAE;QAClB,aAAa,EAAE,YAAY;QAC3B,kBAAkB,EAAE,MAAM;QAC1B,wBAAwB,EAAE,MAAM;KACjC;CACF,CAAC,CAAC;AAEH,0GAA0G;AAC1G,MAAM,aAAa,GAAG,YAAY,CAAC;AAEnC;;;GAGG;AACH,SAAS,SAAS,CAChB,OAAe,EACf,KAAiC;IAEjC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO;QACL,GAAG,GAAG;QACN,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACnB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CACxE;KACF,CAAC;AACJ,CAAC;AAED,oGAAoG;AACpG,SAAS,MAAM,CAAC,CAAY;IAC1B,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACxC,OAAO,CACL,EAAE,IAAI,CAAC;QACP,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,QAAQ,CAAC;QAC/D,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAC5B,CAAC;AACJ,CAAC;AAQD,+EAA+E;AAC/E,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAEpE;;;;GAIG;AACH,SAAS,OAAO,CACd,MAA4B,EAC5B,QAAiC;IAEjC,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CACzB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,KAAK,YAAY,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CACtE,CAAC;QACF,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,MAAM;aACjB,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;aACf,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;QACtD,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oFAAoF;AACpF,MAAM,WAAW,GAAG,CAAC,MAA4B,EAAqB,EAAE,CACtE,MAAM;KACH,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;KACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;KAC3B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AAEpC,kGAAkG;AAClG,MAAM,UAAU,GAAG,CAAC,KAAwB,EAAqB,EAAE,CACjE,CAAC,aAAa,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;AAEhF;;;;GAIG;AACH,SAAS,UAAU,CACjB,MAA4B,EAC5B,KAAwB;IAExB,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,CACL,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CACxE,CAAC;AACJ,CAAC;AAED,4FAA4F;AAC5F,SAAS,SAAS,CAAC,CAAY;IAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE;QAChC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa;QACzB,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,kBAAkB;KACpC,CAAC,CAAC;IACH,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QACtB,OAAO,eAAe,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,eAAe,GAAG,CAAC,EAAE,YAAY,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;IAClG,OAAO,CAAC,CAAC,QAAQ,KAAK,IAAI;QACxB,CAAC,CAAC,0FAA0F;QAC5F,CAAC,CAAC,eAAe,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,yBAAyB,CAAC;AAC5E,CAAC;AAED,qEAAqE;AACrE,SAAS,UAAU,CACjB,QAAgB,EAChB,GAAkB,EAClB,KAAY;IAEZ,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC/D,IAAI,KAAK,KAAK,IAAI;QAChB,OAAO;YACL,4GAA4G;SAC7G,CAAC;IACJ,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC,CAAC,EAAE;QACP,OAAO;YACL,6DAA6D,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;SACjI,CAAC;IACJ,MAAM,KAAK,GACT,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG;QACjB,CAAC,CAAC;YACE,yHAAyH;SAC1H;QACH,CAAC,CAAC,EAAE,CAAC;IACT,OAAO,CAAC,GAAG,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACxC,CAAC;AAcD,8FAA8F;AAC9F,SAAS,QAAQ,CAAC,IAAU,EAAE,GAAW,EAAE,IAAgB;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC;IACjD,MAAM,CAAC,GAAG,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,QAAQ,CAClB,QAAQ,EACR,CAAC,CAAC,IAAI,KAAK,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAClD,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACrD,OAAO;QACL,KAAK,EACH,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE;QAC1E,KAAK,EAAE;YACL,iBAAiB,QAAQ,EAAE;YAC3B,QAAQ,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,EAAE,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,EAAE;YAC9I,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC;SACzC;KACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,mDAAmD;AACnD,MAAM,UAAU,GACd,mEAAmE,CAAC;AAEtE,MAAM,QAAQ,GAAG,qDAAqD,CAAC;AACvE,MAAM,YAAY,GAAG,yDAAyD,CAAC;AAE/E,mDAAmD;AACnD,MAAM,UAAU,GAAG;IACjB,gGAAgG;IAChG,mKAAmK;IACnK,8FAA8F;IAC9F,+HAA+H;IAC/H,oIAAoI;CACrI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,qEAAqE;AACrE,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB,wDAAwD;AACxD,MAAM,iBAAiB,GAAG;IACxB,6PAA6P;IAC7P,2IAA2I;CAC5I,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,0EAA0E;AAC1E,SAAS,MAAM,CAAC,SAA+B;IAC7C,OAAO,SAAS,CAAC,MAAM,KAAK,CAAC;QAC3B,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;QACnC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1B,QAAQ,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG;YACvD,GAAG,CAAC,CAAC,KAAK;SACX,CAAC,CAAC;AACT,CAAC;AAcD,gGAAgG;AAChG,MAAM,UAAU,GAAG,6BAA6B,CAAC;AAYjD,8EAA8E;AAC9E,MAAM,OAAO,GAAU,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAEzD,0CAA0C;AAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAkB,EAClB,OAAoB;IAEpB,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,KAAY,EAAE,IAAI,EAAS,EAAE;QACjE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,KAAK,KAAK,MAAM;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;QACrE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC;QAC9D,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CACnD,CAAC,CAAQ,EAAE,KAAc,EAAS,EAAE,CAClC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,aAAa;YACtC,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC;YAC7B,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,EAChC,KAAK,CACN,CAAC;IACJ,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC;AACtB,CAAC;AAED,+DAA+D;AAC/D,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED,yGAAyG;AACzG,SAAS,OAAO,CAAC,CAAQ,EAAE,KAAc,EAAE,OAAoB;IAC7D,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAChC,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,UAAU,IAAI,OAAO,EAAE,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,iBAAiB;QAC5B,OAAO,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;IAC5D,MAAM,IAAI,GACR,IAAI,KAAK,MAAM;QACf,EAAE,KAAK,OAAO,CAAC,OAAO;QACtB,OAAO,OAAO,KAAK,QAAQ;QAC3B,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,8FAA8F;AAC9F,SAAS,UAAU,CAAC,CAAQ,EAAE,KAAc,EAAE,KAAc;IAC1D,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IACzC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,EAAE,SAAS,CAAC,CAAC;IACpE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CACpE,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAqB,EAAE,CACxC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAC3D,CAAC;IACF,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,+DAA+D;AAC/D,MAAM,KAAK,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;AAEvE,gEAAgE;AAChE,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,IAAuB,EAAW,EAAE,CAChE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AAElE,uDAAuD;AACvD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CACvC,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,GAAG,CAAC,QAAgB,EAAE,MAAc,EAAW,EAAE,CAChE,IAAI,MAAM,CAAC,eAAe,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAQ9E;;;GAGG;AACH,SAAS,OAAO,CACd,OAA0B,EAC1B,KAAY,EACZ,KAAY;IAEZ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAC7C,CAAC;IACF,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1B,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9E,CAAC;AAED,2GAA2G;AAC3G,MAAM,SAAS,GACb,CAAC,CAAQ,EAAS,EAAE,CACpB,CAAC,QAAQ,EAAE,EAAE,CACX,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE,qHAAqH;AACrH,MAAM,WAAW,GACf,CAAC,IAAY,EAAE,KAAa,EAAS,EAAE,CACvC,CAAC,QAAQ,EAAE,EAAE,CACX,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,kBAAkB,GAAG,CACzB,OAA0B,EAC1B,KAAuB,EACvB,MAAoB,EACX,EAAE,CACX,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CACpE,CAAC,KAAY,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,EACvD,IAAI,GAAG,EAAU,CAClB,KAAK,IAAI,CAAC;AAEb,0EAA0E;AAC1E,SAAS,cAAc,CACrB,OAAe,EACf,GAAW,EACX,IAAiB;IAEjB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QACjC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtD,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAa,EAAE,CAChE,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAC3E,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,MAAM,KAAK,GAAG,CAAC,SAA+B,EAAqB,EAAE,CAAC;IACpE,GAAG,QAAQ,gGAAgG;IAC3G,GAAG,MAAM,CAAC,SAAS,CAAC;IACpB,yGAAyG;CAC1G,CAAC;AAQF,mEAAmE;AACnE,SAAS,SAAS,CAAC,GAAW,EAAE,IAAiB;IAC/C,IAAI,CAAC;QACH,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACxD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7C,CAAC;AACH,CAAC;AAWD,6FAA6F;AAC7F,MAAM,aAAa,GAAG,CAAC,CAAQ,EAAqB,EAAE,CACpD,CAAC,CAAC,MAAM,KAAK,IAAI;IACf,CAAC,CAAC,EAAE;IACJ,CAAC,CAAC;QACE,wCAAwC,CAAC,CAAC,MAAM,yFAAyF;KAC1I,CAAC;AAER;;;;;GAKG;AACH,SAAS,MAAM,CACb,CAAU,EACV,OAAgB,EAChB,IAAiB;IAEjB,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5E,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC;IACnD,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,IAAI,OAAO,IAAI,KAAK,QAAQ;QAC1B,OAAO;YACL,GAAG,IAAI,SAAS,IAAI,CAAC,MAAM,6CAA6C;SACzE,CAAC;IACJ,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACnE,IAAI,kBAAkB,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO;QACL,GAAG,IAAI,8DAA8D;QACrE,GAAG,MAAM,CAAC,SAAS,CAAC;QACpB,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED,6GAA6G;AAC7G,MAAM,WAAW,GAAG,CAAC,IAAmB,EAAE,KAAa,EAAqB,EAAE,CAC5E,IAAI,KAAK,IAAI;IACX,CAAC,CAAC,EAAE;IACJ,CAAC,CAAC,CAAC,gBAAgB,IAAI,EAAE,EAAE,kBAAkB,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAE7E,2FAA2F;AAC3F,SAAS,WAAW,CAClB,CAAU,EACV,OAAgB,EAChB,IAAiB;IAEjB,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxC,OAAO;QACL,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,aAAa,CAAC;QAC5C,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAChB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;YACzD,OAAO,CACL,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CACrE,CAAC;QACJ,CAAC;QACD,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE;YACjB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACpD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,oDAAoD;AACpD,SAAS,WAAW,CAAC,OAAgB,EAAE,GAAW;IAChD,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAChC,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1C,CAAC;AAED,qEAAqE;AACrE,SAAS,YAAY,CACnB,OAAgB,EAChB,KAAY;IAEZ,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IACrD,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QACpC,OAAO,EAAE,MAAM,EAAE,uCAAuC,EAAE,CAAC;IAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,OAAO,KAAK,KAAK,IAAI;QACnB,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAkB,IAAI,oBAAoB,EAAE;QACxD,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,0GAA0G;AAC1G,MAAM,CAAC,MAAM,gBAAgB,GAAe,IAAI,CAC9C,iHAAiH,CAClH,CAAC;AAEF,qDAAqD;AACrD,SAAS,MAAM,CAAC,KAAa;IAC3B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,SAAS,KAAK,CACZ,IAAY,EACZ,GAAW,EACX,OAAgB,EAChB,IAAiB;IAEjB,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,IAAI,CAAC,SAAS,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChE,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,CAAC,SAAS,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3E,OAAO,KAAK,KAAK,IAAI;QACnB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAa,EACb,IAAiB;IAEjB,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,gBAAgB,CAAC;IAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,SAAS,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,GAAG,GACP,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxE,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,IAAI,CACT,0EAA0E,SAAS,CAAC,CAAC,CAAC,GAAG,CAC1F,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -399,7 +399,8 @@ papers/my-paper
399
399
  keys or a `.bib` file changed. After that, one **final** pass defines `\finalpass`, which arms
400
400
  the reference guards in `paper-guards.tex`: an undefined `\ref` or `\cite` fails the build
401
401
  there instead of printing `??`. A document that still changes after five passes fails, naming
402
- the file that kept changing. After a green compile the step also writes
402
+ the file that kept changing. The final pass's log is then read for the warnings that fail the
403
+ build (below). After a green compile the step also writes
403
404
  `_build/sources.json`, the record of what TeX read: every file of the paper directory its passes
404
405
  opened (from the `.fls` that `-recorder` makes), whether each came before or after
405
406
  `\begin{document}`, the `.bib` files TeX itself wrote, the databases and keys bibtex read, and a
@@ -452,6 +453,34 @@ without a new one:
452
453
  paper.pdf removed — a stale PDF must not pass for this build
453
454
  ```
454
455
 
456
+ **Some warnings fail the build although pdflatex exits 0**, because the PDF it wrote is wrong. They
457
+ are one list, `FAILING_WARNINGS` in [`src/latex-log.ts`](../src/latex-log.ts), and only the log of
458
+ the **final** pass is read — before the rerun every reference is undefined by construction:
459
+
460
+ | id | the log line | why it fails |
461
+ | ------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------- |
462
+ | `lost-character` | `Missing character: There is no <char> in font <font>!` | the font has no glyph for the character, so the PDF lacks it (`10^-13` → `10^13`) |
463
+ | `undefined-references` | `LaTeX Warning: There were undefined references.` | the PDF prints `??` for a `\ref`, or `[?]` for a `\cite` without natbib |
464
+ | `undefined-citations` | `Package natbib Warning: There were undefined citations.` | the PDF prints `[?]` for a `\cite` (natbib) |
465
+ | `multiply-defined-labels` | `LaTeX Warning: There were multiply-defined labels.` | a `\ref` may point at the wrong one of two labels |
466
+
467
+ Every other warning — overfull and underfull boxes, font substitutions, "Rerun to get …" — leaves
468
+ the build as it was. The failure quotes each distinct line once, with how many times the log has it
469
+ (here `\char"FF` in `cmr10`, measured on TeX Live 2026; TeX writes the character as one raw byte,
470
+ which is no UTF-8 character, so it shows as `�`):
471
+
472
+ ```
473
+ ✗ compile: pdflatex exited 0, but its final log carries a warning that fails the build
474
+ lost-character — the font has no glyph for this character, so pdflatex left it out of the PDF:
475
+ Missing character: There is no � in font cmr10! (2 times)
476
+ full log: papers/my-paper/paper.log
477
+ paper.pdf removed — a stale PDF must not pass for this build
478
+ ```
479
+
480
+ ⚠️ TeX writes the `Missing character` line only while `\tracinglostchars` is above 0. LaTeX sets it
481
+ to 2 (TeX Live 2026); a paper that sets it to 0 hides the line, and this check sees nothing. At 3
482
+ and above TeX makes it an error, and the build fails on pdflatex's exit code instead.
483
+
455
484
  **A paper with no `paper.tex` is a FAILURE, not a skip.** "Nothing to build" and "built" must never
456
485
  look alike: the corpus this came from once let a paper reach its venue without a single paper job
457
486
  having run on it, because a missing build read as nothing to do.
package/docs/e2e.md CHANGED
@@ -98,6 +98,7 @@ A new venue fixture is a new folder: its paper, its `paperlint.json`, its `expec
98
98
  | `broken` | a failing build names pdflatex and its exit code, quotes the error line and its `l.NNN` context, and deletes the stale `paper.pdf` planted before the run |
99
99
  | `no-source` | a paper with no `paper.tex` is named separately, and the run as a whole is a failure |
100
100
  | `empty` | a document with no pages: pdflatex exits 0 and writes no PDF, and that is a failure, not a green `paper.pdf` |
101
+ | `lost-character` | pdflatex exits 0 and writes a PDF without a character its font cannot draw (`\char"FF` in cmr10); the build fails, quotes the `Missing character` line from the log and leaves no `paper.pdf` |
101
102
  | `aidc` | a real `[conference,compsoc]{IEEEtran}` paper builds on paperlint's TeX Live in NimbusRomNo9L, `tex/template`, `tex/required-section` and the `pdf/*` rules find nothing, and `tex/venue-leftover` warns once, on its planted line. It declares `identity` and names nobody, so `anonymity/identity` is silent on a real build, and `format/page-limit` finds the IEEEtran «References» heading |
102
103
  | `anon-*` | six AIDC papers, each planting the declared (invented) name on ONE path, and `anonymity/identity` reports exactly one finding naming where: `author` the author block · `ack` an acknowledgments paragraph · `bib` a self-citation, printed by `IEEEtran.bst` as «A. Example» · `pdfauthor` `\hypersetup{pdfauthor=…}`, on no page · `href` a link whose target is the author's account while its text is not (the anonymous mirror beside it is not reported) · `macro` the name behind `\newcommand` |
103
104
  | `msr` | a real `[10pt,conference]{IEEEtran}` paper whose ten pages of main text include an appendix before the references, then two pages of only references: `format/page-limit`, `tex/template` and `anonymity/identity` find nothing. banal's count of the same PDF (body 9, references 3) would have failed it |