paperlint 2.0.1 → 3.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.
- package/CLAUDE.md +6 -1
- package/CONTRIBUTING.md +21 -0
- package/README.md +231 -180
- package/action.yml +4 -6
- package/dist/adapters/references/index.d.ts +13 -0
- package/dist/adapters/references/index.d.ts.map +1 -0
- package/dist/adapters/references/index.js +55 -0
- package/dist/adapters/references/index.js.map +1 -0
- package/dist/adapters/references/reach.io.d.ts +3 -0
- package/dist/adapters/references/reach.io.d.ts.map +1 -0
- package/dist/adapters/references/reach.io.js +14 -0
- package/dist/adapters/references/reach.io.js.map +1 -0
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +5 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +16 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +40 -5
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +37 -20
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +377 -251
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +37 -52
- package/dist/doctor.js.map +1 -1
- package/dist/facts-file.d.ts +33 -9
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +117 -17
- package/dist/facts-file.js.map +1 -1
- package/dist/hooks-settings.d.ts +0 -21
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +17 -135
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +40 -19
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +144 -109
- package/dist/init.js.map +1 -1
- package/dist/link-skills.d.ts +1 -1
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +11 -29
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts +2 -2
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +6 -2
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +63 -0
- package/dist/paper-files.d.ts.map +1 -0
- package/dist/paper-files.js +69 -0
- package/dist/paper-files.js.map +1 -0
- package/dist/paper-settings.d.ts +40 -0
- package/dist/paper-settings.d.ts.map +1 -0
- package/dist/paper-settings.js +144 -0
- package/dist/paper-settings.js.map +1 -0
- package/dist/ports/check-references.d.ts +31 -0
- package/dist/ports/check-references.d.ts.map +1 -0
- package/dist/ports/check-references.js +7 -0
- package/dist/ports/check-references.js.map +1 -0
- package/dist/presets.d.ts +84 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +184 -0
- package/dist/presets.js.map +1 -0
- package/dist/reference-rules.d.ts +47 -0
- package/dist/reference-rules.d.ts.map +1 -0
- package/dist/reference-rules.js +156 -0
- package/dist/reference-rules.js.map +1 -0
- package/dist/references.d.ts +36 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +91 -0
- package/dist/references.js.map +1 -0
- package/dist/rules-config.d.ts +11 -2
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +25 -11
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +3 -2
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts +63 -7
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +91 -24
- package/dist/tex-requirements.js.map +1 -1
- package/dist/types.d.ts +20 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts +109 -0
- package/dist/venue-rules.d.ts.map +1 -0
- package/dist/venue-rules.js +389 -0
- package/dist/venue-rules.js.map +1 -0
- package/docs/configuration.md +152 -77
- package/docs/e2e.md +1 -1
- package/docs/install.md +25 -40
- package/docs/optional-rules.md +26 -24
- package/docs/rules.md +181 -15
- package/eslint-rules/bib-reachable-entry.mjs +75 -0
- package/eslint-rules/latex-language.mjs +29 -1
- package/eslint-rules/paper-stages.harness.mjs +1 -117
- package/eslint-rules/paper-stages.mjs +0 -120
- package/eslint-rules/paper-stages.mutations.mjs +0 -76
- package/eslint-rules/paper-typography.mjs +267 -192
- package/eslint-rules/paper-typography.test.mjs +297 -0
- package/eslint-rules/papers.harness.mjs +8 -12
- package/eslint-rules/papers.mjs +17 -27
- package/eslint-rules/pdf-last-page-balance.mjs +23 -5
- package/eslint-rules/review-frontmatter.mjs +118 -0
- package/eslint-rules/review-frontmatter.schema.json +36 -0
- package/eslint-rules/review-frontmatter.test.mjs +118 -0
- package/eslint-rules/sibling-frontmatter.mjs +19 -0
- package/eslint-rules/sibling-frontmatter.schema.json +14 -0
- package/eslint.config.mjs +0 -15
- package/fixtures/build-e2e/acmart/paperlint.json +1 -0
- package/fixtures/real-markdown-paper/baseline.json +6 -7
- package/hooks/hooks.harness.mjs +39 -98
- package/hooks/hooks.mutations.mjs +6 -6
- package/hooks/paper-edit-guard.hook.d.mts +2 -3
- package/hooks/paper-edit-guard.hook.mjs +31 -42
- package/hooks/paper-skills-nudge.hook.mjs +21 -19
- package/hooks/paper-status-gates.hook.mjs +21 -19
- package/hooks/paper-status-gates.sh +8 -13
- package/lib/paper-config.d.mts +15 -18
- package/lib/paper-config.harness.mjs +18 -35
- package/lib/paper-config.mjs +90 -81
- package/lib/paper-config.mutations.mjs +2 -2
- package/lib/skill-checks.mjs +1 -1
- package/lib/skill-trigger-cases.harness.mjs +9 -8
- package/lib/skill-trigger-cases.mjs +7 -15
- package/package.json +1 -1
- package/scripts/eslint-report-guard.mjs +1 -1
- package/scripts/harness-api.frozen.json +0 -3
- package/scripts/mutation-batteries.frozen.json +4 -13
- package/skills/analyze-sibling-paper/SKILL.md +16 -12
- package/skills/analyze-sibling-paper/SKILL.md.spec.ts +15 -11
- package/skills/cold-read-diff/SKILL.md +20 -1
- package/skills/cold-read-diff/SKILL.md.spec.ts +19 -0
- package/skills/find-venue/SKILL.md +5 -1
- package/skills/find-venue/SKILL.md.spec.ts +4 -0
- package/skills/grade-paper-writing/SKILL.md +20 -1
- package/skills/grade-paper-writing/SKILL.md.spec.ts +19 -0
- package/skills/paper-pipeline/PIPELINE-MAP.md +14 -4
- package/skills/paper-pipeline/SKILL.md +4 -3
- package/skills/paper-pipeline/SKILL.md.spec.ts +3 -2
- package/skills/paper-pipeline/scripts/consumer.d.mts +0 -1
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +8 -12
- package/skills/paper-pipeline/scripts/consumer.mjs +19 -33
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +6 -4
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +2 -11
- package/skills/plan-paper-timeline/SKILL.md +2 -2
- package/skills/plan-paper-timeline/SKILL.md.spec.ts +1 -1
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +1 -1
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +2 -2
- package/skills/render-paper/check-render.sh +2 -3
- package/skills/render-paper/extract-pdf-facts.harness.mjs +5 -5
- package/skills/render-paper/extract-pdf-facts.mjs +4 -4
- package/skills/render-paper/render-paper.harness.mjs +2 -2
- package/skills/study-accepted-papers/SKILL.md +2 -1
- package/skills/study-accepted-papers/SKILL.md.spec.ts +1 -0
- package/skills/submit-paper/SKILL.md +9 -1
- package/skills/submit-paper/SKILL.md.spec.ts +8 -0
- package/skills/submit-paper/references/venues/acm-sigconf.jsonc +86 -0
- package/skills/submit-paper/references/venues/agenticdev.jsonc +34 -102
- package/skills/submit-paper/references/venues/aisec.jsonc +21 -82
- package/skills/submit-paper/references/venues/realm.jsonc +45 -43
- package/skills/submit-paper/references/venues/tex-base.jsonc +1 -1
- package/skills/submit-paper/references/venues/venue-profile.schema.json +119 -29
- package/skills/verify-citations/SKILL.md +10 -1
- package/skills/verify-citations/SKILL.md.spec.ts +9 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +48 -26
- package/src/adapters/references/index.ts +102 -0
- package/src/adapters/references/reach.io.ts +12 -0
- package/src/build-engine.ts +10 -2
- package/src/build.harness.mjs +31 -22
- package/src/build.ts +65 -8
- package/src/cli.harness.mjs +151 -294
- package/src/cli.mutations.mjs +35 -44
- package/src/cli.ts +457 -288
- package/src/doctor.harness.mjs +41 -84
- package/src/doctor.mutations.mjs +9 -9
- package/src/doctor.ts +41 -61
- package/src/facts-file.test.ts +47 -7
- package/src/facts-file.ts +145 -24
- package/src/hooks-settings.harness.mjs +7 -92
- package/src/hooks-settings.mutations.mjs +2 -17
- package/src/hooks-settings.ts +18 -162
- package/src/init.test.ts +206 -0
- package/src/init.ts +193 -159
- package/src/link-skills.harness.mjs +1 -38
- package/src/link-skills.mutations.mjs +9 -5
- package/src/link-skills.ts +10 -34
- package/src/lint-fix.test.ts +120 -0
- package/src/new-paper.harness.mjs +9 -6
- package/src/new-paper.test.ts +133 -0
- package/src/new-paper.ts +7 -2
- package/src/paper-files.test.ts +290 -0
- package/src/paper-files.ts +144 -0
- package/src/paper-settings-commands.test.ts +274 -0
- package/src/paper-settings.test.ts +249 -0
- package/src/paper-settings.ts +208 -0
- package/src/ports/check-references.ts +28 -0
- package/src/presets.test.ts +236 -0
- package/src/presets.ts +297 -0
- package/src/reference-rules.ts +229 -0
- package/src/references.test.ts +209 -0
- package/src/references.ts +138 -0
- package/src/rules-config.harness.mjs +24 -8
- package/src/rules-config.ts +30 -13
- package/src/structure.harness.mjs +6 -4
- package/src/structure.ts +3 -2
- package/src/tex-requirements.harness.mjs +11 -17
- package/src/tex-requirements.ts +179 -29
- package/src/types.ts +30 -18
- package/src/venue-rules.test.ts +554 -0
- package/src/venue-rules.ts +571 -0
- package/templates/paper/paperlint.json +4 -0
- package/eslint-rules/doc-fields.harness.mjs +0 -336
- package/eslint-rules/doc-fields.mjs +0 -186
- package/eslint-rules/doc-fields.mutations.mjs +0 -96
- package/eslint-rules/paper-typography.harness.mjs +0 -291
- package/eslint-rules/paper-typography.mutations.mjs +0 -131
- package/eslint-rules/review-findings-cause.harness.mjs +0 -228
- package/eslint-rules/review-findings-cause.mjs +0 -135
- package/eslint-rules/review-findings-cause.mutations.mjs +0 -72
- package/fixtures/build-e2e/acmart/venue.json +0 -1
- package/fixtures/paper-stages/authors-ran/PIPELINE-STATUS.md +0 -16
- package/fixtures/paper-stages/marker-in-prose/PIPELINE-STATUS.md +0 -17
- package/fixtures/review-findings-cause/clean.md +0 -17
- package/fixtures/review-findings-cause/defect.md +0 -14
- package/fixtures/review-findings-cause/old-debt.md +0 -14
- package/fixtures/review-findings-cause/quiet-in-fence.md +0 -16
package/src/facts-file.ts
CHANGED
|
@@ -40,13 +40,16 @@ import { describeFailure, type PdfFacts, type PdfReader } from "./pdf-facts.ts";
|
|
|
40
40
|
import {
|
|
41
41
|
flatGeometry,
|
|
42
42
|
type FactsGeometryFields,
|
|
43
|
+
type FlatGeometry,
|
|
43
44
|
type Geometry,
|
|
44
45
|
} from "./domain/geometry.ts";
|
|
45
|
-
import { sha256Hex } from "./domain/sha256.ts";
|
|
46
|
+
import { parseSha256, sha256Hex, type Sha256 } from "./domain/sha256.ts";
|
|
46
47
|
import type { MeasureGeometry } from "./ports/measure-geometry.ts";
|
|
47
48
|
import type { AbsolutePath } from "./domain/paths.ts";
|
|
48
49
|
import type { Files } from "./ports/files.ts";
|
|
49
50
|
import { err, ok, type Result } from "./domain/result.ts";
|
|
51
|
+
import { paperPreset, paperPresetProblem } from "./presets.ts";
|
|
52
|
+
import { packageVenuesDir } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
50
53
|
|
|
51
54
|
export const FACTS_SCHEMA = 2;
|
|
52
55
|
export const FACTS_DIR = "_build";
|
|
@@ -56,41 +59,42 @@ export const FACTS_FILE = "paper.facts.json";
|
|
|
56
59
|
export const factsPath = (paperDir: string): string =>
|
|
57
60
|
join(paperDir, FACTS_DIR, FACTS_FILE);
|
|
58
61
|
|
|
59
|
-
/** The `venue.json` a paper declares: where it is submitted, as which kind, and where its PDF is. */
|
|
60
|
-
export interface VenueDecl {
|
|
61
|
-
readonly venue: string;
|
|
62
|
-
readonly kind: string | null;
|
|
63
|
-
readonly pdf: string | null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
62
|
/**
|
|
67
63
|
* A path as the caller gave it. The `Files` adapter resolves a relative one against the process's
|
|
68
64
|
* cwd, which is what these callers have always meant; the brand is not a claim this code checked it.
|
|
69
65
|
*/
|
|
70
66
|
const at = (p: string): AbsolutePath => p as AbsolutePath;
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const venue = field("venue");
|
|
79
|
-
return venue ? { venue, kind: field("kind"), pdf: field("pdf") } : null;
|
|
68
|
+
/** What the facts need from a paper's `paperlint.json`: its venue's label, its kind, where its PDF is. */
|
|
69
|
+
export interface VenueDecl {
|
|
70
|
+
/** The venue preset's display label (`agenticdev`), or null when the paper extends none. */
|
|
71
|
+
readonly label: string | null;
|
|
72
|
+
readonly kind: string | null;
|
|
73
|
+
readonly pdf: string | null;
|
|
80
74
|
}
|
|
81
75
|
|
|
82
76
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
77
|
+
* What a paper's `paperlint.json` declares, or null when it has none. THROWS with a one-line reason
|
|
78
|
+
* when the file does not parse or its `extends` does not resolve: the build reports that as its
|
|
79
|
+
* `facts` failure rather than building against no venue.
|
|
85
80
|
*/
|
|
86
81
|
export function declaredVenue(
|
|
87
82
|
files: Files,
|
|
88
83
|
paperDir: string,
|
|
84
|
+
venuesDir: string = packageVenuesDir(),
|
|
89
85
|
): VenueDecl | null {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
const p = paperPreset(paperDir, { files, venuesDir });
|
|
87
|
+
const problem = paperPresetProblem(paperDir, p);
|
|
88
|
+
if (problem !== null) throw new Error(problem);
|
|
89
|
+
if (p.kind === "resolved")
|
|
90
|
+
return {
|
|
91
|
+
label: p.preset.label,
|
|
92
|
+
kind: p.settings.kind,
|
|
93
|
+
pdf: p.settings.pdf,
|
|
94
|
+
};
|
|
95
|
+
return p.kind === "none" && p.settings
|
|
96
|
+
? { label: null, kind: p.settings.kind, pdf: p.settings.pdf }
|
|
97
|
+
: null;
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
// ── the document ────────────────────────────────────────────────────────────────────────
|
|
@@ -184,7 +188,7 @@ export interface MeasureOptions {
|
|
|
184
188
|
readonly kind?: string | null;
|
|
185
189
|
/** The page-geometry measurer (banal, as the composition root wired it). */
|
|
186
190
|
readonly measure: MeasureGeometry;
|
|
187
|
-
/** Where the PDF and `
|
|
191
|
+
/** Where the PDF and `paperlint.json` are read from. */
|
|
188
192
|
readonly files: Files;
|
|
189
193
|
}
|
|
190
194
|
|
|
@@ -215,7 +219,7 @@ export async function measurePaper(
|
|
|
215
219
|
const facts = factsDocument({
|
|
216
220
|
pdf: posix(relative(paperDir, pdf)),
|
|
217
221
|
sha: sha256Hex(bytes),
|
|
218
|
-
venue: o.venue ?? decl?.
|
|
222
|
+
venue: o.venue ?? decl?.label ?? null,
|
|
219
223
|
kind: o.kind ?? decl?.kind ?? null,
|
|
220
224
|
read: r.facts,
|
|
221
225
|
geometry,
|
|
@@ -239,3 +243,120 @@ export function writeFactsFile(
|
|
|
239
243
|
);
|
|
240
244
|
return out;
|
|
241
245
|
}
|
|
246
|
+
|
|
247
|
+
// ── reading ─────────────────────────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/** What a rule judges, read back from the facts file and typed once. */
|
|
250
|
+
export interface ReadFacts {
|
|
251
|
+
/** As the file spells it: relative to the paper directory, or absolute. */
|
|
252
|
+
readonly pdf: string;
|
|
253
|
+
readonly sha: Sha256;
|
|
254
|
+
readonly fonts: readonly FontEntry[];
|
|
255
|
+
/** The geometry columns, or null when no measurer ran (`geometry_source` is null). */
|
|
256
|
+
readonly geometry: FlatGeometry | null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Why a facts file cannot be judged. */
|
|
260
|
+
export type FactsProblem =
|
|
261
|
+
| { readonly kind: "schema"; readonly got: string }
|
|
262
|
+
| { readonly kind: "broken"; readonly why: string };
|
|
263
|
+
|
|
264
|
+
const isRecord = (v: unknown): v is Readonly<Record<string, unknown>> =>
|
|
265
|
+
typeof v === "object" && v !== null && !Array.isArray(v);
|
|
266
|
+
const isNum = (v: unknown): v is number =>
|
|
267
|
+
typeof v === "number" && Number.isFinite(v);
|
|
268
|
+
const numOrNull = (v: unknown): v is number | null => v === null || isNum(v);
|
|
269
|
+
|
|
270
|
+
function fontOf(v: unknown): FontEntry | null {
|
|
271
|
+
if (!isRecord(v)) return null;
|
|
272
|
+
const { name, type, embedded, program } = v;
|
|
273
|
+
return typeof name === "string" &&
|
|
274
|
+
typeof type === "string" &&
|
|
275
|
+
typeof embedded === "boolean" &&
|
|
276
|
+
typeof program === "string"
|
|
277
|
+
? { name, type, embedded, program }
|
|
278
|
+
: null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const MAYBE_NUMBERS = [
|
|
282
|
+
"page_w_in",
|
|
283
|
+
"page_h_in",
|
|
284
|
+
"columns",
|
|
285
|
+
"body_pt",
|
|
286
|
+
"ref_pt",
|
|
287
|
+
] as const;
|
|
288
|
+
const NUMBERS = ["body_pages", "ref_pages", "appendix_pages"] as const;
|
|
289
|
+
|
|
290
|
+
/** The geometry columns when a measurer ran; the shape error otherwise. */
|
|
291
|
+
function geometryOf(
|
|
292
|
+
d: Readonly<Record<string, unknown>>,
|
|
293
|
+
): Result<FlatGeometry | null, string> {
|
|
294
|
+
if (d["geometry_source"] === null) return ok(null);
|
|
295
|
+
if (typeof d["geometry_source"] !== "string")
|
|
296
|
+
return err("`geometry_source` is neither a measurer's name nor null");
|
|
297
|
+
const bad =
|
|
298
|
+
MAYBE_NUMBERS.find((k) => !numOrNull(d[k])) ??
|
|
299
|
+
NUMBERS.find((k) => !isNum(d[k]));
|
|
300
|
+
if (bad) return err(`\`${bad}\` is not a number`);
|
|
301
|
+
const pagesByType = d["pages_by_type"];
|
|
302
|
+
if (!isRecord(pagesByType) || !Object.values(pagesByType).every(isNum))
|
|
303
|
+
return err("`pages_by_type` is not an object of page counts");
|
|
304
|
+
const n = (k: (typeof MAYBE_NUMBERS)[number]) => d[k] as number | null;
|
|
305
|
+
return ok({
|
|
306
|
+
page_w_in: n("page_w_in"),
|
|
307
|
+
page_h_in: n("page_h_in"),
|
|
308
|
+
columns: n("columns"),
|
|
309
|
+
body_pt: n("body_pt"),
|
|
310
|
+
ref_pt: n("ref_pt"),
|
|
311
|
+
body_pages: d["body_pages"] as number,
|
|
312
|
+
ref_pages: d["ref_pages"] as number,
|
|
313
|
+
appendix_pages: d["appendix_pages"] as number,
|
|
314
|
+
pages_by_type: pagesByType as Readonly<Record<string, number>>,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** The PDF the facts describe: its path and its sha256. */
|
|
319
|
+
function artifactOf(
|
|
320
|
+
d: Readonly<Record<string, unknown>>,
|
|
321
|
+
): Result<{ pdf: string; sha: Sha256 }, string> {
|
|
322
|
+
const pdf = d["pdf"];
|
|
323
|
+
if (typeof pdf !== "string" || pdf === "") return err("no `pdf` path");
|
|
324
|
+
try {
|
|
325
|
+
return ok({ pdf, sha: parseSha256(String(d["pdf_sha256"])) });
|
|
326
|
+
} catch {
|
|
327
|
+
return err("no `pdf_sha256` of 64 hex digits");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* The facts file's text → what a rule judges, or why it cannot be judged. The one reader of the
|
|
333
|
+
* file in `src/`, beside its one writer: a field renamed here and not there is a compile error in
|
|
334
|
+
* this module, not a rule that silently reads `undefined`. Pure.
|
|
335
|
+
*/
|
|
336
|
+
export function parseFactsText(text: string): Result<ReadFacts, FactsProblem> {
|
|
337
|
+
let d: unknown;
|
|
338
|
+
try {
|
|
339
|
+
d = JSON.parse(text);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
return err({ kind: "broken", why: `not JSON (${(e as Error).message})` });
|
|
342
|
+
}
|
|
343
|
+
if (!isRecord(d)) return err({ kind: "broken", why: "not a JSON object" });
|
|
344
|
+
if (d["schema"] !== FACTS_SCHEMA)
|
|
345
|
+
return err({ kind: "schema", got: JSON.stringify(d["schema"] ?? null) });
|
|
346
|
+
const artifact = artifactOf(d);
|
|
347
|
+
if (!artifact.ok) return err({ kind: "broken", why: artifact.error });
|
|
348
|
+
const rawFonts = d["fonts"];
|
|
349
|
+
const fonts = Array.isArray(rawFonts) ? rawFonts.map(fontOf) : null;
|
|
350
|
+
if (fonts === null || fonts.some((f) => f === null))
|
|
351
|
+
return err({
|
|
352
|
+
kind: "broken",
|
|
353
|
+
why: "`fonts` is not a list of { name, type, embedded, program }",
|
|
354
|
+
});
|
|
355
|
+
const geometry = geometryOf(d);
|
|
356
|
+
if (!geometry.ok) return err({ kind: "broken", why: geometry.error });
|
|
357
|
+
return ok({
|
|
358
|
+
...artifact.value,
|
|
359
|
+
fonts: fonts as FontEntry[],
|
|
360
|
+
geometry: geometry.value,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
@@ -36,7 +36,6 @@ const {
|
|
|
36
36
|
shippedWiring,
|
|
37
37
|
wireHooks,
|
|
38
38
|
wiredCounts,
|
|
39
|
-
pluginEnabledHere,
|
|
40
39
|
doctorHooks,
|
|
41
40
|
readSettings,
|
|
42
41
|
MANAGED_BY,
|
|
@@ -83,36 +82,26 @@ try {
|
|
|
83
82
|
// ── which spelling runs which hook ─────────────────────────────────────────────────────
|
|
84
83
|
const ours = `node "\${CLAUDE_PROJECT_DIR}/${MANAGED_BY}" hook paper-edit-guard`;
|
|
85
84
|
const cases = [
|
|
86
|
-
[ours, { name: "paper-edit-guard", ours: true
|
|
85
|
+
[ours, { name: "paper-edit-guard", ours: true }],
|
|
87
86
|
[
|
|
88
87
|
`node "$CLAUDE_PROJECT_DIR/${MANAGED_BY}" hook paper-status-gates`,
|
|
89
|
-
{ name: "paper-status-gates", ours: true
|
|
90
|
-
],
|
|
91
|
-
// Guards: what an older install wrote is recognised as ours-but-stale. Spelled literally, not
|
|
92
|
-
// built from a constant: these are what 1.x and 2.0.0 actually put in users' settings files.
|
|
93
|
-
[
|
|
94
|
-
`node "$CLAUDE_PROJECT_DIR/node_modules/research-paper-pipeline/bin/rpp.mjs" hook paper-edit-guard`,
|
|
95
|
-
{ name: "paper-edit-guard", ours: false, legacy: true },
|
|
96
|
-
],
|
|
97
|
-
[
|
|
98
|
-
`node "\${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/rpp.mjs" hook paper-status-gates`,
|
|
99
|
-
{ name: "paper-status-gates", ours: false, legacy: true },
|
|
88
|
+
{ name: "paper-status-gates", ours: true },
|
|
100
89
|
],
|
|
101
90
|
[
|
|
102
91
|
`node "$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program "$CLAUDE_PROJECT_DIR/node_modules/paperlint/hooks/paper-edit-guard.hook.mjs"`,
|
|
103
|
-
{ name: "paper-edit-guard", ours: false
|
|
92
|
+
{ name: "paper-edit-guard", ours: false },
|
|
104
93
|
],
|
|
105
94
|
[
|
|
106
95
|
`npx paperlint hook paper-skills-nudge`,
|
|
107
|
-
{ name: "paper-skills-nudge", ours: false
|
|
96
|
+
{ name: "paper-skills-nudge", ours: false },
|
|
108
97
|
],
|
|
109
98
|
[
|
|
110
99
|
`npx paperlint hook paper-skills-nudge`,
|
|
111
|
-
{ name: "paper-skills-nudge", ours: false
|
|
100
|
+
{ name: "paper-skills-nudge", ours: false },
|
|
112
101
|
],
|
|
113
102
|
[
|
|
114
103
|
`node /abs/proj/node_modules/paperlint/bin/paperlint.mjs hook paper-edit-guard`,
|
|
115
|
-
{ name: "paper-edit-guard", ours: false
|
|
104
|
+
{ name: "paper-edit-guard", ours: false },
|
|
116
105
|
],
|
|
117
106
|
[`node my-own-lint.mjs`, null],
|
|
118
107
|
[`node node_modules/paperlint/bin/paperlint.mjs lint`, null],
|
|
@@ -158,46 +147,6 @@ try {
|
|
|
158
147
|
);
|
|
159
148
|
}
|
|
160
149
|
|
|
161
|
-
// ── what an older install wrote is migrated, the user's own command kept ─────────────────
|
|
162
|
-
// Two real histories, spelled literally: 1.x wrote the old package directory, 2.0.0 wrote the
|
|
163
|
-
// new directory with the old entry file name. Neither file exists after an upgrade.
|
|
164
|
-
for (const [release, stale] of [
|
|
165
|
-
["1.x", "node_modules/research-paper-pipeline/bin/rpp.mjs"],
|
|
166
|
-
["2.0.0", "node_modules/paperlint/bin/rpp.mjs"],
|
|
167
|
-
]) {
|
|
168
|
-
const legacyWired = JSON.parse(
|
|
169
|
-
JSON.stringify(merge({}, wiring.compiled, MANAGED_BY)).replaceAll(
|
|
170
|
-
MANAGED_BY,
|
|
171
|
-
stale,
|
|
172
|
-
),
|
|
173
|
-
);
|
|
174
|
-
legacyWired.hooks.PostToolUse[0].hooks.push({
|
|
175
|
-
type: "command",
|
|
176
|
-
command: "node my-own-lint.mjs",
|
|
177
|
-
});
|
|
178
|
-
const dir = project(`legacy-${release}`, legacyWired);
|
|
179
|
-
const before = doctorHooks(dir, wiring).join("\n");
|
|
180
|
-
check(
|
|
181
|
-
`🔴 doctor names hook commands ${release} left behind — the missing file, and the fix`,
|
|
182
|
-
before.includes(`run ${stale}, which this version does not install`) &&
|
|
183
|
-
/npx paperlint init` replaces them/.test(before),
|
|
184
|
-
);
|
|
185
|
-
const r = wireHooks(dir, merge, wiring);
|
|
186
|
-
const s = JSON.parse(text(dir));
|
|
187
|
-
const counts = [...wiredCounts(s, wiring.names).values()];
|
|
188
|
-
check(
|
|
189
|
-
`🔴 init replaces what ${release} wrote: every hook wired once, none left at the old path`,
|
|
190
|
-
r.status === "written" &&
|
|
191
|
-
r.replaced === wiring.names.length &&
|
|
192
|
-
counts.every((c) => c.ours === 1 && c.legacy === 0 && c.other === 0) &&
|
|
193
|
-
!text(dir).includes(stale),
|
|
194
|
-
);
|
|
195
|
-
check(
|
|
196
|
-
`and the user's own command in the same matcher survives the ${release} migration`,
|
|
197
|
-
text(dir).includes("node my-own-lint.mjs"),
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
150
|
// ── the user's own hooks and keys survive ──────────────────────────────────────────────
|
|
202
151
|
{
|
|
203
152
|
const dir = project("user", {
|
|
@@ -250,7 +199,7 @@ try {
|
|
|
250
199
|
{
|
|
251
200
|
type: "command",
|
|
252
201
|
command:
|
|
253
|
-
'node "$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program "$CLAUDE_PROJECT_DIR/node_modules/
|
|
202
|
+
'node "$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program "$CLAUDE_PROJECT_DIR/node_modules/paperlint/hooks/paper-edit-guard.hook.mjs"',
|
|
254
203
|
},
|
|
255
204
|
],
|
|
256
205
|
},
|
|
@@ -296,25 +245,6 @@ try {
|
|
|
296
245
|
);
|
|
297
246
|
}
|
|
298
247
|
|
|
299
|
-
// ── the plugin, enabled by the project ─────────────────────────────────────────────────
|
|
300
|
-
check(
|
|
301
|
-
"the project's enabledPlugins entry for this package is found",
|
|
302
|
-
pluginEnabledHere({
|
|
303
|
-
enabledPlugins: {
|
|
304
|
-
"research-paper-pipeline@research-paper-pipeline": true,
|
|
305
|
-
"other@x": true,
|
|
306
|
-
},
|
|
307
|
-
}).join() === "research-paper-pipeline@research-paper-pipeline",
|
|
308
|
-
);
|
|
309
|
-
check(
|
|
310
|
-
"a disabled entry does not count",
|
|
311
|
-
pluginEnabledHere({
|
|
312
|
-
enabledPlugins: {
|
|
313
|
-
"research-paper-pipeline@research-paper-pipeline": false,
|
|
314
|
-
},
|
|
315
|
-
}).length === 0,
|
|
316
|
-
);
|
|
317
|
-
|
|
318
248
|
// ── doctor's section ───────────────────────────────────────────────────────────────────
|
|
319
249
|
const doc = (settings) => {
|
|
320
250
|
const dir = project(`doc-${String(n)}`, settings);
|
|
@@ -361,21 +291,6 @@ try {
|
|
|
361
291
|
handPartial,
|
|
362
292
|
) && /writes nothing then/.test(handPartial),
|
|
363
293
|
);
|
|
364
|
-
const pluginText = doc({
|
|
365
|
-
...wiredOnce,
|
|
366
|
-
enabledPlugins: { "research-paper-pipeline@research-paper-pipeline": true },
|
|
367
|
-
});
|
|
368
|
-
check(
|
|
369
|
-
"the project also enabling the plugin → told to uninstall it, with the command",
|
|
370
|
-
/ALSO enables the plugin/.test(pluginText) &&
|
|
371
|
-
/\/plugin uninstall research-paper-pipeline@research-paper-pipeline/.test(
|
|
372
|
-
pluginText,
|
|
373
|
-
),
|
|
374
|
-
);
|
|
375
|
-
check(
|
|
376
|
-
"🔴 and it says it cannot see a USER-scope plugin, rather than implying there is none",
|
|
377
|
-
/USER scope is not visible from here/.test(doc({})),
|
|
378
|
-
);
|
|
379
294
|
check(
|
|
380
295
|
"an unparsable file is named in doctor, not crashed on",
|
|
381
296
|
/does not parse/.test(
|
|
@@ -24,12 +24,12 @@ process.exit(
|
|
|
24
24
|
harness: HARNESS,
|
|
25
25
|
// The spelling truth table is the first owner: `npx paperlint hook …` must read as NOT ours,
|
|
26
26
|
// and that row is where it goes red.
|
|
27
|
-
expect: '→ {"name":"paper-
|
|
27
|
+
expect: '→ {"name":"paper-skills-nudge","ours":false}',
|
|
28
28
|
disables:
|
|
29
29
|
"the duplicate guard for every bin spelling but ours. A hook wired by hand as " +
|
|
30
30
|
"`npx paperlint hook …` or through an absolute path to the bin reads as ours, init merges its own " +
|
|
31
31
|
"copy beside it, and the hook runs twice per event",
|
|
32
|
-
edits: [[SRC, "
|
|
32
|
+
edits: [[SRC, "ours: t === MANAGED_BY,", "ours: true,"]],
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
name: "another spelling no longer stops the write",
|
|
@@ -94,21 +94,6 @@ process.exit(
|
|
|
94
94
|
"same thing again — a loop with no exit",
|
|
95
95
|
edits: [[SRC, " const remedy = handWired", " const remedy = false"]],
|
|
96
96
|
},
|
|
97
|
-
{
|
|
98
|
-
name: "a disabled plugin counts as enabled",
|
|
99
|
-
harness: HARNESS,
|
|
100
|
-
expect: "a disabled entry does not count",
|
|
101
|
-
disables:
|
|
102
|
-
"the plugin warning's precision. A project that already switched the plugin off would " +
|
|
103
|
-
"be told to uninstall it",
|
|
104
|
-
edits: [
|
|
105
|
-
[
|
|
106
|
-
SRC,
|
|
107
|
-
' ([id, on]) => on === true && id.split("@")[0] === LEGACY_PACKAGE_NAME,',
|
|
108
|
-
' ([id]) => id.split("@")[0] === LEGACY_PACKAGE_NAME,',
|
|
109
|
-
],
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
97
|
],
|
|
113
98
|
}),
|
|
114
99
|
);
|