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.
Files changed (226) hide show
  1. package/CLAUDE.md +6 -1
  2. package/CONTRIBUTING.md +21 -0
  3. package/README.md +231 -180
  4. package/action.yml +4 -6
  5. package/dist/adapters/references/index.d.ts +13 -0
  6. package/dist/adapters/references/index.d.ts.map +1 -0
  7. package/dist/adapters/references/index.js +55 -0
  8. package/dist/adapters/references/index.js.map +1 -0
  9. package/dist/adapters/references/reach.io.d.ts +3 -0
  10. package/dist/adapters/references/reach.io.d.ts.map +1 -0
  11. package/dist/adapters/references/reach.io.js +14 -0
  12. package/dist/adapters/references/reach.io.js.map +1 -0
  13. package/dist/build-engine.d.ts.map +1 -1
  14. package/dist/build-engine.js +5 -2
  15. package/dist/build-engine.js.map +1 -1
  16. package/dist/build.d.ts +16 -1
  17. package/dist/build.d.ts.map +1 -1
  18. package/dist/build.js +40 -5
  19. package/dist/build.js.map +1 -1
  20. package/dist/cli.d.ts +37 -20
  21. package/dist/cli.d.ts.map +1 -1
  22. package/dist/cli.js +377 -251
  23. package/dist/cli.js.map +1 -1
  24. package/dist/doctor.d.ts.map +1 -1
  25. package/dist/doctor.js +37 -52
  26. package/dist/doctor.js.map +1 -1
  27. package/dist/facts-file.d.ts +33 -9
  28. package/dist/facts-file.d.ts.map +1 -1
  29. package/dist/facts-file.js +117 -17
  30. package/dist/facts-file.js.map +1 -1
  31. package/dist/hooks-settings.d.ts +0 -21
  32. package/dist/hooks-settings.d.ts.map +1 -1
  33. package/dist/hooks-settings.js +17 -135
  34. package/dist/hooks-settings.js.map +1 -1
  35. package/dist/init.d.ts +40 -19
  36. package/dist/init.d.ts.map +1 -1
  37. package/dist/init.js +144 -109
  38. package/dist/init.js.map +1 -1
  39. package/dist/link-skills.d.ts +1 -1
  40. package/dist/link-skills.d.ts.map +1 -1
  41. package/dist/link-skills.js +11 -29
  42. package/dist/link-skills.js.map +1 -1
  43. package/dist/new-paper.d.ts +2 -2
  44. package/dist/new-paper.d.ts.map +1 -1
  45. package/dist/new-paper.js +6 -2
  46. package/dist/new-paper.js.map +1 -1
  47. package/dist/paper-files.d.ts +63 -0
  48. package/dist/paper-files.d.ts.map +1 -0
  49. package/dist/paper-files.js +69 -0
  50. package/dist/paper-files.js.map +1 -0
  51. package/dist/paper-settings.d.ts +40 -0
  52. package/dist/paper-settings.d.ts.map +1 -0
  53. package/dist/paper-settings.js +144 -0
  54. package/dist/paper-settings.js.map +1 -0
  55. package/dist/ports/check-references.d.ts +31 -0
  56. package/dist/ports/check-references.d.ts.map +1 -0
  57. package/dist/ports/check-references.js +7 -0
  58. package/dist/ports/check-references.js.map +1 -0
  59. package/dist/presets.d.ts +84 -0
  60. package/dist/presets.d.ts.map +1 -0
  61. package/dist/presets.js +184 -0
  62. package/dist/presets.js.map +1 -0
  63. package/dist/reference-rules.d.ts +47 -0
  64. package/dist/reference-rules.d.ts.map +1 -0
  65. package/dist/reference-rules.js +156 -0
  66. package/dist/reference-rules.js.map +1 -0
  67. package/dist/references.d.ts +36 -0
  68. package/dist/references.d.ts.map +1 -0
  69. package/dist/references.js +91 -0
  70. package/dist/references.js.map +1 -0
  71. package/dist/rules-config.d.ts +11 -2
  72. package/dist/rules-config.d.ts.map +1 -1
  73. package/dist/rules-config.js +25 -11
  74. package/dist/rules-config.js.map +1 -1
  75. package/dist/structure.d.ts.map +1 -1
  76. package/dist/structure.js +3 -2
  77. package/dist/structure.js.map +1 -1
  78. package/dist/tex-requirements.d.ts +63 -7
  79. package/dist/tex-requirements.d.ts.map +1 -1
  80. package/dist/tex-requirements.js +91 -24
  81. package/dist/tex-requirements.js.map +1 -1
  82. package/dist/types.d.ts +20 -18
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/venue-rules.d.ts +109 -0
  85. package/dist/venue-rules.d.ts.map +1 -0
  86. package/dist/venue-rules.js +389 -0
  87. package/dist/venue-rules.js.map +1 -0
  88. package/docs/configuration.md +152 -77
  89. package/docs/e2e.md +1 -1
  90. package/docs/install.md +25 -40
  91. package/docs/optional-rules.md +26 -24
  92. package/docs/rules.md +181 -15
  93. package/eslint-rules/bib-reachable-entry.mjs +75 -0
  94. package/eslint-rules/latex-language.mjs +29 -1
  95. package/eslint-rules/paper-stages.harness.mjs +1 -117
  96. package/eslint-rules/paper-stages.mjs +0 -120
  97. package/eslint-rules/paper-stages.mutations.mjs +0 -76
  98. package/eslint-rules/paper-typography.mjs +267 -192
  99. package/eslint-rules/paper-typography.test.mjs +297 -0
  100. package/eslint-rules/papers.harness.mjs +8 -12
  101. package/eslint-rules/papers.mjs +17 -27
  102. package/eslint-rules/pdf-last-page-balance.mjs +23 -5
  103. package/eslint-rules/review-frontmatter.mjs +118 -0
  104. package/eslint-rules/review-frontmatter.schema.json +36 -0
  105. package/eslint-rules/review-frontmatter.test.mjs +118 -0
  106. package/eslint-rules/sibling-frontmatter.mjs +19 -0
  107. package/eslint-rules/sibling-frontmatter.schema.json +14 -0
  108. package/eslint.config.mjs +0 -15
  109. package/fixtures/build-e2e/acmart/paperlint.json +1 -0
  110. package/fixtures/real-markdown-paper/baseline.json +6 -7
  111. package/hooks/hooks.harness.mjs +39 -98
  112. package/hooks/hooks.mutations.mjs +6 -6
  113. package/hooks/paper-edit-guard.hook.d.mts +2 -3
  114. package/hooks/paper-edit-guard.hook.mjs +31 -42
  115. package/hooks/paper-skills-nudge.hook.mjs +21 -19
  116. package/hooks/paper-status-gates.hook.mjs +21 -19
  117. package/hooks/paper-status-gates.sh +8 -13
  118. package/lib/paper-config.d.mts +15 -18
  119. package/lib/paper-config.harness.mjs +18 -35
  120. package/lib/paper-config.mjs +90 -81
  121. package/lib/paper-config.mutations.mjs +2 -2
  122. package/lib/skill-checks.mjs +1 -1
  123. package/lib/skill-trigger-cases.harness.mjs +9 -8
  124. package/lib/skill-trigger-cases.mjs +7 -15
  125. package/package.json +1 -1
  126. package/scripts/eslint-report-guard.mjs +1 -1
  127. package/scripts/harness-api.frozen.json +0 -3
  128. package/scripts/mutation-batteries.frozen.json +4 -13
  129. package/skills/analyze-sibling-paper/SKILL.md +16 -12
  130. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +15 -11
  131. package/skills/cold-read-diff/SKILL.md +20 -1
  132. package/skills/cold-read-diff/SKILL.md.spec.ts +19 -0
  133. package/skills/find-venue/SKILL.md +5 -1
  134. package/skills/find-venue/SKILL.md.spec.ts +4 -0
  135. package/skills/grade-paper-writing/SKILL.md +20 -1
  136. package/skills/grade-paper-writing/SKILL.md.spec.ts +19 -0
  137. package/skills/paper-pipeline/PIPELINE-MAP.md +14 -4
  138. package/skills/paper-pipeline/SKILL.md +4 -3
  139. package/skills/paper-pipeline/SKILL.md.spec.ts +3 -2
  140. package/skills/paper-pipeline/scripts/consumer.d.mts +0 -1
  141. package/skills/paper-pipeline/scripts/consumer.harness.mjs +8 -12
  142. package/skills/paper-pipeline/scripts/consumer.mjs +19 -33
  143. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +6 -4
  144. package/skills/paper-pipeline/scripts/run-mechanical.mjs +2 -11
  145. package/skills/plan-paper-timeline/SKILL.md +2 -2
  146. package/skills/plan-paper-timeline/SKILL.md.spec.ts +1 -1
  147. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +1 -1
  148. package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +2 -2
  149. package/skills/render-paper/check-render.sh +2 -3
  150. package/skills/render-paper/extract-pdf-facts.harness.mjs +5 -5
  151. package/skills/render-paper/extract-pdf-facts.mjs +4 -4
  152. package/skills/render-paper/render-paper.harness.mjs +2 -2
  153. package/skills/study-accepted-papers/SKILL.md +2 -1
  154. package/skills/study-accepted-papers/SKILL.md.spec.ts +1 -0
  155. package/skills/submit-paper/SKILL.md +9 -1
  156. package/skills/submit-paper/SKILL.md.spec.ts +8 -0
  157. package/skills/submit-paper/references/venues/acm-sigconf.jsonc +86 -0
  158. package/skills/submit-paper/references/venues/agenticdev.jsonc +34 -102
  159. package/skills/submit-paper/references/venues/aisec.jsonc +21 -82
  160. package/skills/submit-paper/references/venues/realm.jsonc +45 -43
  161. package/skills/submit-paper/references/venues/tex-base.jsonc +1 -1
  162. package/skills/submit-paper/references/venues/venue-profile.schema.json +119 -29
  163. package/skills/verify-citations/SKILL.md +10 -1
  164. package/skills/verify-citations/SKILL.md.spec.ts +9 -0
  165. package/skills/verify-citations/scripts/bib-authors.mjs +48 -26
  166. package/src/adapters/references/index.ts +102 -0
  167. package/src/adapters/references/reach.io.ts +12 -0
  168. package/src/build-engine.ts +10 -2
  169. package/src/build.harness.mjs +31 -22
  170. package/src/build.ts +65 -8
  171. package/src/cli.harness.mjs +151 -294
  172. package/src/cli.mutations.mjs +35 -44
  173. package/src/cli.ts +457 -288
  174. package/src/doctor.harness.mjs +41 -84
  175. package/src/doctor.mutations.mjs +9 -9
  176. package/src/doctor.ts +41 -61
  177. package/src/facts-file.test.ts +47 -7
  178. package/src/facts-file.ts +145 -24
  179. package/src/hooks-settings.harness.mjs +7 -92
  180. package/src/hooks-settings.mutations.mjs +2 -17
  181. package/src/hooks-settings.ts +18 -162
  182. package/src/init.test.ts +206 -0
  183. package/src/init.ts +193 -159
  184. package/src/link-skills.harness.mjs +1 -38
  185. package/src/link-skills.mutations.mjs +9 -5
  186. package/src/link-skills.ts +10 -34
  187. package/src/lint-fix.test.ts +120 -0
  188. package/src/new-paper.harness.mjs +9 -6
  189. package/src/new-paper.test.ts +133 -0
  190. package/src/new-paper.ts +7 -2
  191. package/src/paper-files.test.ts +290 -0
  192. package/src/paper-files.ts +144 -0
  193. package/src/paper-settings-commands.test.ts +274 -0
  194. package/src/paper-settings.test.ts +249 -0
  195. package/src/paper-settings.ts +208 -0
  196. package/src/ports/check-references.ts +28 -0
  197. package/src/presets.test.ts +236 -0
  198. package/src/presets.ts +297 -0
  199. package/src/reference-rules.ts +229 -0
  200. package/src/references.test.ts +209 -0
  201. package/src/references.ts +138 -0
  202. package/src/rules-config.harness.mjs +24 -8
  203. package/src/rules-config.ts +30 -13
  204. package/src/structure.harness.mjs +6 -4
  205. package/src/structure.ts +3 -2
  206. package/src/tex-requirements.harness.mjs +11 -17
  207. package/src/tex-requirements.ts +179 -29
  208. package/src/types.ts +30 -18
  209. package/src/venue-rules.test.ts +554 -0
  210. package/src/venue-rules.ts +571 -0
  211. package/templates/paper/paperlint.json +4 -0
  212. package/eslint-rules/doc-fields.harness.mjs +0 -336
  213. package/eslint-rules/doc-fields.mjs +0 -186
  214. package/eslint-rules/doc-fields.mutations.mjs +0 -96
  215. package/eslint-rules/paper-typography.harness.mjs +0 -291
  216. package/eslint-rules/paper-typography.mutations.mjs +0 -131
  217. package/eslint-rules/review-findings-cause.harness.mjs +0 -228
  218. package/eslint-rules/review-findings-cause.mjs +0 -135
  219. package/eslint-rules/review-findings-cause.mutations.mjs +0 -72
  220. package/fixtures/build-e2e/acmart/venue.json +0 -1
  221. package/fixtures/paper-stages/authors-ran/PIPELINE-STATUS.md +0 -16
  222. package/fixtures/paper-stages/marker-in-prose/PIPELINE-STATUS.md +0 -17
  223. package/fixtures/review-findings-cause/clean.md +0 -17
  224. package/fixtures/review-findings-cause/defect.md +0 -14
  225. package/fixtures/review-findings-cause/old-debt.md +0 -14
  226. 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
- const str = (v: unknown): string | null => (typeof v === "string" ? v : null);
73
-
74
- /** A parsed `venue.json`, or null when it names no venue. */
75
- export function parseVenueDecl(json: unknown): VenueDecl | null {
76
- const d = typeof json === "object" && json !== null ? json : {};
77
- const field = (k: string) => str((d as Record<string, unknown>)[k]);
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
- * The paper's `venue.json`, or null when there is none or it names no venue. The one reader of
84
- * that file: the build, the facts writer and the shim all read it here.
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 bytes = files.readBytes(at(join(paperDir, "venue.json")));
91
- return bytes === null
92
- ? null
93
- : parseVenueDecl(JSON.parse(new TextDecoder().decode(bytes)));
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 `venue.json` are read from. */
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?.venue ?? null,
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, legacy: false }],
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, legacy: false },
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, legacy: 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, legacy: 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, legacy: 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, legacy: 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/research-paper-pipeline/hooks/paper-edit-guard.hook.mjs"',
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-edit-guard","ours":false,"legacy":true}',
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, " ours: t === MANAGED_BY,", " ours: true,"]],
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
  );