paperlint 3.1.1 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +18 -97
- package/CONTRIBUTING.md +7 -21
- package/README.md +11 -7
- package/dist/adapters/references/clock.io.d.ts +5 -0
- package/dist/adapters/references/clock.io.d.ts.map +1 -0
- package/dist/adapters/references/clock.io.js +7 -0
- package/dist/adapters/references/clock.io.js.map +1 -0
- package/dist/adapters/references/index.d.ts +24 -2
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +125 -15
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/build.d.ts +4 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +10 -3
- package/dist/build.js.map +1 -1
- package/dist/domain/lookup-cache.d.ts +75 -0
- package/dist/domain/lookup-cache.d.ts.map +1 -0
- package/dist/domain/lookup-cache.js +137 -0
- package/dist/domain/lookup-cache.js.map +1 -0
- package/dist/ports/check-references.d.ts +12 -2
- package/dist/ports/check-references.d.ts.map +1 -1
- package/dist/ports/check-references.js +0 -5
- package/dist/ports/check-references.js.map +1 -1
- package/dist/references.d.ts +11 -3
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +63 -15
- package/dist/references.js.map +1 -1
- package/docs/prior-art/README.md +11 -10
- package/docs/prior-art/citation-checkers.md +174 -0
- package/docs/references.md +126 -0
- package/docs/testing.md +118 -0
- package/docs/toolchain.md +6 -4
- package/package.json +1 -1
- package/skills/paper-pipeline/references/artifact-checklist.md +7 -7
- package/skills/render-paper/checkers-requirements.txt +30 -19
- package/skills/render-paper/ensure-checkers.sh +20 -4
- package/skills/verify-citations/SKILL.md +26 -10
- package/skills/verify-citations/SKILL.md.spec.ts +25 -9
- package/skills/verify-citations/scripts/__snapshots__/verify-cites.net.test.mjs.snap +28 -10
- package/skills/verify-citations/scripts/bib-authors.mjs +5 -6
- package/skills/verify-citations/scripts/verify-cites.mjs +296 -141
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +356 -20
- package/skills/verify-citations/scripts/verify-cites.test.mjs +18 -2
- package/src/adapters/references/clock.io.ts +8 -0
- package/src/adapters/references/index.test.ts +341 -9
- package/src/adapters/references/index.ts +198 -29
- package/src/build.ts +10 -3
- package/src/domain/lookup-cache.test.ts +178 -0
- package/src/domain/lookup-cache.ts +202 -0
- package/src/ports/check-references.ts +16 -2
- package/src/references.test.ts +160 -10
- package/src/references.ts +93 -17
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Crossref answer. The adapter (`adapters/references/`) runs the package's two checkers; a test
|
|
4
4
|
* passes a function.
|
|
5
5
|
*/
|
|
6
|
+
import type { LookupCache } from "../domain/lookup-cache.ts";
|
|
6
7
|
|
|
7
8
|
/** What one bibliography entry came to. */
|
|
8
9
|
export interface EntryVerdict {
|
|
@@ -24,5 +25,18 @@ export type ReferencesCheck =
|
|
|
24
25
|
/** Nothing could be asked — no network, every service down. Never a pass. */
|
|
25
26
|
| { readonly kind: "not-checked"; readonly why: string };
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
-
export
|
|
28
|
+
/** A check's verdicts, and the lookup cache grown by whatever it had to fetch. */
|
|
29
|
+
export interface ReferencesRun {
|
|
30
|
+
readonly check: ReferencesCheck;
|
|
31
|
+
/** The cache passed in, plus every new successful answer; the same object when nothing was fetched. */
|
|
32
|
+
readonly cache: LookupCache;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The bibliography's text (BibTeX) and the paper's lookup cache → the verdicts, derived afresh from
|
|
37
|
+
* cached and newly fetched answers. Never throws: a failure is `not-checked`.
|
|
38
|
+
*/
|
|
39
|
+
export type CheckReferences = (
|
|
40
|
+
bib: string,
|
|
41
|
+
cache: LookupCache,
|
|
42
|
+
) => Promise<ReferencesRun>;
|
package/src/references.test.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { describe, expect, it } from "vitest";
|
|
|
11
11
|
import { ESLint } from "eslint";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
14
|
+
import { ok } from "./domain/result.ts";
|
|
14
15
|
import type { AbsolutePath } from "./domain/paths.ts";
|
|
15
16
|
import type {
|
|
16
17
|
CheckReferences,
|
|
@@ -20,9 +21,16 @@ import { referencesStep } from "./build.ts";
|
|
|
20
21
|
import {
|
|
21
22
|
bibHash,
|
|
22
23
|
bibliographyOf,
|
|
24
|
+
lookupCachePath,
|
|
23
25
|
readReferences,
|
|
24
26
|
referencesPath,
|
|
25
27
|
} from "./references.ts";
|
|
28
|
+
import {
|
|
29
|
+
parseLookupCache,
|
|
30
|
+
serializeLookupCache,
|
|
31
|
+
type CachedResponse,
|
|
32
|
+
type LookupCache,
|
|
33
|
+
} from "./domain/lookup-cache.ts";
|
|
26
34
|
import { referenceRules, REFERENCE_RULE_LEVELS } from "./reference-rules.ts";
|
|
27
35
|
// @ts-expect-error — an ESLint language in .mjs, it has no types
|
|
28
36
|
import { texLanguage } from "../eslint-rules/latex-language.mjs";
|
|
@@ -55,22 +63,30 @@ const verdict = (
|
|
|
55
63
|
|
|
56
64
|
const checker =
|
|
57
65
|
(entries: readonly EntryVerdict[]): CheckReferences =>
|
|
58
|
-
async () => ({ kind: "checked", entries });
|
|
59
|
-
const offline: CheckReferences = async () => ({
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
async (_bib, cache) => ({ check: { kind: "checked", entries }, cache });
|
|
67
|
+
const offline: CheckReferences = async (_bib, cache) => ({
|
|
68
|
+
check: {
|
|
69
|
+
kind: "not-checked",
|
|
70
|
+
why: "the citation services cannot be reached (fetch failed)",
|
|
71
|
+
},
|
|
72
|
+
cache,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/** The step's context over `files`, with every port it does not use stubbed. */
|
|
76
|
+
const ctx = (files: ReturnType<typeof memoryFiles>) => ({
|
|
77
|
+
paperDir: PAPER,
|
|
78
|
+
env: {},
|
|
79
|
+
run: (() => ({})) as never,
|
|
80
|
+
readPdf: (() => null) as never,
|
|
81
|
+
measure: (() => null) as never,
|
|
82
|
+
files,
|
|
62
83
|
});
|
|
63
84
|
|
|
64
85
|
/** Run the build step on a paper held in memory; returns the files and the step's outcome. */
|
|
65
86
|
async function build(tex: string, check: CheckReferences) {
|
|
66
87
|
const files = memoryFiles({ [`${PAPER}/paper.tex`]: tex });
|
|
67
88
|
const out = await referencesStep.run({
|
|
68
|
-
|
|
69
|
-
env: {},
|
|
70
|
-
run: (() => ({})) as never,
|
|
71
|
-
readPdf: (() => null) as never,
|
|
72
|
-
measure: (() => null) as never,
|
|
73
|
-
files,
|
|
89
|
+
...ctx(files),
|
|
74
90
|
checkReferences: check,
|
|
75
91
|
});
|
|
76
92
|
return { files, out };
|
|
@@ -144,6 +160,108 @@ describe("the references build step", () => {
|
|
|
144
160
|
});
|
|
145
161
|
});
|
|
146
162
|
|
|
163
|
+
const ANSWER: CachedResponse = {
|
|
164
|
+
fetched: "2026-09-27",
|
|
165
|
+
response: { db: "crossref", transport: "ok", query: "doi" },
|
|
166
|
+
};
|
|
167
|
+
/** A checker that fetches `key` unless the cache has it, recording the cache it was handed. */
|
|
168
|
+
function fetching(key: string) {
|
|
169
|
+
const seen: LookupCache[] = [];
|
|
170
|
+
const check: CheckReferences = async (_bib, cache) => {
|
|
171
|
+
seen.push(cache);
|
|
172
|
+
return {
|
|
173
|
+
check: { kind: "checked", entries: [verdict("schick2023")] },
|
|
174
|
+
cache: cache.citations.has(key)
|
|
175
|
+
? cache
|
|
176
|
+
: {
|
|
177
|
+
citations: new Map([...cache.citations, [key, ANSWER]]),
|
|
178
|
+
dblp: cache.dblp,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
return { check, seen };
|
|
183
|
+
}
|
|
184
|
+
const cacheText = (files: ReturnType<typeof memoryFiles>): string | null => {
|
|
185
|
+
const b = files.readBytes(lookupCachePath(PAPER) as AbsolutePath);
|
|
186
|
+
return b === null ? null : new TextDecoder().decode(b);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
describe("the lookup cache — <paper>/repro/references-cache.json", () => {
|
|
190
|
+
it("a build that fetched writes the cache; the next build is handed it and writes nothing", async () => {
|
|
191
|
+
const f = fetching("crossref:doi:10.1/x");
|
|
192
|
+
const { files, out } = await build(TEX(ENTRIES), f.check);
|
|
193
|
+
expect(out).toEqual({
|
|
194
|
+
ok: true,
|
|
195
|
+
note: "references: 1 checked, 0 failing → _build/references.json; 1 new answer → repro/references-cache.json",
|
|
196
|
+
});
|
|
197
|
+
const written = cacheText(files);
|
|
198
|
+
expect(written).toBe(
|
|
199
|
+
serializeLookupCache({
|
|
200
|
+
citations: new Map([["crossref:doi:10.1/x", ANSWER]]),
|
|
201
|
+
dblp: new Map(),
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
const again = await referencesStep.run({
|
|
205
|
+
...ctx(files),
|
|
206
|
+
checkReferences: f.check,
|
|
207
|
+
});
|
|
208
|
+
expect(again).toEqual({
|
|
209
|
+
ok: true,
|
|
210
|
+
note: "references: 1 checked, 0 failing → _build/references.json; nothing fetched",
|
|
211
|
+
});
|
|
212
|
+
// The second build was handed exactly what the first one wrote.
|
|
213
|
+
expect(parseLookupCache(written!)).toEqual(ok(f.seen[1]));
|
|
214
|
+
expect(cacheText(files)).toBe(written);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("the lookup cache — counting and refusing", () => {
|
|
219
|
+
it("two new answers are counted as two", async () => {
|
|
220
|
+
const two: CheckReferences = async (_bib, cache) => ({
|
|
221
|
+
check: { kind: "checked", entries: [verdict("schick2023")] },
|
|
222
|
+
cache: {
|
|
223
|
+
citations: new Map([
|
|
224
|
+
["a", ANSWER],
|
|
225
|
+
["b", ANSWER],
|
|
226
|
+
]),
|
|
227
|
+
dblp: cache.dblp,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
const { out } = await build(TEX(ENTRIES), two);
|
|
231
|
+
expect(out).toEqual({
|
|
232
|
+
ok: true,
|
|
233
|
+
note: "references: 1 checked, 0 failing → _build/references.json; 2 new answers → repro/references-cache.json",
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("no answer fetched and no cache on disk: no cache file is created", async () => {
|
|
238
|
+
const { files } = await build(
|
|
239
|
+
TEX(ENTRIES),
|
|
240
|
+
checker([verdict("schick2023")]),
|
|
241
|
+
);
|
|
242
|
+
expect(cacheText(files)).toBeNull();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("🔴 a cache that does not parse is refused BY NAME and left alone — the check is not run over half a cache", async () => {
|
|
246
|
+
const files = memoryFiles({
|
|
247
|
+
[`${PAPER}/paper.tex`]: TEX(ENTRIES),
|
|
248
|
+
[lookupCachePath(PAPER)]: '{"schema": 7}',
|
|
249
|
+
});
|
|
250
|
+
const f = fetching("k");
|
|
251
|
+
const out = await referencesStep.run({
|
|
252
|
+
...ctx(files),
|
|
253
|
+
checkReferences: f.check,
|
|
254
|
+
});
|
|
255
|
+
expect(out).toEqual({
|
|
256
|
+
ok: true,
|
|
257
|
+
note: "references NOT checked — repro/references-cache.json cannot be read (schema 7: this paperlint reads schema 1) — fix it, or delete it to ask every question again; lint will say so",
|
|
258
|
+
});
|
|
259
|
+
expect(f.seen).toEqual([]);
|
|
260
|
+
expect(cacheText(files)).toBe('{"schema": 7}');
|
|
261
|
+
expect(readReferences(files, PAPER)?.status).toBe("not-checked");
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
147
265
|
describe("the reference rules", () => {
|
|
148
266
|
it("one mismatched entry → paper/author-list on that entry's own line", async () => {
|
|
149
267
|
const tex = TEX(ENTRIES);
|
|
@@ -304,3 +422,35 @@ describe("the reference rules over refs.bib, and on other files", () => {
|
|
|
304
422
|
);
|
|
305
423
|
});
|
|
306
424
|
});
|
|
425
|
+
|
|
426
|
+
describe("the lookup cache — refreshed answers", () => {
|
|
427
|
+
// Guards: an answer asked again after MAX_AGE_DAYS keeps its key, so the key count does not change.
|
|
428
|
+
// Counted by size, the refreshed answer was not written, and every later build asked it again.
|
|
429
|
+
it("an answer refreshed under the same key is counted and written", async () => {
|
|
430
|
+
const OLD: CachedResponse = { ...ANSWER, fetched: "2026-08-01" };
|
|
431
|
+
const put =
|
|
432
|
+
(answer: CachedResponse): CheckReferences =>
|
|
433
|
+
async (_bib, cache) => ({
|
|
434
|
+
check: { kind: "checked", entries: [verdict("schick2023")] },
|
|
435
|
+
cache: {
|
|
436
|
+
citations: new Map([...cache.citations, ["a", answer]]),
|
|
437
|
+
dblp: cache.dblp,
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
const { files } = await build(TEX(ENTRIES), put(OLD));
|
|
441
|
+
const again = await referencesStep.run({
|
|
442
|
+
...ctx(files),
|
|
443
|
+
checkReferences: put(ANSWER),
|
|
444
|
+
});
|
|
445
|
+
expect({ again, file: cacheText(files) }).toEqual({
|
|
446
|
+
again: {
|
|
447
|
+
ok: true,
|
|
448
|
+
note: "references: 1 checked, 0 failing → _build/references.json; 1 new answer → repro/references-cache.json",
|
|
449
|
+
},
|
|
450
|
+
file: serializeLookupCache({
|
|
451
|
+
citations: new Map([["a", ANSWER]]),
|
|
452
|
+
dblp: new Map(),
|
|
453
|
+
}),
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
});
|
package/src/references.ts
CHANGED
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
* stale when it changes (`pdf_sha256`); these are about the bibliography and are stale when IT
|
|
18
18
|
* changes (`bib.sha256`). One staleness key per file, the way `pdf/fresh` already works.
|
|
19
19
|
*
|
|
20
|
+
* 🔴 WHAT THE CHECKERS ANSWERED IS KEPT, COMMITTED, IN `<paper>/repro/references-cache.json`
|
|
21
|
+
* (`domain/lookup-cache.ts`). Loaded before the check and saved after it — only when something new
|
|
22
|
+
* was fetched, a refreshed answer included — so a build asks each question at most once every
|
|
23
|
+
* MAX_AGE_DAYS, not once per build (#107: 217 s for 27 references, every build). The verdicts above are NOT cached: they are derived from the cached
|
|
24
|
+
* answers on every build, so a fix to the checkers reaches every paper at once.
|
|
25
|
+
*
|
|
20
26
|
* 🔴 THE STEP NEVER FAILS THE BUILD. A build without network still builds the PDF; the reference
|
|
21
27
|
* step records `not-checked` and the lint rule says so, as a warning. Recording "not checked" as
|
|
22
28
|
* a pass would be the counter that counts what it never looked at.
|
|
@@ -25,10 +31,18 @@ import { join } from "node:path";
|
|
|
25
31
|
import type { AbsolutePath } from "./domain/paths.ts";
|
|
26
32
|
import { sha256Hex } from "./domain/sha256.ts";
|
|
27
33
|
import type { Files } from "./ports/files.ts";
|
|
34
|
+
import {
|
|
35
|
+
EMPTY_LOOKUP_CACHE,
|
|
36
|
+
parseLookupCache,
|
|
37
|
+
serializeLookupCache,
|
|
38
|
+
type LookupCache,
|
|
39
|
+
} from "./domain/lookup-cache.ts";
|
|
40
|
+
import { ok, type Result } from "./domain/result.ts";
|
|
28
41
|
import type {
|
|
29
42
|
CheckReferences,
|
|
30
43
|
EntryVerdict,
|
|
31
44
|
ReferencesCheck,
|
|
45
|
+
ReferencesRun,
|
|
32
46
|
} from "./ports/check-references.ts";
|
|
33
47
|
// @ts-expect-error — an ESLint rule module in .mjs, it has no types
|
|
34
48
|
import { bibRange } from "../eslint-rules/paper-typography.mjs";
|
|
@@ -36,6 +50,12 @@ import { bibRange } from "../eslint-rules/paper-typography.mjs";
|
|
|
36
50
|
export const REFERENCES_SCHEMA = 1;
|
|
37
51
|
export const REFERENCES_FILE = "references.json";
|
|
38
52
|
|
|
53
|
+
/** The lookup cache's place in a paper: committed, beside the paper's other reproduction files. */
|
|
54
|
+
export const LOOKUP_CACHE_FILE = "repro/references-cache.json";
|
|
55
|
+
|
|
56
|
+
export const lookupCachePath = (paperDir: string): string =>
|
|
57
|
+
join(paperDir, LOOKUP_CACHE_FILE);
|
|
58
|
+
|
|
39
59
|
/** Where a paper's reference verdicts live — beside `paper.facts.json`. */
|
|
40
60
|
export const referencesPath = (paperDir: string): string =>
|
|
41
61
|
join(paperDir, "_build", REFERENCES_FILE);
|
|
@@ -106,10 +126,48 @@ export function readReferences(
|
|
|
106
126
|
}
|
|
107
127
|
}
|
|
108
128
|
|
|
129
|
+
/** The paper's lookup cache: empty when there is none, or why the file on disk cannot be read. */
|
|
130
|
+
export function readLookupCache(
|
|
131
|
+
files: Files,
|
|
132
|
+
paperDir: string,
|
|
133
|
+
): Result<LookupCache, string> {
|
|
134
|
+
const raw = text(files, lookupCachePath(paperDir));
|
|
135
|
+
return raw === null ? ok(EMPTY_LOOKUP_CACHE) : parseLookupCache(raw);
|
|
136
|
+
}
|
|
137
|
+
|
|
109
138
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
139
|
+
* How many answers the run fetched: entries of `after` that are not the very object `before` held
|
|
140
|
+
* under that key. The checker keeps every answer it did not fetch as the same object, so this counts
|
|
141
|
+
* both new keys and answers asked again past MAX_AGE_DAYS under an existing key — which a count of
|
|
142
|
+
* keys would miss, leaving the refreshed answer unwritten and asked again on every build.
|
|
143
|
+
*/
|
|
144
|
+
const newAnswers = (before: LookupCache, after: LookupCache): number => {
|
|
145
|
+
const changed = <V>(b: ReadonlyMap<string, V>, a: ReadonlyMap<string, V>) =>
|
|
146
|
+
[...a].filter(([k, v]) => b.get(k) !== v).length;
|
|
147
|
+
return (
|
|
148
|
+
changed(before.citations, after.citations) +
|
|
149
|
+
changed(before.dblp, after.dblp)
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Run the checker; a throw is `not-checked`, with the cache as it was. */
|
|
154
|
+
async function run(
|
|
155
|
+
check: CheckReferences,
|
|
156
|
+
bib: string,
|
|
157
|
+
cache: LookupCache,
|
|
158
|
+
): Promise<ReferencesRun> {
|
|
159
|
+
try {
|
|
160
|
+
return await check(bib, cache);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return { check: { kind: "not-checked", why: (e as Error).message }, cache };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The build step's work: check the bibliography, serving what it can from the paper's lookup
|
|
168
|
+
* cache, and record the verdicts. Returns the one-line note the build prints. A checker that
|
|
169
|
+
* throws, or a cache that does not parse, is recorded as `not-checked` — the step reports, it
|
|
170
|
+
* never refuses. A cache that does not parse is left as it is, and named.
|
|
113
171
|
*/
|
|
114
172
|
export async function recordReferences(
|
|
115
173
|
files: Files,
|
|
@@ -118,21 +176,39 @@ export async function recordReferences(
|
|
|
118
176
|
): Promise<string> {
|
|
119
177
|
const bib = bibliographyOf(files, paperDir);
|
|
120
178
|
if (bib === null) return "no bibliography — nothing to check";
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
179
|
+
const record = (c: ReferencesCheck): ReferencesDocument => {
|
|
180
|
+
const doc = documentOf(bib, c);
|
|
181
|
+
files.writeAtomic(
|
|
182
|
+
at(referencesPath(paperDir)),
|
|
183
|
+
new TextEncoder().encode(`${JSON.stringify(doc, null, 2)}\n`),
|
|
184
|
+
);
|
|
185
|
+
return doc;
|
|
186
|
+
};
|
|
187
|
+
const notChecked = (why: string): string => {
|
|
188
|
+
record({ kind: "not-checked", why });
|
|
189
|
+
return `references NOT checked — ${why}; lint will say so`;
|
|
190
|
+
};
|
|
191
|
+
const cache = readLookupCache(files, paperDir);
|
|
192
|
+
if (!cache.ok)
|
|
193
|
+
return notChecked(
|
|
194
|
+
`${LOOKUP_CACHE_FILE} cannot be read (${cache.error}) — fix it, or delete it to ask every question again`,
|
|
195
|
+
);
|
|
196
|
+
const result = await run(check, bib.text, cache.value);
|
|
197
|
+
if (result.check.kind === "not-checked") return notChecked(result.check.why);
|
|
198
|
+
const doc = record(result.check);
|
|
199
|
+
const fetched = newAnswers(cache.value, result.cache);
|
|
200
|
+
if (fetched > 0)
|
|
201
|
+
files.writeAtomic(
|
|
202
|
+
at(lookupCachePath(paperDir)),
|
|
203
|
+
new TextEncoder().encode(serializeLookupCache(result.cache)),
|
|
204
|
+
);
|
|
134
205
|
const bad = doc.entries.filter(
|
|
135
206
|
(e) => e.exists === "false" || e.authors === "mismatch",
|
|
136
207
|
).length;
|
|
137
|
-
return
|
|
208
|
+
return (
|
|
209
|
+
`references: ${String(doc.entries.length)} checked, ${String(bad)} failing → _build/${REFERENCES_FILE}; ` +
|
|
210
|
+
(fetched > 0
|
|
211
|
+
? `${String(fetched)} new answer${fetched === 1 ? "" : "s"} → ${LOOKUP_CACHE_FILE}`
|
|
212
|
+
: "nothing fetched")
|
|
213
|
+
);
|
|
138
214
|
}
|