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
|
@@ -4,7 +4,7 @@ description: Use when asking "does this paper read well?" / "is the writing any
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Agent, Skill, Bash(node .claude/skills/paper-pipeline/scripts/announce.mjs:*), Bash(node .claude/skills/paper-pipeline/scripts/ledger.mjs:*)]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:c0907d5c98e2c831 compiled from skills/grade-paper-writing/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# grade-paper-writing — grade how the paper READS, then fix it sentence by sentence
|
|
10
10
|
|
|
@@ -255,6 +255,25 @@ is what happened.
|
|
|
255
255
|
`PIPELINE-STATUS.md` (Wc row). Same rule as `pc-panel-review`'s ledger and `analyze-sibling-paper`'s
|
|
256
256
|
`siblings/` — review artifacts live colocated and ride with the paper to camera-ready/extension.
|
|
257
257
|
|
|
258
|
+
## The report's frontmatter — the findings, as records
|
|
259
|
+
|
|
260
|
+
The report opens with YAML frontmatter that lists what it found, one record per finding.
|
|
261
|
+
`paperlint lint` validates it (`review/frontmatter`): an **open** finding must name the
|
|
262
|
+
pipeline `cause` that let it through — fix that, and the text edit falls out of running the
|
|
263
|
+
fixed tool.
|
|
264
|
+
|
|
265
|
+
```yaml
|
|
266
|
+
---
|
|
267
|
+
findings:
|
|
268
|
+
- id: 1
|
|
269
|
+
status: open # open | fixed | wontfix
|
|
270
|
+
cause: missing-skill # skill-defect | missing-skill | hook | rule — required when open
|
|
271
|
+
title: "§3 opens on a term the reader has not met"
|
|
272
|
+
- id: 2
|
|
273
|
+
status: fixed
|
|
274
|
+
---
|
|
275
|
+
```
|
|
276
|
+
|
|
258
277
|
## Record the verdict
|
|
259
278
|
|
|
260
279
|
🔴 LAST step, once the deliverable exists:
|
|
@@ -271,6 +271,25 @@ is what happened.
|
|
|
271
271
|
\`PIPELINE-STATUS.md\` (Wc row). Same rule as \`pc-panel-review\`'s ledger and \`analyze-sibling-paper\`'s
|
|
272
272
|
\`siblings/\` — review artifacts live colocated and ride with the paper to camera-ready/extension.
|
|
273
273
|
|
|
274
|
+
## The report's frontmatter — the findings, as records
|
|
275
|
+
|
|
276
|
+
The report opens with YAML frontmatter that lists what it found, one record per finding.
|
|
277
|
+
\`paperlint lint\` validates it (\`review/frontmatter\`): an **open** finding must name the
|
|
278
|
+
pipeline \`cause\` that let it through — fix that, and the text edit falls out of running the
|
|
279
|
+
fixed tool.
|
|
280
|
+
|
|
281
|
+
\`\`\`yaml
|
|
282
|
+
---
|
|
283
|
+
findings:
|
|
284
|
+
- id: 1
|
|
285
|
+
status: open # open | fixed | wontfix
|
|
286
|
+
cause: missing-skill # skill-defect | missing-skill | hook | rule — required when open
|
|
287
|
+
title: "§3 opens on a term the reader has not met"
|
|
288
|
+
- id: 2
|
|
289
|
+
status: fixed
|
|
290
|
+
---
|
|
291
|
+
\`\`\`
|
|
292
|
+
|
|
274
293
|
## Record the verdict
|
|
275
294
|
|
|
276
295
|
🔴 LAST step, once the deliverable exists:
|
|
@@ -199,6 +199,14 @@ format.
|
|
|
199
199
|
|
|
200
200
|
## 3. Venue constants belong in a card, not in code
|
|
201
201
|
|
|
202
|
+
> **Done (#79, 2.1.0), in a different shape than planned below.** The constants live in **venue
|
|
203
|
+
> presets**, JSONC files in `submit-paper/references/venues/`: `acm-sigconf.jsonc` is the ACM
|
|
204
|
+
> family (the `format` block — page size, columns, fonts, font sizes — plus the `tex` packages),
|
|
205
|
+
> `agenticdev.jsonc` and `aisec.jsonc` extend it and add their `kinds` (page limits) and `rules`,
|
|
206
|
+
> `realm.jsonc` stands alone. A paper picks one in `<paper>/paperlint.json`
|
|
207
|
+
> (`{ "extends": "paperlint:<venue>", "kind": "…" }`); the `pdf/*` lint rules judge the built PDF
|
|
208
|
+
> against it. The `.md` cards stay as prose. What follows is the plan as it was written.
|
|
209
|
+
|
|
202
210
|
Right now `LinLibertine`, `LinBiolinum`, `acmart` are hardcoded into `check-render.sh`. This is
|
|
203
211
|
**venue data**, and it belongs in
|
|
204
212
|
`.claude/skills/submit-paper/references/venues/<venue>.md`:
|
|
@@ -292,7 +300,7 @@ imports, paths inside regexes).
|
|
|
292
300
|
## 2. One build entry point for all papers, not one per paper
|
|
293
301
|
|
|
294
302
|
> **Done (#59, 2026-09-24).** `paperlint build <paper>` is the entry point, and `paperlint toolchain` installs
|
|
295
|
-
> TeX Live with the packages the venue
|
|
303
|
+
> TeX Live with the packages the venue presets declare; `ensure-toolchain.sh` and
|
|
296
304
|
> `ci-install-texlive.sh` are deleted. What follows is the plan as it was written.
|
|
297
305
|
|
|
298
306
|
**The hole:** `compile-rules` has `repro/build-submission.sh`, which calls `ensure-toolchain.sh`.
|
|
@@ -324,9 +332,11 @@ point. Nothing to check.
|
|
|
324
332
|
| page limit | **4** | `venues/realm.md` 8 · `build-submission.sh` 8 · `PIPELINE-STATUS` "8/8" · the build prints **9** |
|
|
325
333
|
| the TeX package list | **5** | `ensure-toolchain.sh` (executable) · `SKILL.md` · `render-paper.harness.mjs` (pins it ✅) · `build-submission.sh` · `PIPELINE-STATUS` |
|
|
326
334
|
|
|
327
|
-
The canonical source is the venue
|
|
328
|
-
format
|
|
329
|
-
|
|
335
|
+
The canonical source is the venue preset (`venues/<name>.jsonc`, resolved through its `extends`
|
|
336
|
+
chain): its `format` block for the numbers (page size, columns, fonts, font sizes, and `kinds` for
|
|
337
|
+
page limits), its `tex` block (plus `tex-base.jsonc`) for packages — since 2026-09-24;
|
|
338
|
+
`ensure-toolchain.sh` held them until then — and its `rules` block for the checks the venue
|
|
339
|
+
implies. The `.md` card is prose about the venue. Everything else is a pointer.
|
|
330
340
|
|
|
331
341
|
## 3-ter. Drop the font gate from `check-render.sh` once the rules run in CI
|
|
332
342
|
|
|
@@ -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:291d2266e29874be compiled from skills/paper-pipeline/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# paper-pipeline — the conductor for the whole organism
|
|
10
10
|
|
|
@@ -28,8 +28,9 @@ of that — who is filing what, and when — stays in the author's own private n
|
|
|
28
28
|
- `references/acl-venue-rules.md` — what holds at **every** ACL-family venue and workshop: the
|
|
29
29
|
page-limit map, the appendix rule and the trap inside it, Limitations/Ethics as free-but-restricted
|
|
30
30
|
space, anonymity, OpenReview mechanics. Fetch once, not per paper. Venue-specific facts stay in
|
|
31
|
-
`submit-paper/references/venues/<venue>.md`. (No equivalent yet for ACM or IEEE — write one
|
|
32
|
-
the next paper goes there.
|
|
31
|
+
`submit-paper/references/venues/<venue>.md`. (No prose equivalent yet for ACM or IEEE — write one
|
|
32
|
+
when the next paper goes there. ACM's machine-checked format already exists: the
|
|
33
|
+
`paperlint:acm-sigconf` preset, `submit-paper/references/venues/acm-sigconf.jsonc`.)
|
|
33
34
|
|
|
34
35
|
## The readiness scorecard (which boxes are checked)
|
|
35
36
|
Stages run in subagents and report into chat, which evaporates — so "did everything run?" becomes
|
|
@@ -45,8 +45,9 @@ of that — who is filing what, and when — stays in the author's own private n
|
|
|
45
45
|
- \`references/acl-venue-rules.md\` — what holds at **every** ACL-family venue and workshop: the
|
|
46
46
|
page-limit map, the appendix rule and the trap inside it, Limitations/Ethics as free-but-restricted
|
|
47
47
|
space, anonymity, OpenReview mechanics. Fetch once, not per paper. Venue-specific facts stay in
|
|
48
|
-
\`submit-paper/references/venues/<venue>.md\`. (No equivalent yet for ACM or IEEE — write one
|
|
49
|
-
the next paper goes there.
|
|
48
|
+
\`submit-paper/references/venues/<venue>.md\`. (No prose equivalent yet for ACM or IEEE — write one
|
|
49
|
+
when the next paper goes there. ACM's machine-checked format already exists: the
|
|
50
|
+
\`paperlint:acm-sigconf\` preset, \`submit-paper/references/venues/acm-sigconf.jsonc\`.)
|
|
50
51
|
|
|
51
52
|
## The readiness scorecard (which boxes are checked)
|
|
52
53
|
Stages run in subagents and report into chat, which evaporates — so "did everything run?" becomes
|
|
@@ -29,5 +29,4 @@ export declare function installedSkills(dir: string): string[];
|
|
|
29
29
|
export declare function packageVenuesDir(): string;
|
|
30
30
|
export declare const SHIPPED_SKILLS_DIR: string;
|
|
31
31
|
export declare const PACKAGE_NAME: "paperlint";
|
|
32
|
-
export declare const LEGACY_PACKAGE_NAME: "research-paper-pipeline";
|
|
33
32
|
export declare const BIN_FILE: "bin/paperlint.mjs";
|
|
@@ -32,7 +32,6 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
32
32
|
import { fileURLToPath } from "node:url";
|
|
33
33
|
import { spawnSync } from "node:child_process";
|
|
34
34
|
import {
|
|
35
|
-
CONFIG_KEY,
|
|
36
35
|
DEFAULT_SCRIPTS_ROOT,
|
|
37
36
|
consumerRoot,
|
|
38
37
|
consumerSkillsDir,
|
|
@@ -87,15 +86,12 @@ const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
87
86
|
const TMP = realpathSync(mkdtempSync(join(tmpdir(), "consumer-harness-")));
|
|
88
87
|
process.on("exit", () => rmSync(TMP, { recursive: true, force: true }));
|
|
89
88
|
|
|
90
|
-
/** A throwaway consumer repository with the given `paperlint`
|
|
89
|
+
/** A throwaway consumer repository with the given root `paperlint.json` settings (or none). */
|
|
91
90
|
function fakeConsumer(block) {
|
|
92
91
|
const root = mkdtempSync(join(TMP, "repo-"));
|
|
93
|
-
writeFileSync(
|
|
94
|
-
|
|
95
|
-
JSON.stringify(
|
|
96
|
-
block === undefined ? { name: "x" } : { name: "x", [CONFIG_KEY]: block },
|
|
97
|
-
),
|
|
98
|
-
);
|
|
92
|
+
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "x" }));
|
|
93
|
+
if (block !== undefined)
|
|
94
|
+
writeFileSync(join(root, "paperlint.json"), JSON.stringify(block));
|
|
99
95
|
return root;
|
|
100
96
|
}
|
|
101
97
|
/** A directory that looks like an installed copy of this package. */
|
|
@@ -188,7 +184,7 @@ function installedDir(root) {
|
|
|
188
184
|
);
|
|
189
185
|
assert.match(
|
|
190
186
|
err.message,
|
|
191
|
-
/
|
|
187
|
+
/paperlint\.json/,
|
|
192
188
|
"the refusal must name the file the key goes in",
|
|
193
189
|
);
|
|
194
190
|
}
|
|
@@ -409,12 +405,12 @@ assert.equal(
|
|
|
409
405
|
);
|
|
410
406
|
assert.match(
|
|
411
407
|
missing.message,
|
|
412
|
-
/
|
|
408
|
+
/paperlint\.json/,
|
|
413
409
|
"the refusal must name the file the key goes in",
|
|
414
410
|
);
|
|
415
411
|
|
|
416
412
|
// And the default's version of that message must say the default was used — otherwise someone
|
|
417
|
-
// who declared nothing goes looking in
|
|
413
|
+
// who declared nothing goes looking in paperlint.json for a line that is not there.
|
|
418
414
|
assert.throws(
|
|
419
415
|
() => scriptsRoot({ env: {}, cwd: fakeConsumer(undefined) }),
|
|
420
416
|
/Nothing was declared/,
|
|
@@ -462,7 +458,7 @@ assert.equal(
|
|
|
462
458
|
assert.equal(s.ledger, "a/b/ledger.mjs");
|
|
463
459
|
|
|
464
460
|
// EXACTLY ONE slash, whatever the consumer typed. `scriptsRoot()` returns the declaration
|
|
465
|
-
// verbatim, so a trailing slash in
|
|
461
|
+
// verbatim, so a trailing slash in paperlint.json arrives here intact; `${root}/` would then give
|
|
466
462
|
// `a/b//`, which matches nothing — the silent direction again.
|
|
467
463
|
const typedSlash = pipelineScripts("a/b/");
|
|
468
464
|
assert.equal(
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
* ── THE LEDGER, THREE RUNGS ─────────────────────────────────────────────────
|
|
57
57
|
* 1. `PIPELINE_LEDGER` in the environment — always wins. This is what test harnesses set to
|
|
58
58
|
* keep fixture rows out of real history, so it must outrank a declaration on disk.
|
|
59
|
-
* 2. `
|
|
59
|
+
* 2. `{ "ledger": "…" }` in the CONSUMER's root `paperlint.json`, read
|
|
60
60
|
* from `process.cwd()`, resolved relative to it.
|
|
61
61
|
* 3. `runs.jsonl` beside this file — ONLY when this file is not inside `node_modules`, i.e.
|
|
62
62
|
* when the package is being developed in its own checkout. Inside `node_modules` with
|
|
@@ -75,7 +75,6 @@
|
|
|
75
75
|
*/
|
|
76
76
|
import {
|
|
77
77
|
existsSync,
|
|
78
|
-
readFileSync,
|
|
79
78
|
readdirSync,
|
|
80
79
|
readlinkSync,
|
|
81
80
|
realpathSync,
|
|
@@ -86,7 +85,11 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
86
85
|
|
|
87
86
|
// Re-exported, not re-declared — see the note in `lib/paper-config.mjs`. This file is not a hook
|
|
88
87
|
// and never needed its own copy.
|
|
89
|
-
import {
|
|
88
|
+
import {
|
|
89
|
+
CONFIG_FILE,
|
|
90
|
+
CONFIG_KEY,
|
|
91
|
+
settingsOf,
|
|
92
|
+
} from "../../../lib/paper-config.mjs";
|
|
90
93
|
export { CONFIG_KEY, settingsOf };
|
|
91
94
|
|
|
92
95
|
/**
|
|
@@ -144,13 +147,6 @@ export function packageVenuesDir() {
|
|
|
144
147
|
*/
|
|
145
148
|
export const PACKAGE_NAME = "paperlint";
|
|
146
149
|
|
|
147
|
-
/**
|
|
148
|
-
* The name the package was published under before 2.0.0. Only for recognising what an older
|
|
149
|
-
* install left behind (hook commands, skill links, the config key) so `init` can replace it and
|
|
150
|
-
* `doctor` can name it.
|
|
151
|
-
*/
|
|
152
|
-
export const LEGACY_PACKAGE_NAME = "research-paper-pipeline";
|
|
153
|
-
|
|
154
150
|
/**
|
|
155
151
|
* The package's one executable, relative to the package root — what `bin` in package.json names
|
|
156
152
|
* and what every hook command runs. Named like the package; `consumer.harness.mjs` checks it
|
|
@@ -245,16 +241,6 @@ function readlinkOr(entry) {
|
|
|
245
241
|
}
|
|
246
242
|
}
|
|
247
243
|
|
|
248
|
-
/** The consumer's parsed `package.json`, or `null` when there is none or it does not parse. */
|
|
249
|
-
export function consumerPkg(opts) {
|
|
250
|
-
const file = join(consumerRoot(opts), "package.json");
|
|
251
|
-
try {
|
|
252
|
-
return JSON.parse(readFileSync(file, "utf8"));
|
|
253
|
-
} catch {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
244
|
/**
|
|
259
245
|
* True when `dir` lies inside a `node_modules` directory.
|
|
260
246
|
*
|
|
@@ -282,7 +268,7 @@ export function ledgerPath(
|
|
|
282
268
|
|
|
283
269
|
// Rung 2 — the consumer's declaration.
|
|
284
270
|
const root = consumerRoot({ env, cwd });
|
|
285
|
-
const declared = settingsOf(
|
|
271
|
+
const declared = settingsOf(consumerRoot({ env, cwd }))?.ledger;
|
|
286
272
|
// 🔴 `declared === undefined`, NOT `declared ?? default` — the same distinction `papersRoot()`
|
|
287
273
|
// makes and for the same reason: `"ledger": null` is a keystroke, not an absence, and reading
|
|
288
274
|
// it as "nothing was declared" would silently pick a different file than the one asked for.
|
|
@@ -299,8 +285,8 @@ export function ledgerPath(
|
|
|
299
285
|
throw new Error(
|
|
300
286
|
`${CONFIG_KEY}: no ledger location is declared, and the default (a file beside this ` +
|
|
301
287
|
`module) is inside node_modules, which \`npm ci\` deletes.\n` +
|
|
302
|
-
`Declare where the ledger lives, in ${join(root,
|
|
303
|
-
`
|
|
288
|
+
`Declare where the ledger lives, in ${join(root, CONFIG_FILE)}:\n` +
|
|
289
|
+
` { "ledger": "docs/pipeline-runs.jsonl" }\n` +
|
|
304
290
|
`or set PIPELINE_LEDGER for a single run.\n` +
|
|
305
291
|
`This is thrown rather than defaulted on purpose: appending to a path under ` +
|
|
306
292
|
`node_modules succeeds, so the rows would look recorded right up until the next ` +
|
|
@@ -321,7 +307,7 @@ export function ledgerPathExisting(hereDir, opts) {
|
|
|
321
307
|
if (!existsSync(p))
|
|
322
308
|
throw new Error(
|
|
323
309
|
`${CONFIG_KEY}: the ledger "${p}" does not exist. Create it (an empty file is a valid ` +
|
|
324
|
-
`empty ledger) or fix the "ledger" declaration in
|
|
310
|
+
`empty ledger) or fix the "ledger" declaration in ${CONFIG_FILE}.`,
|
|
325
311
|
);
|
|
326
312
|
return p;
|
|
327
313
|
}
|
|
@@ -374,7 +360,7 @@ export const DEFAULT_SCRIPTS_ROOT = ".claude/skills/paper-pipeline/scripts";
|
|
|
374
360
|
*/
|
|
375
361
|
export function scriptsRoot({ env = process.env, cwd = process.cwd() } = {}) {
|
|
376
362
|
const root = consumerRoot({ env, cwd });
|
|
377
|
-
const declared = settingsOf(
|
|
363
|
+
const declared = settingsOf(consumerRoot({ env, cwd }))?.scripts;
|
|
378
364
|
// 🔴 `declared === undefined`, NOT `declared ?? DEFAULT` — the same distinction `papersRoot()`
|
|
379
365
|
// and `ledgerPath()` make, for the same reason: `"scripts": null` is a keystroke, not an
|
|
380
366
|
// absence, and silently substituting the default for it hides a typo behind a working run.
|
|
@@ -393,8 +379,8 @@ export function scriptsRoot({ env = process.env, cwd = process.cwd() } = {}) {
|
|
|
393
379
|
`instruction would point at a directory the repository does not track.\n` +
|
|
394
380
|
`Keep a symlink where the prose already looks —\n` +
|
|
395
381
|
` ${join(root, DEFAULT_SCRIPTS_ROOT)} -> node_modules/${CONFIG_KEY}/skills/paper-pipeline/scripts\n` +
|
|
396
|
-
`— or declare the real, repository-relative location in ${join(root,
|
|
397
|
-
`
|
|
382
|
+
`— or declare the real, repository-relative location in ${join(root, CONFIG_FILE)}:\n` +
|
|
383
|
+
` { "scripts": "path/to/pipeline/scripts" }\n` +
|
|
398
384
|
`(If nothing was declared, the root itself is under node_modules: run the command from ` +
|
|
399
385
|
`the consumer repository, or export CLAUDE_PROJECT_DIR.)`,
|
|
400
386
|
);
|
|
@@ -403,9 +389,9 @@ export function scriptsRoot({ env = process.env, cwd = process.cwd() } = {}) {
|
|
|
403
389
|
`${CONFIG_KEY}: the pipeline scripts path "${rel}" does not exist under ${root}.\n` +
|
|
404
390
|
(declared === undefined
|
|
405
391
|
? `Nothing was declared, so the default "${DEFAULT_SCRIPTS_ROOT}" was used. Create the ` +
|
|
406
|
-
`symlink there, or declare the real location in
|
|
407
|
-
`
|
|
408
|
-
: `It is declared in
|
|
392
|
+
`symlink there, or declare the real location in ${CONFIG_FILE}:\n` +
|
|
393
|
+
` { "scripts": "path/to/pipeline/scripts" }`
|
|
394
|
+
: `It is declared in ${CONFIG_FILE} as ` +
|
|
409
395
|
`"scripts": ${JSON.stringify(declared)}. Fix it there, or create the directory.`) +
|
|
410
396
|
`\nThis is thrown rather than ignored on purpose: this value is a PREFIX that callers ` +
|
|
411
397
|
`filter prose with, so a wrong one matches no instruction at all and every check built ` +
|
|
@@ -453,7 +439,7 @@ export function consumerTimezone({
|
|
|
453
439
|
env = process.env,
|
|
454
440
|
cwd = process.cwd(),
|
|
455
441
|
} = {}) {
|
|
456
|
-
const declared = settingsOf(
|
|
442
|
+
const declared = settingsOf(consumerRoot({ env, cwd }))?.timezone;
|
|
457
443
|
// 🔴 `declared === undefined`, NOT `declared ?? DEFAULT` — the same distinction every carrier
|
|
458
444
|
// above makes: `"timezone": null` is a keystroke, not an absence.
|
|
459
445
|
const tz = declared === undefined ? DEFAULT_TIMEZONE : declared;
|
|
@@ -467,7 +453,7 @@ export function consumerTimezone({
|
|
|
467
453
|
throw new RangeError(
|
|
468
454
|
`${CONFIG_KEY}: "timezone" is ${JSON.stringify(tz)}, which is not an IANA time zone this ` +
|
|
469
455
|
`runtime knows (e.g. "Europe/Berlin", "America/New_York", "UTC").\n` +
|
|
470
|
-
`Fix it in
|
|
456
|
+
`Fix it in ${CONFIG_FILE}.\n` +
|
|
471
457
|
`This throws rather than falling back to ${DEFAULT_TIMEZONE} on purpose: a silent fallback ` +
|
|
472
458
|
`would put every deadline anchor at the wrong hour while looking like it worked.`,
|
|
473
459
|
);
|
|
@@ -505,7 +491,7 @@ export function consumerContactEmail({
|
|
|
505
491
|
env = process.env,
|
|
506
492
|
cwd = process.cwd(),
|
|
507
493
|
} = {}) {
|
|
508
|
-
const declared = settingsOf(
|
|
494
|
+
const declared = settingsOf(consumerRoot({ env, cwd }))?.contactEmail;
|
|
509
495
|
if (declared === undefined || declared === null) return null;
|
|
510
496
|
if (typeof declared !== "string" || !declared.includes("@"))
|
|
511
497
|
throw new TypeError(
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
* as success, so an exported test object would report ✓ without running anything.
|
|
22
22
|
*/
|
|
23
23
|
import assert from "node:assert/strict";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_PAPERS_ROOT,
|
|
26
|
+
PAPERS_DIR_FIELD,
|
|
27
|
+
settingsOf,
|
|
28
|
+
} from "../../../lib/paper-config.mjs";
|
|
25
29
|
import {
|
|
26
30
|
existsSync,
|
|
27
31
|
mkdirSync,
|
|
@@ -57,9 +61,7 @@ const X = await import(join(HERE, "extract-ref-facts.mjs"));
|
|
|
57
61
|
// A directory on disk answers the same question without knowing any name in advance.
|
|
58
62
|
const PAPERS_ROOT = join(
|
|
59
63
|
ROOT,
|
|
60
|
-
|
|
61
|
-
PAPERS_DIR_FIELD
|
|
62
|
-
] ?? "papers",
|
|
64
|
+
settingsOf(ROOT)?.[PAPERS_DIR_FIELD] ?? DEFAULT_PAPERS_ROOT,
|
|
63
65
|
);
|
|
64
66
|
const REAL_PAPERS = existsSync(PAPERS_ROOT)
|
|
65
67
|
? readdirSync(PAPERS_ROOT, { withFileTypes: true })
|
|
@@ -33,13 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
import { record, ABSTENTIONS } from "./ledger.mjs";
|
|
35
35
|
import { execFileSync } from "node:child_process";
|
|
36
|
-
import {
|
|
37
|
-
existsSync,
|
|
38
|
-
mkdirSync,
|
|
39
|
-
readFileSync,
|
|
40
|
-
readdirSync,
|
|
41
|
-
writeFileSync,
|
|
42
|
-
} from "node:fs";
|
|
36
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
43
37
|
import { resolve, join, dirname } from "node:path";
|
|
44
38
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
45
39
|
import { isMain, settingsOf } from "./consumer.mjs";
|
|
@@ -83,10 +77,7 @@ const ROOT = consumerRoot();
|
|
|
83
77
|
// simply do not run: `needs()` below checks the directory on disk, so the absence reads as
|
|
84
78
|
// ABSTAINED `input-missing`, not as a crash and not as a clean run.
|
|
85
79
|
const CITE_CHECKS =
|
|
86
|
-
process.env.PIPELINE_CITE_CHECKS ||
|
|
87
|
-
settingsOf(JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")))
|
|
88
|
-
?.citeChecks ||
|
|
89
|
-
null;
|
|
80
|
+
process.env.PIPELINE_CITE_CHECKS || settingsOf(ROOT)?.citeChecks || null;
|
|
90
81
|
// The citation-checker directory exists and is declared. It sits in the `needs()` of every row
|
|
91
82
|
// that calls it: without that a wrong or undeclared path would fail with a python stack trace, and
|
|
92
83
|
// in `flags` mode a stack trace is counted line by line and goes into the ledger as a FINDING whose
|
|
@@ -4,7 +4,7 @@ description: Turn a venue's CFP dates into a scheduled, buffered plan on the Goo
|
|
|
4
4
|
allowed-tools: [Read, Write, Grep, Glob, mcp__Google_Calendar__list_events, mcp__Google_Calendar__search_events, mcp__Google_Calendar__create_event, mcp__Google_Calendar__update_event, Bash(node .claude/skills/paper-pipeline/scripts/announce.mjs:*), Bash(node .claude/skills/paper-pipeline/scripts/ledger.mjs:*)]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:faf0c175f1a0c5e5 compiled from skills/plan-paper-timeline/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# plan-paper-timeline — CFP dates → a buffered, filing-aware schedule
|
|
10
10
|
|
|
@@ -96,7 +96,7 @@ Use `mcp__Google_Calendar__create_event` for three anchors per paper. 🔴 **Rea
|
|
|
96
96
|
assume it** — it is the `timezone` carrier, and it is the author's, not the pipeline's:
|
|
97
97
|
|
|
98
98
|
```
|
|
99
|
-
node -p "((
|
|
99
|
+
node -p "(() => { try { return require('./paperlint.json')?.timezone } catch {} })() ?? 'UTC'"
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
The example below shows the default, `UTC`; substitute whatever that command prints.
|
|
@@ -113,7 +113,7 @@ Use \`mcp__Google_Calendar__create_event\` for three anchors per paper. 🔴 **R
|
|
|
113
113
|
assume it** — it is the \`timezone\` carrier, and it is the author's, not the pipeline's:
|
|
114
114
|
|
|
115
115
|
\`\`\`
|
|
116
|
-
node -p "((
|
|
116
|
+
node -p "(() => { try { return require('./paperlint.json')?.timezone } catch {} })() ?? 'UTC'"
|
|
117
117
|
\`\`\`
|
|
118
118
|
|
|
119
119
|
The example below shows the default, \`UTC\`; substitute whatever that command prints.
|
|
@@ -383,7 +383,7 @@ try {
|
|
|
383
383
|
//
|
|
384
384
|
// 🔴 THIS USED TO COMPARE AGAINST ONE HARD-CODED ZONE — the first consumer's. That is exactly
|
|
385
385
|
// the thing a package may not know: the same CFP date is a different wall-clock hour for every
|
|
386
|
-
// author, so the zone is the `timezone` CARRIER, read from the
|
|
386
|
+
// author, so the zone is the `timezone` CARRIER, read from the project's paperlint.json. What
|
|
387
387
|
// the PRESCRIPTION in §3 must show is therefore not anybody's real zone but the documented
|
|
388
388
|
// DEFAULT, next to the instruction telling the model to substitute the declared one.
|
|
389
389
|
//
|
|
@@ -53,8 +53,8 @@ process.exit(
|
|
|
53
53
|
edits: [
|
|
54
54
|
[
|
|
55
55
|
SKILL,
|
|
56
|
-
"
|
|
57
|
-
"
|
|
56
|
+
"require('./paperlint.json')?.timezone } catch {} })() ?? 'UTC'",
|
|
57
|
+
"require('./paperlint.json')?.papersDir } catch {} })() ?? 'papers'",
|
|
58
58
|
],
|
|
59
59
|
],
|
|
60
60
|
harness: HARNESS,
|
|
@@ -348,9 +348,8 @@ fi
|
|
|
348
348
|
# and it does it WIDER: the block here skipped review mode, the rule judges the artifact always. Two
|
|
349
349
|
# sources of truth about one fact drift apart — so the duplicate was deleted, not kept "just in case".
|
|
350
350
|
#
|
|
351
|
-
# 🔴 The condition under which this becomes a loss: the rule looks at papers
|
|
352
|
-
#
|
|
353
|
-
# fonts. Today there are none (all three real papers are declared).
|
|
351
|
+
# 🔴 The condition under which this becomes a loss: the rule looks at papers whose `paperlint.json`
|
|
352
|
+
# names a venue preset. A paper with a build but no preset gets its fonts checked by nobody.
|
|
354
353
|
|
|
355
354
|
# --- chktex, if installed: LaTeX-source typography the log cannot see ---
|
|
356
355
|
# Adopted 2026-08-24 instead of writing our own. Measured first: out of the box it produced 28
|
|
@@ -59,8 +59,8 @@ try {
|
|
|
59
59
|
mkdirSync(paper, { recursive: true });
|
|
60
60
|
cpSync(join(FIX, "t3-mixed.pdf"), join(paper, "paper.pdf"));
|
|
61
61
|
writeFileSync(
|
|
62
|
-
join(paper, "
|
|
63
|
-
JSON.stringify({
|
|
62
|
+
join(paper, "paperlint.json"),
|
|
63
|
+
JSON.stringify({ extends: "paperlint:agenticdev", kind: "short" }),
|
|
64
64
|
);
|
|
65
65
|
const factsFile = join(paper, "_build", "paper.facts.json");
|
|
66
66
|
|
|
@@ -88,7 +88,7 @@ try {
|
|
|
88
88
|
);
|
|
89
89
|
const facts = JSON.parse(readFileSync(factsFile, "utf8"));
|
|
90
90
|
check(
|
|
91
|
-
"the facts are schema 2, about paper.pdf, for the venue
|
|
91
|
+
"the facts are schema 2, about paper.pdf, for the venue paperlint.json declares",
|
|
92
92
|
facts.schema === 2 &&
|
|
93
93
|
facts.pdf === "paper.pdf" &&
|
|
94
94
|
facts.venue === "agenticdev" &&
|
|
@@ -148,8 +148,8 @@ try {
|
|
|
148
148
|
);
|
|
149
149
|
|
|
150
150
|
writeFileSync(
|
|
151
|
-
join(paper, "
|
|
152
|
-
JSON.stringify({
|
|
151
|
+
join(paper, "paperlint.json"),
|
|
152
|
+
JSON.stringify({ pdf: "build/other.pdf" }),
|
|
153
153
|
);
|
|
154
154
|
const missing = shim([paper, "--strict"], { BANAL: fake });
|
|
155
155
|
check(
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* it compiles a paper. This file is a composition root: it reads the environment and builds the real
|
|
9
9
|
* adapters (`dist/adapters/node/`), and it owns the `--strict` policy. This
|
|
10
10
|
* script exists for two callers the build does not serve: a PDF paperlint did not build (a paper that
|
|
11
|
-
* declares its artifact elsewhere in `
|
|
11
|
+
* declares its artifact elsewhere in `paperlint.json`), and CI steps that name this script by path.
|
|
12
12
|
* It reaches the package's compiled code through `../../dist/`, resolved from this file's real
|
|
13
13
|
* location, so it works the same from a checkout, from `node_modules` and through a symlink.
|
|
14
14
|
*
|
|
@@ -48,14 +48,14 @@ import {
|
|
|
48
48
|
import { whyNoGeometry } from "../../dist/domain/geometry.js";
|
|
49
49
|
import { exitCodeFor } from "../../dist/exit-code.js";
|
|
50
50
|
|
|
51
|
-
/** The paper's `
|
|
51
|
+
/** The venue the paper's `paperlint.json` declares, read from disk. */
|
|
52
52
|
export const declaredVenue = (paperDir) => declaredIn(nodeFiles, paperDir);
|
|
53
53
|
|
|
54
54
|
const ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Accept either a path to a PDF or a PAPER DIRECTORY — and in the second case find the artifact on
|
|
58
|
-
* its own: the path the paper declares in the `pdf` field of `
|
|
58
|
+
* its own: the path the paper declares in the `pdf` field of `paperlint.json`, else `paper.pdf` beside
|
|
59
59
|
* it. A paper written in markdown and built by its own script into `build/acl_latex.pdf` would
|
|
60
60
|
* otherwise be skipped silently. The facts ALWAYS go into `<paper directory>/_build/`, not next to
|
|
61
61
|
* the PDF, where neither a rule's glob nor a human looks.
|
|
@@ -111,7 +111,7 @@ if (isMain(import.meta.url)) {
|
|
|
111
111
|
finish(
|
|
112
112
|
"no-artifact",
|
|
113
113
|
strict,
|
|
114
|
-
`🛑 no artifact ${pdf} — the paper declared it in
|
|
114
|
+
`🛑 no artifact ${pdf} — the paper declared it in paperlint.json, but it is not built`,
|
|
115
115
|
);
|
|
116
116
|
// The composition root: banal as the measurer, wired from this process's environment.
|
|
117
117
|
const settings = parseBanalSettings(process.env, hostDirs());
|
|
@@ -36,8 +36,8 @@ import { dirname, join } from "node:path";
|
|
|
36
36
|
import { checkSkill } from "../../lib/skill-checks.mjs";
|
|
37
37
|
import { papersRoot } from "../../eslint-rules/papers.mjs";
|
|
38
38
|
import {
|
|
39
|
-
consumerPkg,
|
|
40
39
|
consumerRoot,
|
|
40
|
+
settingsOf,
|
|
41
41
|
} from "../paper-pipeline/scripts/consumer.mjs";
|
|
42
42
|
|
|
43
43
|
await checkSkill("render-paper");
|
|
@@ -308,7 +308,7 @@ await checkSkill("render-paper");
|
|
|
308
308
|
// and say so out loud.
|
|
309
309
|
const root = (() => {
|
|
310
310
|
try {
|
|
311
|
-
return papersRoot(
|
|
311
|
+
return papersRoot(settingsOf(consumerRoot()), consumerRoot());
|
|
312
312
|
} catch {
|
|
313
313
|
return null;
|
|
314
314
|
}
|
|
@@ -5,7 +5,7 @@ context: fork
|
|
|
5
5
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Agent]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
<!-- vigiles:sha256:
|
|
8
|
+
<!-- vigiles:sha256:177ca8805990361e compiled from skills/study-accepted-papers/SKILL.md.spec.ts -->
|
|
9
9
|
|
|
10
10
|
# study-accepted-papers — learn the venue's bar from its own accepted corpus, then lever your draft up
|
|
11
11
|
|
|
@@ -196,6 +196,7 @@ label on it.
|
|
|
196
196
|
- `verify-citations` — consumes the Step-4 citation gaps.
|
|
197
197
|
- `extend-paper` — the natural home for every EXPENSIVE lever this skill surfaces.
|
|
198
198
|
- `submit-paper` venue data card (`submit-paper/references/venues/<venue>.md`) — save durable venue-bar findings there as data, not as a new skill per venue.
|
|
199
|
+
- the paper's `<paper>/paperlint.json` (`{ "extends": "paperlint:<venue>", "kind": "<kind>" }`) — which venue preset the `pdf/*` rules check the built PDF against; a venue with no shipped preset extends a family (`paperlint:acm-sigconf`) or the project's own `./venues/<name>.jsonc`. Findings about the venue's page limit or format belong in that preset, with their source quote.
|
|
199
200
|
|
|
200
201
|
## Provenance
|
|
201
202
|
Built from the **AISec 2026 @ ACM CCS** polish run (2026-07): the "Safety Theater in Agentic Coding /
|
|
@@ -212,6 +212,7 @@ label on it.
|
|
|
212
212
|
- \`verify-citations\` — consumes the Step-4 citation gaps.
|
|
213
213
|
- \`extend-paper\` — the natural home for every EXPENSIVE lever this skill surfaces.
|
|
214
214
|
- \`submit-paper\` venue data card (\`submit-paper/references/venues/<venue>.md\`) — save durable venue-bar findings there as data, not as a new skill per venue.
|
|
215
|
+
- the paper's \`<paper>/paperlint.json\` (\`{ "extends": "paperlint:<venue>", "kind": "<kind>" }\`) — which venue preset the \`pdf/*\` rules check the built PDF against; a venue with no shipped preset extends a family (\`paperlint:acm-sigconf\`) or the project's own \`./venues/<name>.jsonc\`. Findings about the venue's page limit or format belong in that preset, with their source quote.
|
|
215
216
|
|
|
216
217
|
## Provenance
|
|
217
218
|
Built from the **AISec 2026 @ ACM CCS** polish run (2026-07): the "Safety Theater in Agentic Coding /
|
|
@@ -4,7 +4,7 @@ description: End-to-end playbook for submitting a peer-reviewed paper to a doubl
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:4a42424fd18098e5 compiled from skills/submit-paper/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# submit-paper — get a reviewed paper from "done" to "ready for review"
|
|
10
10
|
|
|
@@ -36,6 +36,14 @@ skill with `name:`/`description:`). This preserves the "save the exact venue ins
|
|
|
36
36
|
keeping the skill namespace from proliferating one card per venue. The *mechanics* stay here; the venue
|
|
37
37
|
file is only the facts.
|
|
38
38
|
|
|
39
|
+
**The card is prose; the machine-checked format is a PRESET beside it.** A paper declares its venue
|
|
40
|
+
in `<paper>/paperlint.json`: `{ "extends": "paperlint:<venue>", "kind": "<kind>" }`, and the
|
|
41
|
+
`pdf/*` lint rules judge the built PDF against that preset (page limit per kind, fonts, page size,
|
|
42
|
+
columns, font sizes). A venue with no shipped preset extends a family — `paperlint:acm-sigconf`
|
|
43
|
+
for an ACM venue — or a preset of the project's own, `./venues/<name>.jsonc` (paperlint's
|
|
44
|
+
`docs/rules.md`, "Writing your own venue preset"). Put the page limits you fetched from the CFP in
|
|
45
|
+
the preset's `format.kinds`, each with its quote.
|
|
46
|
+
|
|
39
47
|
## Publisher specifics — one level ABOVE the venue
|
|
40
48
|
Camera-ready mechanics belong to the **publisher**, not the venue: ACM eRights, the submit-vs-final
|
|
41
49
|
preamble swap, the copyright block, CCS 2012 codes and the mandatory Source-files upload are identical
|
|
@@ -52,6 +52,14 @@ skill with \`name:\`/\`description:\`). This preserves the "save the exact venue
|
|
|
52
52
|
keeping the skill namespace from proliferating one card per venue. The *mechanics* stay here; the venue
|
|
53
53
|
file is only the facts.
|
|
54
54
|
|
|
55
|
+
**The card is prose; the machine-checked format is a PRESET beside it.** A paper declares its venue
|
|
56
|
+
in \`<paper>/paperlint.json\`: \`{ "extends": "paperlint:<venue>", "kind": "<kind>" }\`, and the
|
|
57
|
+
\`pdf/*\` lint rules judge the built PDF against that preset (page limit per kind, fonts, page size,
|
|
58
|
+
columns, font sizes). A venue with no shipped preset extends a family — \`paperlint:acm-sigconf\`
|
|
59
|
+
for an ACM venue — or a preset of the project's own, \`./venues/<name>.jsonc\` (paperlint's
|
|
60
|
+
\`docs/rules.md\`, "Writing your own venue preset"). Put the page limits you fetched from the CFP in
|
|
61
|
+
the preset's \`format.kinds\`, each with its quote.
|
|
62
|
+
|
|
55
63
|
## Publisher specifics — one level ABOVE the venue
|
|
56
64
|
Camera-ready mechanics belong to the **publisher**, not the venue: ACM eRights, the submit-vs-final
|
|
57
65
|
preamble swap, the copyright block, CCS 2012 codes and the mandatory Source-files upload are identical
|