paperlint 4.0.0 → 4.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/.github/workflows/ci.yml +16 -23
- package/CLAUDE.md +31 -11
- package/CONTRIBUTING.md +23 -7
- package/README.md +11 -8
- package/dist/adapters/banal/invocation.d.ts +1 -1
- package/dist/adapters/latex/document-class.d.ts +22 -0
- package/dist/adapters/latex/document-class.d.ts.map +1 -0
- package/dist/adapters/latex/document-class.js +102 -0
- package/dist/adapters/latex/document-class.js.map +1 -0
- package/dist/adapters/latex/hidden.d.ts +16 -0
- package/dist/adapters/latex/hidden.d.ts.map +1 -0
- package/dist/adapters/latex/hidden.js +49 -0
- package/dist/adapters/latex/hidden.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +29 -0
- package/dist/adapters/latex/index.d.ts.map +1 -0
- package/dist/adapters/latex/index.js +18 -0
- package/dist/adapters/latex/index.js.map +1 -0
- package/dist/adapters/latex/nodes.d.ts +54 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -0
- package/dist/adapters/latex/nodes.js +93 -0
- package/dist/adapters/latex/nodes.js.map +1 -0
- package/dist/adapters/latex/outline.d.ts +10 -0
- package/dist/adapters/latex/outline.d.ts.map +1 -0
- package/dist/adapters/latex/outline.js +39 -0
- package/dist/adapters/latex/outline.js.map +1 -0
- package/dist/adapters/latex/parse.d.ts +42 -0
- package/dist/adapters/latex/parse.d.ts.map +1 -0
- package/dist/adapters/latex/parse.js +94 -0
- package/dist/adapters/latex/parse.js.map +1 -0
- package/dist/adapters/latex/rendered.d.ts +10 -0
- package/dist/adapters/latex/rendered.d.ts.map +1 -0
- package/dist/adapters/latex/rendered.js +39 -0
- package/dist/adapters/latex/rendered.js.map +1 -0
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +12 -12
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/build.js +4 -4
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -5
- package/dist/cli.js.map +1 -1
- package/dist/domain/tex-document.d.ts +84 -0
- package/dist/domain/tex-document.d.ts.map +1 -0
- package/dist/domain/tex-document.js +43 -0
- package/dist/domain/tex-document.js.map +1 -0
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +2 -2
- package/dist/facts-file.js.map +1 -1
- package/dist/new-paper.d.ts +5 -0
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +3 -0
- package/dist/new-paper.js.map +1 -1
- package/dist/package-dirs.d.ts +5 -0
- package/dist/package-dirs.d.ts.map +1 -0
- package/dist/package-dirs.js +14 -0
- package/dist/package-dirs.js.map +1 -0
- package/dist/ports/latex.d.ts +24 -0
- package/dist/ports/latex.d.ts.map +1 -0
- package/dist/ports/latex.js +2 -0
- package/dist/ports/latex.js.map +1 -0
- package/dist/presets.d.ts +15 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -6
- package/dist/presets.js.map +1 -1
- package/dist/tex-requirements.d.ts +11 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +14 -8
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +83 -0
- package/dist/tex-venue-rules.d.ts.map +1 -0
- package/dist/tex-venue-rules.js +254 -0
- package/dist/tex-venue-rules.js.map +1 -0
- package/dist/venue-rules.d.ts +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/docs/configuration.md +3 -2
- package/docs/e2e.md +69 -40
- package/docs/install.md +1 -1
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/package-location.md +5 -5
- package/docs/prior-art/repro/README.md +1 -1
- package/docs/prior-art/test-tooling.md +21 -10
- package/docs/rules/tex/required-section.md +73 -0
- package/docs/rules/tex/template.md +62 -0
- package/docs/rules/tex/venue-leftover.md +71 -0
- package/docs/rules.md +70 -48
- package/docs/testing.md +17 -4
- package/docs/texlive-install-decision.md +1 -1
- package/docs/toolchain.md +2 -2
- package/eslint.config.mjs +155 -15
- package/fixtures/build-e2e/acmart/expect.json +26 -0
- package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/aidc/expect.json +14 -0
- package/fixtures/build-e2e/aidc/paper.tex +46 -0
- package/fixtures/build-e2e/aidc/paperlint.json +1 -0
- package/fixtures/build-e2e/broken/expect.json +11 -0
- package/fixtures/build-e2e/cite/expect.json +27 -0
- package/fixtures/build-e2e/empty/expect.json +12 -0
- package/fixtures/build-e2e/fallback/expect.json +53 -0
- package/fixtures/build-e2e/guards/expect.json +6 -0
- package/fixtures/build-e2e/no-source/expect.json +10 -0
- package/fixtures/build-e2e/unbalanced/expect.json +60 -0
- package/fixtures/pdf-facts/README.md +1 -1
- package/fixtures/real-markdown-paper/baseline.ts +1 -1
- package/fixtures/render-paper/build-clean.sh +3 -5
- package/package.json +13 -2
- package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
- package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
- package/presets/aidc.jsonc +73 -0
- package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
- package/presets/ieee-conference.jsonc +66 -0
- package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
- package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
- package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
- package/scripts/check.harness.ts +64 -10
- package/scripts/check.test.ts +60 -13
- package/scripts/check.ts +83 -30
- package/scripts/test.test.ts +1 -1
- package/scripts/test.ts +1 -1
- package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
- package/skills/paper-pipeline/SKILL.md +2 -2
- package/skills/paper-pipeline/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
- package/skills/paper-pipeline/scripts/consumer.mjs +1 -15
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/check-render.sh +16 -63
- package/skills/render-paper/render-paper.harness.mjs +17 -59
- package/src/adapters/banal/index.test.ts +1 -1
- package/src/adapters/banal/invocation.ts +1 -1
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +1 -1
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/latex/document-class.ts +147 -0
- package/src/adapters/latex/hidden.ts +70 -0
- package/src/adapters/latex/index.test.ts +361 -0
- package/src/adapters/latex/index.ts +55 -0
- package/src/adapters/latex/nodes.ts +141 -0
- package/src/adapters/latex/outline.ts +64 -0
- package/src/adapters/latex/parse.ts +136 -0
- package/src/adapters/latex/rendered.ts +59 -0
- package/src/adapters/pdfjs/fill.test.ts +1 -1
- package/src/adapters/pdfjs/fill.ts +18 -10
- package/src/build.harness.mjs +5 -7
- package/src/build.ts +4 -4
- package/src/cli.harness.mjs +1 -1
- package/src/cli.test.ts +2 -8
- package/src/cli.ts +33 -8
- package/src/domain/tex-document.test.ts +80 -0
- package/src/domain/tex-document.ts +110 -0
- package/src/engine.harness.mjs +1 -1
- package/src/facts-file.test.ts +4 -7
- package/src/facts-file.ts +2 -2
- package/src/latex-loop.harness.ts +1 -1
- package/src/new-paper.test.ts +28 -3
- package/src/new-paper.ts +8 -0
- package/src/package-dirs.ts +17 -0
- package/src/paper-settings-commands.test.ts +6 -1
- package/src/ports/latex.ts +29 -0
- package/src/presets.test.ts +65 -4
- package/src/presets.ts +38 -15
- package/src/rule-cases.test.ts +474 -0
- package/src/rule-docs.test.ts +159 -0
- package/src/tex-requirements.harness.ts +13 -23
- package/src/tex-requirements.ts +35 -8
- package/src/tex-venue-rules.test.ts +659 -0
- package/src/tex-venue-rules.ts +429 -0
- package/src/toolchain.harness.mjs +1 -1
- package/src/venue-rules.test.ts +96 -4
- package/src/venue-rules.ts +1 -1
- package/templates/paper/paper.tex +2 -1
- /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
- /package/{skills/submit-paper/references/venues → presets}/tex-base.jsonc +0 -0
package/scripts/check.ts
CHANGED
|
@@ -34,9 +34,11 @@
|
|
|
34
34
|
* (no TeX, no network), and the tail names every skip. "I ran check" then means one thing.
|
|
35
35
|
*/
|
|
36
36
|
import { spawnSync } from "node:child_process";
|
|
37
|
-
import { existsSync } from "node:fs";
|
|
37
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
38
|
+
import { tmpdir } from "node:os";
|
|
38
39
|
import { delimiter, dirname, join } from "node:path";
|
|
39
40
|
import { fileURLToPath } from "node:url";
|
|
41
|
+
import { z } from "zod";
|
|
40
42
|
import { isMain } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
41
43
|
|
|
42
44
|
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -72,11 +74,17 @@ type GateCommand =
|
|
|
72
74
|
readonly script: string;
|
|
73
75
|
readonly run?: undefined;
|
|
74
76
|
readonly inCi?: RunInCi;
|
|
77
|
+
/**
|
|
78
|
+
* The script is a vitest run, so its JSON report says how many tests were SKIPPED: a run
|
|
79
|
+
* whose exit is 0 and whose report counts a skip is a skip, not a pass.
|
|
80
|
+
*/
|
|
81
|
+
readonly vitest?: true;
|
|
75
82
|
}
|
|
76
83
|
| {
|
|
77
84
|
readonly run: readonly [string, ...string[]];
|
|
78
85
|
readonly script?: undefined;
|
|
79
86
|
readonly inCi?: undefined;
|
|
87
|
+
readonly vitest?: undefined;
|
|
80
88
|
};
|
|
81
89
|
|
|
82
90
|
/**
|
|
@@ -160,22 +168,14 @@ export const GATES: readonly Gate[] = [
|
|
|
160
168
|
{
|
|
161
169
|
name: "install e2e — pack, install under npm and pnpm, run the binary",
|
|
162
170
|
job: "gates",
|
|
163
|
-
|
|
171
|
+
script: "test:e2e:install",
|
|
172
|
+
vitest: true,
|
|
164
173
|
},
|
|
165
174
|
{
|
|
166
|
-
name: "
|
|
175
|
+
name: "TeX e2e — real TeX Live: paperlint toolchain, a real pdflatex build of every fixture, the real banal",
|
|
167
176
|
job: "build-e2e",
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
{
|
|
171
|
-
name: "banal e2e — the real banal on pdf.js-written XML gives banal-on-pdftohtml's numbers",
|
|
172
|
-
job: "build-e2e",
|
|
173
|
-
run: ["node", "test/e2e/banal.ts"],
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
name: "toolchain e2e — real TeX Live into $PAPERLINT_TEXLIVE_DIR, then a build with only it on PATH",
|
|
177
|
-
job: "build-e2e",
|
|
178
|
-
run: ["node", "test/e2e/toolchain.ts"],
|
|
177
|
+
script: "test:e2e:tex",
|
|
178
|
+
vitest: true,
|
|
179
179
|
},
|
|
180
180
|
];
|
|
181
181
|
|
|
@@ -205,18 +205,41 @@ export const NOT_COVERED: Readonly<Record<string, string>> = {
|
|
|
205
205
|
};
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
|
-
*
|
|
209
|
-
* absent and --strict is off. Anything else nonzero is a failure.
|
|
208
|
+
* vitest's JSON report, as far as the verdict reads it: skipped (`pending`) and `todo` tests.
|
|
210
209
|
*
|
|
211
|
-
* 🔴 A SKIP IS A THIRD OUTCOME. Until the Codex review on #45 the e2e
|
|
212
|
-
* this loop counted that as a pass
|
|
213
|
-
*
|
|
210
|
+
* 🔴 A SKIP IS A THIRD OUTCOME. Until the Codex review on #45 the e2e runs exited 0 on a skip and
|
|
211
|
+
* this loop counted that as a pass: a machine without pnpm or TeX printed "all gates passed" for
|
|
212
|
+
* runs that never happened. The count comes from the runner's own report, not from an exit code
|
|
213
|
+
* the tests would have to agree on.
|
|
214
214
|
*/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
215
|
+
const VitestReport = z.looseObject({
|
|
216
|
+
numPendingTests: z.number().int().nonnegative(),
|
|
217
|
+
numTodoTests: z.number().int().nonnegative(),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
/** How many tests a vitest JSON report says were not run, or null when it is not one. */
|
|
221
|
+
export function skippedIn(report: string | null): number | null {
|
|
222
|
+
if (report === null) return null;
|
|
223
|
+
try {
|
|
224
|
+
const r = VitestReport.safeParse(JSON.parse(report));
|
|
225
|
+
return r.success ? r.data.numPendingTests + r.data.numTodoTests : null;
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A gate's outcome from its exit status and, for a vitest gate, the skip count its report gave
|
|
233
|
+
* (`null`: the report is missing or unreadable, which cannot be read as a pass).
|
|
234
|
+
*/
|
|
235
|
+
export function outcome(
|
|
236
|
+
status: number | null,
|
|
237
|
+
vitest?: { readonly skipped: number | null },
|
|
238
|
+
): Outcome {
|
|
239
|
+
if (status !== 0) return "fail";
|
|
240
|
+
if (vitest === undefined) return "pass";
|
|
241
|
+
if (vitest.skipped === null) return "fail";
|
|
242
|
+
return vitest.skipped > 0 ? "skip" : "pass";
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
/** The gates that did not pass, each as the line the verdict prints, split by outcome. */
|
|
@@ -225,11 +248,29 @@ interface NotPassed {
|
|
|
225
248
|
skipped: string[];
|
|
226
249
|
}
|
|
227
250
|
|
|
251
|
+
/** A report file's text, read once and removed; null when the run wrote none. */
|
|
252
|
+
export type ReadReport = (file: string) => string | null;
|
|
253
|
+
const readReport: ReadReport = (file) => {
|
|
254
|
+
if (!existsSync(file)) return null;
|
|
255
|
+
const text = readFileSync(file, "utf8");
|
|
256
|
+
rmSync(file, { force: true });
|
|
257
|
+
return text;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/** The arguments that make a vitest gate write its JSON report to `file`, beside the usual output. */
|
|
261
|
+
export const reportArgs = (file: string): string[] => [
|
|
262
|
+
"--",
|
|
263
|
+
"--reporter=default",
|
|
264
|
+
"--reporter=json",
|
|
265
|
+
`--outputFile.json=${file}`,
|
|
266
|
+
];
|
|
267
|
+
|
|
228
268
|
/** Run each gate in order; `write` announces it before it starts. */
|
|
229
269
|
function runEach(
|
|
230
270
|
gates: readonly Gate[],
|
|
231
271
|
spawn: Spawn,
|
|
232
272
|
write: (text: string) => unknown,
|
|
273
|
+
reportOf: ReadReport,
|
|
233
274
|
): NotPassed {
|
|
234
275
|
const BIN_FIRST_PATH = [join(ROOT, "node_modules", ".bin"), process.env.PATH]
|
|
235
276
|
.filter(Boolean)
|
|
@@ -237,10 +278,14 @@ function runEach(
|
|
|
237
278
|
const failed: string[] = [];
|
|
238
279
|
const skipped: string[] = [];
|
|
239
280
|
|
|
240
|
-
for (const g of gates) {
|
|
281
|
+
for (const [i, g] of gates.entries()) {
|
|
241
282
|
write(`── ${g.name}\n`);
|
|
242
283
|
const [cmd, ...args] = commandOf(g);
|
|
243
|
-
const
|
|
284
|
+
const file = join(
|
|
285
|
+
tmpdir(),
|
|
286
|
+
`paperlint-check-${String(process.pid)}-${String(i)}.json`,
|
|
287
|
+
);
|
|
288
|
+
const r = spawn(cmd, g.vitest ? [...args, ...reportArgs(file)] : args, {
|
|
244
289
|
cwd: ROOT,
|
|
245
290
|
stdio: "inherit",
|
|
246
291
|
encoding: "utf8",
|
|
@@ -249,12 +294,18 @@ function runEach(
|
|
|
249
294
|
// 🔴 The exit code is read from the command itself, never from a pipe. `cmd | tail && …`
|
|
250
295
|
// reports the FILTER's status, which is almost always zero — that is how a red harness
|
|
251
296
|
// shipped on 2026-09-16.
|
|
252
|
-
const
|
|
297
|
+
const skips = g.vitest ? { skipped: skippedIn(reportOf(file)) } : undefined;
|
|
298
|
+
const o = outcome(r.status, skips);
|
|
253
299
|
if (o === "pass") continue;
|
|
254
300
|
const shown = commandOf(g).join(" ");
|
|
255
301
|
if (o === "skip")
|
|
256
|
-
skipped.push(
|
|
257
|
-
|
|
302
|
+
skipped.push(
|
|
303
|
+
`${g.name} (${shown} → ${String(skips?.skipped)} test(s) skipped)`,
|
|
304
|
+
);
|
|
305
|
+
else
|
|
306
|
+
failed.push(
|
|
307
|
+
`${g.name} (${shown} → ${r.status === 0 ? "exit 0, but no vitest JSON report to read skips from" : String(r.status)})`,
|
|
308
|
+
);
|
|
258
309
|
}
|
|
259
310
|
return { failed, skipped };
|
|
260
311
|
}
|
|
@@ -299,14 +350,16 @@ export function runGates({
|
|
|
299
350
|
log = console.log,
|
|
300
351
|
err = console.error,
|
|
301
352
|
write = (s: string) => process.stdout.write(s),
|
|
353
|
+
reportOf = readReport,
|
|
302
354
|
}: {
|
|
303
355
|
gates?: readonly Gate[];
|
|
304
356
|
spawn?: Spawn;
|
|
305
357
|
log?: (line: string) => void;
|
|
306
358
|
err?: (line: string) => void;
|
|
307
359
|
write?: (text: string) => unknown;
|
|
360
|
+
reportOf?: ReadReport;
|
|
308
361
|
} = {}): number {
|
|
309
|
-
const notPassed = runEach(gates, spawn, write);
|
|
362
|
+
const notPassed = runEach(gates, spawn, write, reportOf);
|
|
310
363
|
report(gates, notPassed, { log, err });
|
|
311
364
|
return notPassed.failed.length ? 1 : 0;
|
|
312
365
|
}
|
package/scripts/test.test.ts
CHANGED
|
@@ -24,7 +24,7 @@ test("a failing vitest does not skip the harnesses, and is named as the failure"
|
|
|
24
24
|
1,
|
|
25
25
|
);
|
|
26
26
|
assert.deepEqual(calls, [
|
|
27
|
-
["vitest", "run"],
|
|
27
|
+
["vitest", "run", "--project", "unit"],
|
|
28
28
|
["vigiles", "test", "--min=1", "--no-skip"],
|
|
29
29
|
]);
|
|
30
30
|
assert.deepEqual(said, [
|
package/scripts/test.ts
CHANGED
|
@@ -46,7 +46,7 @@ export function withSources(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export const SUITES: readonly Suite[] = [
|
|
49
|
-
{ name: "vitest", command: ["vitest", "run"] },
|
|
49
|
+
{ name: "vitest", command: ["vitest", "run", "--project", "unit"] },
|
|
50
50
|
{ name: "harnesses", command: ["vigiles", "test", "--min=1"] },
|
|
51
51
|
];
|
|
52
52
|
|
|
@@ -200,7 +200,7 @@ format.
|
|
|
200
200
|
## 3. Venue constants belong in a card, not in code
|
|
201
201
|
|
|
202
202
|
> **Done (#79, 2.1.0), in a different shape than planned below.** The constants live in **venue
|
|
203
|
-
> presets**, JSONC files in `
|
|
203
|
+
> presets**, JSONC files in the package's `presets/`: `acm-sigconf.jsonc` is the ACM
|
|
204
204
|
> family (the `format` block — page size, columns, fonts, font sizes — plus the `tex` packages),
|
|
205
205
|
> `agenticdev.jsonc` and `aisec.jsonc` extend it and add their `kinds` (page limits) and `rules`,
|
|
206
206
|
> `realm.jsonc` stands alone. A paper picks one in `<paper>/paperlint.json`
|
|
@@ -332,7 +332,7 @@ point. Nothing to check.
|
|
|
332
332
|
| page limit | **4** | `venues/realm.md` 8 · `build-submission.sh` 8 · `PIPELINE-STATUS` "8/8" · the build prints **9** |
|
|
333
333
|
| the TeX package list | **5** | `ensure-toolchain.sh` (executable) · `SKILL.md` · `render-paper.harness.mjs` (pins it ✅) · `build-submission.sh` · `PIPELINE-STATUS` |
|
|
334
334
|
|
|
335
|
-
The canonical source is the venue preset (`
|
|
335
|
+
The canonical source is the venue preset (`presets/<name>.jsonc`, resolved through its `extends`
|
|
336
336
|
chain): its `format` block for the numbers (page size, columns, fonts, font sizes, and `kinds` for
|
|
337
337
|
page limits), its `tex` block (plus `tex-base.jsonc`) for packages — since 2026-09-24;
|
|
338
338
|
`ensure-toolchain.sh` held them until then — and its `rules` block for the checks the venue
|
|
@@ -4,7 +4,7 @@ description: The orchestrator for writing a research paper end-to-end, from idea
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:c3eea3ac60e9c816 compiled from skills/paper-pipeline/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# paper-pipeline — the conductor for the whole organism
|
|
10
10
|
|
|
@@ -30,7 +30,7 @@ of that — who is filing what, and when — stays in the author's own private n
|
|
|
30
30
|
space, anonymity, OpenReview mechanics. Fetch once, not per paper. Venue-specific facts stay in
|
|
31
31
|
`submit-paper/references/venues/<venue>.md`. (No prose equivalent yet for ACM or IEEE — write one
|
|
32
32
|
when the next paper goes there. ACM's machine-checked format already exists: the
|
|
33
|
-
`paperlint:acm-sigconf` preset, `
|
|
33
|
+
`paperlint:acm-sigconf` preset, `presets/acm-sigconf.jsonc` in the package.)
|
|
34
34
|
|
|
35
35
|
## The readiness scorecard (which boxes are checked)
|
|
36
36
|
Stages run in subagents and report into chat, which evaporates — so "did everything run?" becomes
|
|
@@ -47,7 +47,7 @@ of that — who is filing what, and when — stays in the author's own private n
|
|
|
47
47
|
space, anonymity, OpenReview mechanics. Fetch once, not per paper. Venue-specific facts stay in
|
|
48
48
|
\`submit-paper/references/venues/<venue>.md\`. (No prose equivalent yet for ACM or IEEE — write one
|
|
49
49
|
when the next paper goes there. ACM's machine-checked format already exists: the
|
|
50
|
-
\`paperlint:acm-sigconf\` preset, \`
|
|
50
|
+
\`paperlint:acm-sigconf\` preset, \`presets/acm-sigconf.jsonc\` in the package.)
|
|
51
51
|
|
|
52
52
|
## The readiness scorecard (which boxes are checked)
|
|
53
53
|
Stages run in subagents and report into chat, which evaporates — so "did everything run?" becomes
|
|
@@ -32,8 +32,6 @@ export interface SkillsFs {
|
|
|
32
32
|
}
|
|
33
33
|
export declare function installedSkills(dir: string, fs?: SkillsFs): string[];
|
|
34
34
|
|
|
35
|
-
/** The directory of venue TeX files this package ships; `paperlint build` prepends it to TEXINPUTS. */
|
|
36
|
-
export declare function packageVenuesDir(): string;
|
|
37
35
|
export declare const SHIPPED_SKILLS_DIR: string;
|
|
38
36
|
export declare const PACKAGE_NAME: "paperlint";
|
|
39
37
|
export declare const BIN_FILE: "bin/paperlint.mjs";
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
statSync,
|
|
58
58
|
} from "node:fs";
|
|
59
59
|
import { join, resolve, sep } from "node:path";
|
|
60
|
-
import {
|
|
60
|
+
import { pathToFileURL } from "node:url";
|
|
61
61
|
|
|
62
62
|
// Re-exported, not re-declared — see the note in `lib/paper-config.mjs`. This file is not a hook
|
|
63
63
|
// and never needed its own copy.
|
|
@@ -98,20 +98,6 @@ export function consumerRoot({ env = process.env, cwd = process.cwd() } = {}) {
|
|
|
98
98
|
return env.CLAUDE_PROJECT_DIR || cwd;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
/**
|
|
102
|
-
* The directory of venue TeX files THIS PACKAGE ships — `paper-guards.tex` and each venue's
|
|
103
|
-
* `<venue>.tex` — which a paper's preamble `\input`s and `paperlint build` puts on `TEXINPUTS`.
|
|
104
|
-
*
|
|
105
|
-
* Resolved from this file's own location, never from the caller's cwd: the directory travels
|
|
106
|
-
* with the package, wherever the package is installed (own checkout, `node_modules`, a plugin
|
|
107
|
-
* cache). `import.meta.url` is the realpath, so a symlinked skills directory changes nothing.
|
|
108
|
-
*/
|
|
109
|
-
export function packageVenuesDir() {
|
|
110
|
-
return fileURLToPath(
|
|
111
|
-
new URL("../../submit-paper/references/venues", import.meta.url),
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
101
|
/**
|
|
116
102
|
* The package's npm name — the directory it lands in under `node_modules`, and the name Node
|
|
117
103
|
* resolves it by. Every path into the installed package is built from this, never re-spelled;
|
|
@@ -4,7 +4,7 @@ description: Compile a LaTeX paper (ACM/IEEE/arXiv) to PDF and render its pages
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, SendUserFile]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:5e2ee8850c098375 compiled from skills/render-paper/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# render-paper — .tex → PDF → readable page PNGs
|
|
10
10
|
|
|
@@ -34,7 +34,7 @@ That list was replaced on 2026-08-03; nothing about the toolchain below changed.
|
|
|
34
34
|
by `kpsewhich` against the files each profile names. The last line it prints is `bin: <dir>` —
|
|
35
35
|
put that directory first on PATH before calling `pdflatex` or `check-render.sh` by hand.
|
|
36
36
|
|
|
37
|
-
🔴 The package list lives in the venue profiles (`
|
|
37
|
+
🔴 The package list lives in the venue profiles (`presets/*.jsonc` in the package, the
|
|
38
38
|
`tex` block; `tex-base.jsonc` for every paper) and nowhere else. A new venue that needs a new
|
|
39
39
|
package gets it there — never in a script, never here. A TeX Live without `libertine` builds an
|
|
40
40
|
acmart paper GREEN in Computer Modern; that is why the files are checked, not the exit code.
|
|
@@ -40,7 +40,7 @@ That list was replaced on 2026-08-03; nothing about the toolchain below changed.
|
|
|
40
40
|
by \`kpsewhich\` against the files each profile names. The last line it prints is \`bin: <dir>\` —
|
|
41
41
|
put that directory first on PATH before calling \`pdflatex\` or \`check-render.sh\` by hand.
|
|
42
42
|
|
|
43
|
-
🔴 The package list lives in the venue profiles (\`
|
|
43
|
+
🔴 The package list lives in the venue profiles (\`presets/*.jsonc\` in the package, the
|
|
44
44
|
\`tex\` block; \`tex-base.jsonc\` for every paper) and nowhere else. A new venue that needs a new
|
|
45
45
|
package gets it there — never in a script, never here. A TeX Live without \`libertine\` builds an
|
|
46
46
|
acmart paper GREEN in Computer Modern; that is why the files are checked, not the exit code.
|
|
@@ -25,75 +25,28 @@ set -uo pipefail
|
|
|
25
25
|
# check — that is, the defect the guard was set up for comes back together with a green report.
|
|
26
26
|
# A broken `import` in JS fails loudly; here we have to fail on our own.
|
|
27
27
|
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# The order "the declared package → a sibling inside the package → the consumer's directory" is
|
|
31
|
-
# deliberate: the package must win silently. It only gets loud when there is NOT A SINGLE ONE.
|
|
32
|
-
# 🔴 THE SCRIPT'S OWN DIRECTORY — TAKEN ABSOLUTE AND BEFORE ANY `cd` (12.09.2026, evening).
|
|
33
|
-
# Found by a RED HARNESS, `gates.harness.mjs`, not by proofreading, and it is my own regression from
|
|
34
|
-
# the same day: below, the script does `cd "$DIR"` into the paper directory, and the venues resolve
|
|
35
|
-
# stood AFTER that `cd`. So `git rev-parse --show-toplevel` was already running in the fixture's
|
|
36
|
-
# temporary directory, the root was not found, and the build honestly stopped — on a test that had
|
|
37
|
-
# been passing until then.
|
|
28
|
+
# TWO candidates, in this order: the package by its NAME, from the current directory (a consumer's
|
|
29
|
+
# installed paperlint wins), then the package THIS script ships in. Loud only when neither holds it.
|
|
38
30
|
#
|
|
39
|
-
#
|
|
40
|
-
# the
|
|
41
|
-
# of an old one; still, a test that was green yesterday and is red today is mine to fix.
|
|
42
|
-
#
|
|
43
|
-
# The neighbouring `build.sh` already carries this same lesson in a comment ("The script directory is
|
|
44
|
-
# taken ONCE and ABSOLUTE, before cd") — CI failed on it there on 30.08. A second instance of the same
|
|
45
|
-
# class in a file right next door.
|
|
31
|
+
# 🔴 THE SCRIPT'S OWN DIRECTORY IS TAKEN ABSOLUTE AND BEFORE ANY `cd`: below, the script `cd`s into
|
|
32
|
+
# the paper directory, and a lookup after that would start from the paper, not from here.
|
|
46
33
|
SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
47
34
|
|
|
48
35
|
resolve_venues() {
|
|
49
|
-
local
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
# depends neither on the current directory nor on whether the caller sits inside a git tree.
|
|
53
|
-
# ⚠️ THE PARENTHESES ARE MANDATORY, and the harness caught this a minute after I wrote the line
|
|
54
|
-
# without them. In bash `A || B && C` parses as `(A || B) && C`, so on a SUCCESSFUL `git rev-parse`
|
|
55
|
-
# the `pwd` ran as well, and the substitution returned TWO lines — the repository root plus the
|
|
56
|
-
# current directory. By hand from a directory outside git this did not reproduce: there git failed,
|
|
57
|
-
# and only one branch ran.
|
|
58
|
-
# ⚠️ THE LAST RUNG SEARCHES, IT DOES NOT COUNT LEVELS. `cd "$SELF_DIR/../../.."` used to stand here
|
|
59
|
-
# — correct for `.claude/skills/render-paper/` (three levels to the root) and silently wrong after
|
|
60
|
-
# the skill moved into the package, where it lies in `skills/render-paper/` (two): the climb
|
|
61
|
-
# overshot ABOVE the repository, and rung 2 looked for venues in someone else's directory. Climbing
|
|
62
|
-
# up to the directory that holds `.claude` does not depend on the depth.
|
|
63
|
-
local probe="$SELF_DIR" fallback=""
|
|
64
|
-
while [ "$probe" != "/" ]; do
|
|
65
|
-
if [ -d "$probe/.claude" ]; then fallback="$probe"; break; fi
|
|
66
|
-
probe=$(dirname "$probe")
|
|
67
|
-
done
|
|
68
|
-
root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || printf '%s' "$fallback")}"
|
|
69
|
-
|
|
70
|
-
# 1) the package, if it already carries venues/ (after step 5)
|
|
71
|
-
pkg=$(node -e 'try{process.stdout.write(require.resolve("paperlint/venues/paper-guards.tex"))}catch{}' 2>/dev/null || true)
|
|
36
|
+
local pkg own
|
|
37
|
+
# 1) the package by its name. `node -e` resolves from the current directory.
|
|
38
|
+
pkg=$(node -e 'try{process.stdout.write(require.resolve("paperlint/presets/tex/paper-guards.tex"))}catch{}' 2>/dev/null || true)
|
|
72
39
|
if [ -n "$pkg" ] && [ -f "$pkg" ]; then
|
|
73
40
|
dirname "$pkg"
|
|
74
41
|
return 0
|
|
75
42
|
fi
|
|
76
43
|
|
|
77
|
-
# 2)
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# leaves the logical path)
|
|
84
|
-
# Rung 1 depends on cwd (`node -e` resolves from the current directory), rung 3 depends on the
|
|
85
|
-
# consumer's root having been found at all. This one depends on neither — exactly the anchor whose
|
|
86
|
-
# absence had `gates.harness.mjs` red on 12.09.
|
|
87
|
-
local sibling="$SELF_DIR/../submit-paper/references/venues"
|
|
88
|
-
if [ -f "$sibling/paper-guards.tex" ]; then
|
|
89
|
-
(cd "$sibling" && pwd)
|
|
90
|
-
return 0
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
# 3) the skill directory in the consumer — for a consumer that mounted the skills differently
|
|
94
|
-
local local_dir="$root/.claude/skills/submit-paper/references/venues"
|
|
95
|
-
if [ -f "$local_dir/paper-guards.tex" ]; then
|
|
96
|
-
printf '%s\n' "$local_dir"
|
|
44
|
+
# 2) the package this script is part of: `skills/render-paper/` → `presets/tex/`. `cd -P` follows
|
|
45
|
+
# the symlinks a consumer's `.claude/skills/render-paper` is made of, so `../..` is the PACKAGE's
|
|
46
|
+
# root, not the consumer's `.claude/`. Depends neither on the cwd nor on a git tree.
|
|
47
|
+
own="$(cd -P "$SELF_DIR" && pwd)/../../presets/tex"
|
|
48
|
+
if [ -f "$own/paper-guards.tex" ]; then
|
|
49
|
+
(cd "$own" && pwd)
|
|
97
50
|
return 0
|
|
98
51
|
fi
|
|
99
52
|
|
|
@@ -107,8 +60,8 @@ if [ "${1:-}" = "--print-venues" ]; then
|
|
|
107
60
|
printf '%s\n' "$VD"
|
|
108
61
|
exit 0
|
|
109
62
|
fi
|
|
110
|
-
echo "✗ paper-guards.tex not found: not under the package name paperlint,
|
|
111
|
-
echo "
|
|
63
|
+
echo "✗ paper-guards.tex not found: not under the package name paperlint (paperlint/presets/tex)," >&2
|
|
64
|
+
echo " not in the package this script ships in (../../presets/tex from it)." >&2
|
|
112
65
|
echo " Building the paper without it is not allowed: \\input{paper-guards} will not fail, it will vanish silently," >&2
|
|
113
66
|
echo " and the PDF will be built WITHOUT the check for dangling \\ref and \\cite." >&2
|
|
114
67
|
exit 2
|
|
@@ -135,7 +88,7 @@ if [ "${NO_COMPILE:-0}" != "1" ] && [ -f "$BASE.tex" ]; then
|
|
|
135
88
|
# Two passes: overfull boxes surface on every pass; a second pass settles refs
|
|
136
89
|
# so Reference/Citation-undefined warnings are accurate.
|
|
137
90
|
# 🔴 TEXINPUTS is mandatory, and that was found by a crash on 26.08. Papers pull in their venue's
|
|
138
|
-
# numbers via `\input{<venue>}`, and the file lies in
|
|
91
|
+
# numbers via `\input{<venue>}`, and the file lies in the package's `presets/tex/`.
|
|
139
92
|
# Without that path the build fails with `File \`agenticdev.tex' not found` — and the `|| true`
|
|
140
93
|
# below swallows it, after which the script reads a log of UNKNOWN origin and reports on it. That
|
|
141
94
|
# is, compiling this paper here silently did not work, and it only became visible once a pass
|
|
@@ -68,34 +68,11 @@ await checkSkill("render-paper");
|
|
|
68
68
|
const run = (args, opts = {}) =>
|
|
69
69
|
spawnSync("bash", [CHECK, ...args], { encoding: "utf8", ...opts });
|
|
70
70
|
|
|
71
|
-
// First half — on
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// 🔴 THERE USED TO BE AN UNCONDITIONAL `assert.equal(status, 0)` HERE, AND THE MOVE BROKE IT —
|
|
75
|
-
// for a real reason, not by accident. It relied on the SURROUNDING tree: while the skill lived
|
|
76
|
-
// at the consumer, the second rung of the ladder won
|
|
77
|
-
// (`.claude/skills/submit-paper/references/venues/`). The package checkout has neither that nor
|
|
78
|
-
// the first rung — `venues/` arrives together with `submit-paper`, which hasn't moved yet. So
|
|
79
|
-
// the assertion wasn't checking resolution, it was checking for the presence of an unrelated
|
|
80
|
-
// directory nearby.
|
|
81
|
-
//
|
|
82
|
-
// The form now is: resolution must succeed IF AND ONLY IF at least one candidate exists on
|
|
83
|
-
// disk. This is still a real assertion — it fails if a candidate exists and the script didn't
|
|
84
|
-
// find it — but it stops requiring the existence of something this repository doesn't have yet.
|
|
85
|
-
// The absence of both candidates gets PRINTED, not swallowed: "the check found nothing to
|
|
86
|
-
// check" and "the check passed" have to look different.
|
|
71
|
+
// First half — on this tree, resolution must succeed IF AND ONLY IF a candidate exists on disk:
|
|
72
|
+
// the package's own `presets/tex/`. The absence of it is PRINTED, not swallowed: "the check found
|
|
73
|
+
// nothing to check" and "the check passed" have to look different.
|
|
87
74
|
const candidates = [
|
|
88
|
-
|
|
89
|
-
join(HERE, "..", "..", "venues", "paper-guards.tex"),
|
|
90
|
-
// rung 2 — the skill directory at the consumer
|
|
91
|
-
join(
|
|
92
|
-
HERE,
|
|
93
|
-
"..",
|
|
94
|
-
"submit-paper",
|
|
95
|
-
"references",
|
|
96
|
-
"venues",
|
|
97
|
-
"paper-guards.tex",
|
|
98
|
-
),
|
|
75
|
+
join(HERE, "..", "..", "presets", "tex", "paper-guards.tex"),
|
|
99
76
|
].filter((c) => existsSync(c));
|
|
100
77
|
|
|
101
78
|
const ok = run(["--print-venues"]);
|
|
@@ -108,9 +85,8 @@ await checkSkill("render-paper");
|
|
|
108
85
|
`silent success this whole block was written to catch.`,
|
|
109
86
|
);
|
|
110
87
|
console.log(
|
|
111
|
-
` --print-venues: NOT VERIFIED on the live tree — paper-guards.tex is absent from
|
|
112
|
-
`
|
|
113
|
-
`\`submit-paper\`; until then only the fixtures below hold this half.`,
|
|
88
|
+
` --print-venues: NOT VERIFIED on the live tree — paper-guards.tex is absent from the ` +
|
|
89
|
+
`package's presets/tex/; only the fixtures below hold this half.`,
|
|
114
90
|
);
|
|
115
91
|
} else {
|
|
116
92
|
assert.equal(
|
|
@@ -159,18 +135,9 @@ await checkSkill("render-paper");
|
|
|
159
135
|
// When the file is NOWHERE, the script must exit with code 2 and say why. A silent success here
|
|
160
136
|
// is exactly the defect the whole ladder was written for.
|
|
161
137
|
//
|
|
162
|
-
// 🔴 RUN AGAINST A COPY OF THE SCRIPT
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
// the root), and an empty directory found nothing at either. Now the script has a rung anchored
|
|
166
|
-
// to ITS OWN directory (`$SELF_DIR/../submit-paper/references/venues`) — added for
|
|
167
|
-
// cwd-independence — so as long as the script sits inside the package, venues next to it exist
|
|
168
|
-
// ALWAYS, and "nowhere" can no longer be reached through the environment. The only honest way to
|
|
169
|
-
// demonstrate that state is to move the script itself out of the package. The property being
|
|
170
|
-
// checked is otherwise identical, word for word: nothing resolved ⇒ exit 2 + the name of the
|
|
171
|
-
// missing file. ⚠️ What this form no longer checks: that an empty consumer root won't make the
|
|
172
|
-
// script lie — and it shouldn't, because such a root is now legitimately shadowed by the copy in
|
|
173
|
-
// the package.
|
|
138
|
+
// 🔴 RUN AGAINST A COPY OF THE SCRIPT. Inside the package its second rung (the package it ships
|
|
139
|
+
// in) always holds the file, so "nowhere" is reachable only by moving the script out of the
|
|
140
|
+
// package: nothing resolved ⇒ exit 2 + the name of the missing file.
|
|
174
141
|
const empty = realpathSync(mkdtempSync(join(tmpdir(), "venues-none-")));
|
|
175
142
|
const lonely = join(empty, "check-render.sh");
|
|
176
143
|
copyFileSync(CHECK, lonely);
|
|
@@ -190,30 +157,21 @@ await checkSkill("render-paper");
|
|
|
190
157
|
"the failure message must name the missing file",
|
|
191
158
|
);
|
|
192
159
|
|
|
193
|
-
// Third half — the ORDER of the rungs.
|
|
194
|
-
//
|
|
195
|
-
//
|
|
160
|
+
// Third half — the ORDER of the rungs. The package the consumer installed, found by its name,
|
|
161
|
+
// must win over the copy this script ships in; otherwise a consumer silently builds with a stale
|
|
162
|
+
// copy. Plant a package by name beside the real script and see which one is chosen.
|
|
196
163
|
const both = realpathSync(mkdtempSync(join(tmpdir(), "venues-both-")));
|
|
197
|
-
const pkgVenues = join(both, "node_modules", "paperlint", "
|
|
198
|
-
|
|
199
|
-
both,
|
|
200
|
-
".claude",
|
|
201
|
-
"skills",
|
|
202
|
-
"submit-paper",
|
|
203
|
-
"references",
|
|
204
|
-
"venues",
|
|
205
|
-
);
|
|
206
|
-
for (const d of [pkgVenues, skillVenues]) mkdirSync(d, { recursive: true });
|
|
164
|
+
const pkgVenues = join(both, "node_modules", "paperlint", "presets", "tex");
|
|
165
|
+
mkdirSync(pkgVenues, { recursive: true });
|
|
207
166
|
writeFileSync(
|
|
208
167
|
join(both, "node_modules", "paperlint", "package.json"),
|
|
209
168
|
JSON.stringify({
|
|
210
169
|
name: "paperlint",
|
|
211
170
|
version: "0.0.0",
|
|
212
|
-
exports: { "
|
|
171
|
+
exports: { "./*": "./*" },
|
|
213
172
|
}),
|
|
214
173
|
);
|
|
215
174
|
writeFileSync(join(pkgVenues, "paper-guards.tex"), "% from package\n");
|
|
216
|
-
writeFileSync(join(skillVenues, "paper-guards.tex"), "% from skill dir\n");
|
|
217
175
|
const race = run(["--print-venues"], {
|
|
218
176
|
cwd: both,
|
|
219
177
|
env: { ...process.env, CLAUDE_PROJECT_DIR: both },
|
|
@@ -226,7 +184,7 @@ await checkSkill("render-paper");
|
|
|
226
184
|
assert.equal(
|
|
227
185
|
race.stdout.trim(),
|
|
228
186
|
pkgVenues,
|
|
229
|
-
"with both candidates present, the
|
|
187
|
+
"with both candidates present, the INSTALLED package wins, not the copy the script ships in",
|
|
230
188
|
);
|
|
231
189
|
}
|
|
232
190
|
|
|
@@ -276,7 +234,7 @@ await checkSkill("render-paper");
|
|
|
276
234
|
);
|
|
277
235
|
assert.match(
|
|
278
236
|
build,
|
|
279
|
-
/require\.resolve\("paperlint\/
|
|
237
|
+
/require\.resolve\("paperlint\/presets\/tex\/paper-guards\.tex"\)/,
|
|
280
238
|
`${where}: the ladder's first rung (the package) must remain`,
|
|
281
239
|
);
|
|
282
240
|
assert.match(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `adapters/banal/index.ts` — banal as the two ports the app sees. What `run.test.ts` checks in
|
|
3
3
|
* banal's own terms is checked here as the domain receives it: a `Geometry` whose provenance names
|
|
4
4
|
* banal and the rule that found it, a reason that is one line of text, a `Ready` that says what was
|
|
5
|
-
* verified. In-memory ports throughout; the real banal is `test/e2e/banal.ts`.
|
|
5
|
+
* verified. In-memory ports throughout; the real banal is `test/e2e/tex/banal.e2e.ts`.
|
|
6
6
|
*/
|
|
7
7
|
import assert from "node:assert/strict";
|
|
8
8
|
import { test } from "vitest";
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (`Error: Failed to run pdftohtml`, measured); told a version below 0.85 it applies a larger
|
|
8
8
|
* correction and every font size moves. So `$PDFTOHTML` (banal's own override, line 166) points at a
|
|
9
9
|
* stub that answers `-v` with `XML_DIALECT.version` — the pdftohtml whose XML `xml.ts` writes — and
|
|
10
|
-
* refuses anything else. `test/e2e/banal.ts` shows both failures with the real banal.
|
|
10
|
+
* refuses anything else. `test/e2e/tex/banal.e2e.ts` shows both failures with the real banal.
|
|
11
11
|
*/
|
|
12
12
|
import type { Opaque } from "ts-essentials";
|
|
13
13
|
import type { AbsolutePath } from "../../domain/paths.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `output.ts` — every way a banal run can end, as a table of `ProcessExit` → `Result`, and the
|
|
3
|
-
* measurement → geometry projection. Pure: no perl, no disk. The real banal is `test/e2e/banal.ts`.
|
|
3
|
+
* measurement → geometry projection. Pure: no perl, no disk. The real banal is `test/e2e/tex/banal.e2e.ts`.
|
|
4
4
|
*/
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { test } from "vitest";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `adapters/banal/run.ts` — running and installing banal, on in-memory ports: which `Command` it builds, what it stages,
|
|
3
3
|
* what it downloads and writes, and what it concludes. No perl, no disk, no network: the adapters
|
|
4
|
-
* record, and each assertion reads the record. Real perl and the real banal are `test/e2e/banal.ts`;
|
|
4
|
+
* record, and each assertion reads the record. Real perl and the real banal are `test/e2e/tex/banal.e2e.ts`;
|
|
5
5
|
* the adapters' own behaviour is `src/adapters/node/*.test.ts`.
|
|
6
6
|
*/
|
|
7
7
|
import assert from "node:assert/strict";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Every page here is written by hand. The real-PDF half — pdf.js boxes of a committed fixture,
|
|
6
6
|
* written as XML, measured by the real banal and compared with banal on real pdftohtml — is
|
|
7
|
-
* `test/e2e/banal.ts`, which runs where `paperlint toolchain` has installed banal.
|
|
7
|
+
* `test/e2e/tex/banal.e2e.ts`, which runs where `paperlint toolchain` has installed banal.
|
|
8
8
|
*/
|
|
9
9
|
import assert from "node:assert/strict";
|
|
10
10
|
import { test } from "vitest";
|