paperlint 11.1.3 → 12.1.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.
- package/CONTRIBUTING.md +2 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +18 -8
- package/dist/build.js.map +1 -1
- package/dist/latex-log.d.ts +61 -26
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +59 -6
- package/dist/latex-log.js.map +1 -1
- package/dist/latex-loop.d.ts +21 -6
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js +32 -12
- package/dist/latex-loop.js.map +1 -1
- package/docs/configuration.md +30 -1
- package/docs/e2e.md +1 -0
- package/fixtures/build-e2e/lost-character/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/lost-character/expect.json +15 -0
- package/fixtures/build-e2e/lost-character/paper.tex +8 -0
- package/package.json +1 -1
- package/presets/agent.jsonc +75 -0
- package/presets/agent.md +70 -0
- package/presets/agentverify.jsonc +72 -0
- package/presets/agentverify.md +85 -0
- package/presets/ieee-conference.md +1 -1
- package/presets/nlbse.jsonc +76 -0
- package/presets/nlbse.md +85 -0
- package/scripts/coverage-config.test.ts +3 -0
- package/skills/CLAUDE.md +2 -2
- package/skills/CLAUDE.md.spec.ts +1 -1
- package/src/build.test.ts +46 -3
- package/src/build.ts +20 -8
- package/src/latex-log.harness.ts +145 -14
- package/src/latex-log.ts +105 -16
- package/src/latex-loop.harness.ts +70 -34
- package/src/latex-loop.ts +51 -30
- package/src/paper-settings-commands.test.ts +1 -1
- package/src/preset-card-rule.test.ts +3 -0
- package/src/presets.test.ts +55 -31
- package/src/tex-venue-rules.test.ts +3 -0
- package/src/venue-rules.test.ts +2 -2
package/src/latex-loop.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* Both halves are table tests (`latex-loop.harness.ts`), not scenarios that need TeX.
|
|
7
7
|
*
|
|
8
8
|
* ── THE LOOP: `summarize` reduces the history to a `State`, and `nextStep` asks four questions ──
|
|
9
|
-
* 1. did the last program fail
|
|
9
|
+
* 1. did the last program fail — a non-zero exit, or a
|
|
10
|
+
* final log carrying a warning on latex-log's list of
|
|
11
|
+
* failing ones (`FAILING_WARNINGS`)? → fail, with its lines
|
|
10
12
|
* 2. did the citations, a .bib or the style change since
|
|
11
13
|
* bibtex last ran? → bibtex
|
|
12
14
|
* 3. did the last pass change a tracked file, or did the
|
|
@@ -43,12 +45,17 @@ export const TRACKED: readonly Tracked[] = ["aux", "toc", "out", "bbl"];
|
|
|
43
45
|
export type Hashes = { readonly [K in Tracked]: string | null };
|
|
44
46
|
|
|
45
47
|
/** Log markers, as `latex-log.ts` reads them. Re-declared as a type so this module has no imports. */
|
|
46
|
-
export type LogMarker =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
export type LogMarker = "rerun-requested" | "labels-changed" | "rerunfilecheck";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A warning on `latex-log.ts`'s list of those that fail the build, as the log of one pass carries it:
|
|
52
|
+
* its id, why it fails, and each distinct line quoted once with how many times it appeared.
|
|
53
|
+
*/
|
|
54
|
+
export interface LogFailure {
|
|
55
|
+
readonly id: string;
|
|
56
|
+
readonly reason: string;
|
|
57
|
+
readonly quotes: readonly { readonly text: string; readonly times: number }[];
|
|
58
|
+
}
|
|
52
59
|
|
|
53
60
|
const RERUN: readonly LogMarker[] = [
|
|
54
61
|
"rerun-requested",
|
|
@@ -88,6 +95,8 @@ export type Observation =
|
|
|
88
95
|
readonly errorLines: readonly string[];
|
|
89
96
|
/** What `-recorder` wrote after this pass (`paper.fls`), or null when pdflatex wrote none. */
|
|
90
97
|
readonly fls: string | null;
|
|
98
|
+
/** The failing warnings this pass's log carries. Only the final pass's are judged. */
|
|
99
|
+
readonly failing: readonly LogFailure[];
|
|
91
100
|
}
|
|
92
101
|
| {
|
|
93
102
|
readonly step: "bibtex";
|
|
@@ -101,16 +110,13 @@ export type Observation =
|
|
|
101
110
|
|
|
102
111
|
export type FailCause =
|
|
103
112
|
| { readonly kind: "exit"; readonly code: number }
|
|
104
|
-
| { readonly kind: "no-convergence"; readonly unsettled: readonly string[] }
|
|
113
|
+
| { readonly kind: "no-convergence"; readonly unsettled: readonly string[] }
|
|
114
|
+
| { readonly kind: "log-warnings"; readonly ids: readonly string[] };
|
|
105
115
|
|
|
106
116
|
export type Step =
|
|
107
117
|
| { readonly kind: "latex"; readonly final: boolean }
|
|
108
118
|
| { readonly kind: "bibtex" }
|
|
109
|
-
| {
|
|
110
|
-
readonly kind: "done";
|
|
111
|
-
/** Undefined-reference summaries still in the final log — a converged doc with a bad key. */
|
|
112
|
-
readonly warnings: readonly LogMarker[];
|
|
113
|
-
}
|
|
119
|
+
| { readonly kind: "done" }
|
|
114
120
|
| {
|
|
115
121
|
readonly kind: "fail";
|
|
116
122
|
readonly step: "latex" | "bibtex";
|
|
@@ -136,13 +142,11 @@ export type State = {
|
|
|
136
142
|
/** Non-final pdflatex passes so far. */
|
|
137
143
|
readonly latexPasses: number;
|
|
138
144
|
readonly finalDone: boolean;
|
|
139
|
-
/** Undefined-reference summaries in the log of the final pass. */
|
|
140
|
-
readonly warnings: readonly LogMarker[];
|
|
141
145
|
};
|
|
142
146
|
|
|
143
147
|
export function nextStep(s: State): Step {
|
|
144
148
|
if (s.failed) return { kind: "fail", ...s.failed };
|
|
145
|
-
if (s.finalDone) return { kind: "done"
|
|
149
|
+
if (s.finalDone) return { kind: "done" };
|
|
146
150
|
if (s.bibOutdated) return { kind: "bibtex" };
|
|
147
151
|
if (s.unsettled.length === 0) return { kind: "latex", final: true };
|
|
148
152
|
if (s.latexPasses < MAX_PASSES) return { kind: "latex", final: false };
|
|
@@ -183,31 +187,48 @@ function unsettledBy(history: readonly Observation[]): string[] {
|
|
|
183
187
|
return [];
|
|
184
188
|
}
|
|
185
189
|
|
|
190
|
+
/** A failing warning as the human reads it: the id and reason, then each quoted line. */
|
|
191
|
+
const failureLines = (f: LogFailure): readonly string[] => [
|
|
192
|
+
`${f.id} — ${f.reason}:`,
|
|
193
|
+
...f.quotes.map(
|
|
194
|
+
(q) => ` ${q.text}${q.times > 1 ? ` (${String(q.times)} times)` : ""}`,
|
|
195
|
+
),
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Did the last run fail? A non-zero exit, whichever program; or a FINAL pass whose log carries a
|
|
200
|
+
* failing warning. An earlier pass's log is not judged: before the rerun every reference is
|
|
201
|
+
* undefined by construction.
|
|
202
|
+
*/
|
|
203
|
+
function failureOf(last: Observation | undefined): State["failed"] {
|
|
204
|
+
if (last === undefined) return null;
|
|
205
|
+
if (last.exitCode !== 0)
|
|
206
|
+
return {
|
|
207
|
+
step: last.step,
|
|
208
|
+
cause: { kind: "exit", code: last.exitCode },
|
|
209
|
+
lines: last.errorLines,
|
|
210
|
+
};
|
|
211
|
+
if (last.step !== "latex" || !last.final || last.failing.length === 0)
|
|
212
|
+
return null;
|
|
213
|
+
return {
|
|
214
|
+
step: "latex",
|
|
215
|
+
cause: { kind: "log-warnings", ids: last.failing.map((f) => f.id) },
|
|
216
|
+
lines: last.failing.flatMap(failureLines),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
186
220
|
export function summarize(history: readonly Observation[]): State {
|
|
187
221
|
const last = history.at(-1);
|
|
188
222
|
const latexRuns = history.filter((o) => o.step === "latex");
|
|
189
223
|
const lastLatex = latexRuns.at(-1);
|
|
190
224
|
const lastBibtex = history.filter((o) => o.step === "bibtex").at(-1);
|
|
191
225
|
return {
|
|
192
|
-
failed:
|
|
193
|
-
last && last.exitCode !== 0
|
|
194
|
-
? {
|
|
195
|
-
step: last.step,
|
|
196
|
-
cause: { kind: "exit", code: last.exitCode },
|
|
197
|
-
lines: last.errorLines,
|
|
198
|
-
}
|
|
199
|
-
: null,
|
|
226
|
+
failed: failureOf(last),
|
|
200
227
|
bibOutdated:
|
|
201
228
|
lastLatex?.bib.kind === "needed" &&
|
|
202
229
|
!(lastBibtex && sameBib(lastBibtex.bib, lastLatex.bib)),
|
|
203
230
|
unsettled: unsettledBy(history),
|
|
204
231
|
latexPasses: latexRuns.filter((o) => !o.final).length,
|
|
205
232
|
finalDone: last?.step === "latex" && last.final,
|
|
206
|
-
warnings:
|
|
207
|
-
last?.step === "latex"
|
|
208
|
-
? last.markers.filter(
|
|
209
|
-
(m) => m === "undefined-references" || m === "undefined-citations",
|
|
210
|
-
)
|
|
211
|
-
: [],
|
|
212
233
|
};
|
|
213
234
|
}
|
|
@@ -302,7 +302,7 @@ describe("paperlint lint — the paper over its preset, and the project's own pr
|
|
|
302
302
|
const r = await lint(root);
|
|
303
303
|
expect(r.code).toBe(1);
|
|
304
304
|
expect(r.out).toMatch(
|
|
305
|
-
/acm-sigconf, agenticdev, aidc, aisec, ieee-conference, msr, realm/,
|
|
305
|
+
/acm-sigconf, agent, agenticdev, agentverify, aidc, aisec, ieee-conference, msr, nlbse, realm/,
|
|
306
306
|
);
|
|
307
307
|
});
|
|
308
308
|
});
|
|
@@ -87,11 +87,14 @@ describe("the shipped cards", () => {
|
|
|
87
87
|
// Guards: a glob that matched nothing lints nothing and reports clean.
|
|
88
88
|
expect(results.map((r) => r.filePath.slice(P.length + 1)).sort()).toEqual([
|
|
89
89
|
"acm-sigconf.md",
|
|
90
|
+
"agent.md",
|
|
90
91
|
"agenticdev.md",
|
|
92
|
+
"agentverify.md",
|
|
91
93
|
"aidc.md",
|
|
92
94
|
"aisec.md",
|
|
93
95
|
"ieee-conference.md",
|
|
94
96
|
"msr.md",
|
|
97
|
+
"nlbse.md",
|
|
95
98
|
"realm.md",
|
|
96
99
|
"tex-base.md",
|
|
97
100
|
]);
|
package/src/presets.test.ts
CHANGED
|
@@ -174,6 +174,53 @@ const FAMILIES = PARSED.filter((p) =>
|
|
|
174
174
|
PARSED.some((q) => q.preset.extends === `${SHIPPED_PREFIX}${p.name}`),
|
|
175
175
|
);
|
|
176
176
|
|
|
177
|
+
// What each shipped preset declares itself to be; a new preset adds its row here.
|
|
178
|
+
const DECLARED = {
|
|
179
|
+
"acm-sigconf.jsonc": { type: "family" },
|
|
180
|
+
"agenticdev.jsonc": {
|
|
181
|
+
type: "venue",
|
|
182
|
+
name: "AgenticDev",
|
|
183
|
+
url: "https://conf.researchr.org/home/ase-2026/agenticdev-2026",
|
|
184
|
+
},
|
|
185
|
+
"agent.jsonc": {
|
|
186
|
+
type: "venue",
|
|
187
|
+
name: "AGENT 2027",
|
|
188
|
+
url: "https://conf.researchr.org/home/icse-2027/agent-2027",
|
|
189
|
+
},
|
|
190
|
+
"agentverify.jsonc": {
|
|
191
|
+
type: "venue",
|
|
192
|
+
name: "AGENTVERIFY",
|
|
193
|
+
url: "https://agentverify2027.github.io/cfp.html",
|
|
194
|
+
},
|
|
195
|
+
"aidc.jsonc": {
|
|
196
|
+
type: "venue",
|
|
197
|
+
name: "AIDC",
|
|
198
|
+
url: "https://aidcworkshop.github.io/",
|
|
199
|
+
},
|
|
200
|
+
"aisec.jsonc": {
|
|
201
|
+
type: "venue",
|
|
202
|
+
name: "AISec",
|
|
203
|
+
url: "https://aisec.cc/",
|
|
204
|
+
},
|
|
205
|
+
"ieee-conference.jsonc": { type: "family" },
|
|
206
|
+
"msr.jsonc": {
|
|
207
|
+
type: "venue",
|
|
208
|
+
name: "MSR",
|
|
209
|
+
url: "https://2027.msrconf.org/track/msr-2027-technical-papers",
|
|
210
|
+
},
|
|
211
|
+
"nlbse.jsonc": {
|
|
212
|
+
type: "venue",
|
|
213
|
+
name: "NLBSE",
|
|
214
|
+
url: "https://conf.researchr.org/home/icse-2027/nlbse-2027",
|
|
215
|
+
},
|
|
216
|
+
"realm.jsonc": {
|
|
217
|
+
type: "venue",
|
|
218
|
+
name: "REALM",
|
|
219
|
+
url: "https://realm-workshop.github.io/call_for_papers",
|
|
220
|
+
},
|
|
221
|
+
"tex-base.jsonc": { type: "base" },
|
|
222
|
+
};
|
|
223
|
+
|
|
177
224
|
describe("what each shipped preset declares itself to be", () => {
|
|
178
225
|
it("every shipped preset loads and declares what it is; a venue links its call", () => {
|
|
179
226
|
const declared = Object.fromEntries(
|
|
@@ -185,36 +232,7 @@ describe("what each shipped preset declares itself to be", () => {
|
|
|
185
232
|
.identity,
|
|
186
233
|
]),
|
|
187
234
|
);
|
|
188
|
-
expect(declared).toEqual(
|
|
189
|
-
"acm-sigconf.jsonc": { type: "family" },
|
|
190
|
-
"agenticdev.jsonc": {
|
|
191
|
-
type: "venue",
|
|
192
|
-
name: "AgenticDev",
|
|
193
|
-
url: "https://conf.researchr.org/home/ase-2026/agenticdev-2026",
|
|
194
|
-
},
|
|
195
|
-
"aidc.jsonc": {
|
|
196
|
-
type: "venue",
|
|
197
|
-
name: "AIDC",
|
|
198
|
-
url: "https://aidcworkshop.github.io/",
|
|
199
|
-
},
|
|
200
|
-
"aisec.jsonc": {
|
|
201
|
-
type: "venue",
|
|
202
|
-
name: "AISec",
|
|
203
|
-
url: "https://aisec.cc/",
|
|
204
|
-
},
|
|
205
|
-
"ieee-conference.jsonc": { type: "family" },
|
|
206
|
-
"msr.jsonc": {
|
|
207
|
-
type: "venue",
|
|
208
|
-
name: "MSR",
|
|
209
|
-
url: "https://2027.msrconf.org/track/msr-2027-technical-papers",
|
|
210
|
-
},
|
|
211
|
-
"realm.jsonc": {
|
|
212
|
-
type: "venue",
|
|
213
|
-
name: "REALM",
|
|
214
|
-
url: "https://realm-workshop.github.io/call_for_papers",
|
|
215
|
-
},
|
|
216
|
-
"tex-base.jsonc": { type: "base" },
|
|
217
|
-
});
|
|
235
|
+
expect(declared).toEqual(DECLARED);
|
|
218
236
|
});
|
|
219
237
|
|
|
220
238
|
it("the families another preset extends are exactly the ones that declare `family`", () => {
|
|
@@ -457,7 +475,7 @@ describe("what does not resolve, and says why", () => {
|
|
|
457
475
|
"a shipped name with a typo",
|
|
458
476
|
"paperlint:agenticdve",
|
|
459
477
|
"not-found",
|
|
460
|
-
/acm-sigconf, agenticdev, aidc, aisec, ieee-conference, msr, realm/,
|
|
478
|
+
/acm-sigconf, agent, agenticdev, agentverify, aidc, aisec, ieee-conference, msr, nlbse, realm/,
|
|
461
479
|
],
|
|
462
480
|
["the base TeX set", "paperlint:tex-base", "not-found", /agenticdev/],
|
|
463
481
|
[
|
|
@@ -625,11 +643,14 @@ describe("shippedVenueNames — what each shipped venue is called", () => {
|
|
|
625
643
|
// A venue is labelled by its `name`, a family by its file name.
|
|
626
644
|
expect(names.map((n) => n.label)).toEqual([
|
|
627
645
|
"acm-sigconf",
|
|
646
|
+
"AGENT 2027",
|
|
628
647
|
"AgenticDev",
|
|
648
|
+
"AGENTVERIFY",
|
|
629
649
|
"AIDC",
|
|
630
650
|
"AISec",
|
|
631
651
|
"ieee-conference",
|
|
632
652
|
"MSR",
|
|
653
|
+
"NLBSE",
|
|
633
654
|
"REALM",
|
|
634
655
|
]);
|
|
635
656
|
expect(names.find((n) => n.label === "AISec")?.aliases).toEqual(
|
|
@@ -647,11 +668,14 @@ describe("shippedVenueNames — what each shipped venue is called", () => {
|
|
|
647
668
|
shippedVenueNames({ files, venuesDir: VENUES }).map((n) => n.label),
|
|
648
669
|
).toEqual([
|
|
649
670
|
"acm-sigconf",
|
|
671
|
+
"AGENT 2027",
|
|
650
672
|
"AgenticDev",
|
|
673
|
+
"AGENTVERIFY",
|
|
651
674
|
"AIDC",
|
|
652
675
|
"AISec",
|
|
653
676
|
"ieee-conference",
|
|
654
677
|
"MSR",
|
|
678
|
+
"NLBSE",
|
|
655
679
|
]);
|
|
656
680
|
});
|
|
657
681
|
});
|
|
@@ -998,9 +998,12 @@ describe("otherVenues", () => {
|
|
|
998
998
|
if (!aidc.ok) return;
|
|
999
999
|
expect(otherVenues(aidc.value, deps).map((o) => o.label)).toEqual([
|
|
1000
1000
|
"acm-sigconf",
|
|
1001
|
+
"AGENT 2027",
|
|
1001
1002
|
"AgenticDev",
|
|
1003
|
+
"AGENTVERIFY",
|
|
1002
1004
|
"AISec",
|
|
1003
1005
|
"MSR",
|
|
1006
|
+
"NLBSE",
|
|
1004
1007
|
]);
|
|
1005
1008
|
});
|
|
1006
1009
|
});
|
package/src/venue-rules.test.ts
CHANGED
|
@@ -270,13 +270,13 @@ describe("pdf/profile — the declaration must resolve, or nothing is judged", (
|
|
|
270
270
|
"an unknown venue (a typo would silently disable every check)",
|
|
271
271
|
{ extends: "paperlint:agentic-dev", kind: "short" },
|
|
272
272
|
"preset",
|
|
273
|
-
/agenticdev, aidc, aisec, ieee-conference, msr, realm/,
|
|
273
|
+
/agent, agenticdev, agentverify, aidc, aisec, ieee-conference, msr, nlbse, realm/,
|
|
274
274
|
],
|
|
275
275
|
[
|
|
276
276
|
"the base TeX set is not a venue",
|
|
277
277
|
{ extends: "paperlint:tex-base" },
|
|
278
278
|
"preset",
|
|
279
|
-
/agenticdev, aidc, aisec, ieee-conference, msr, realm/,
|
|
279
|
+
/agent, agenticdev, agentverify, aidc, aisec, ieee-conference, msr, nlbse, realm/,
|
|
280
280
|
],
|
|
281
281
|
[
|
|
282
282
|
"paperlint.json that is not JSON",
|