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/lib/paper-config.mjs
CHANGED
|
@@ -20,64 +20,87 @@
|
|
|
20
20
|
* COVERAGE.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
import { dirname, join, resolve } from "node:path";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The tool's name. Its settings file is named after it (`CONFIG_FILE`), its messages start with it,
|
|
28
|
+
* and the hooks resolve their scripts through it. It equals the package name, and
|
|
29
|
+
* `lib/paper-config.harness.mjs` checks that.
|
|
30
|
+
*/
|
|
24
31
|
export const CONFIG_KEY = "paperlint";
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
34
|
+
* THE SETTINGS FILE, ONE NAME AT TWO LEVELS, ONE SCHEMA (`SETTINGS_KEYS`):
|
|
35
|
+
*
|
|
36
|
+
* paperlint.json optional, at the project root: where the papers are, the
|
|
37
|
+
* project's rules, and defaults for every paper
|
|
38
|
+
* <papersDir>/<paper>/paperlint.json one paper: its venue preset, its kind, its own rules
|
|
39
|
+
*
|
|
40
|
+
* A paper's file merges over the root's: its `extends`, `kind` and `pdf` win, and its `rules` are
|
|
41
|
+
* applied after the root's. A venue preset (`paperlint:<name>`, or the project's own `./x.jsonc`)
|
|
42
|
+
* is the third level, below both.
|
|
43
|
+
*/
|
|
44
|
+
export const CONFIG_FILE = "paperlint.json";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The files that make a directory a PAPER. A `paperlint.json` beside one of them is that paper's,
|
|
48
|
+
* never the project's — so walking up from inside a paper does not stop at the paper's own file.
|
|
30
49
|
*/
|
|
31
|
-
export const
|
|
50
|
+
export const PAPER_MARKERS = Object.freeze([
|
|
51
|
+
"paper.tex",
|
|
52
|
+
"paper.md",
|
|
53
|
+
"draft.md",
|
|
54
|
+
"PIPELINE-STATUS.md",
|
|
55
|
+
]);
|
|
32
56
|
|
|
33
57
|
/**
|
|
34
|
-
*
|
|
58
|
+
* The project root: the nearest directory, walking up from `startDir`, that holds a root
|
|
59
|
+
* `paperlint.json` (one not beside a paper's files); else the nearest directory holding a
|
|
60
|
+
* `package.json`; else `startDir` itself. The same walk eslint, prettier and tsc use for theirs.
|
|
35
61
|
*
|
|
36
|
-
* @
|
|
37
|
-
* from the old key), `conflict` (the message when both keys are there and differ, else null).
|
|
62
|
+
* @param isFile whether a path exists — `existsSync` by default; `src/` passes its Files port.
|
|
38
63
|
*/
|
|
39
|
-
export function
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`package.json has both "${CONFIG_KEY}" and "${LEGACY_CONFIG_KEY}", and they differ. ` +
|
|
54
|
-
`Keep "${CONFIG_KEY}" and delete "${LEGACY_CONFIG_KEY}" (its old name).`,
|
|
55
|
-
};
|
|
56
|
-
if (current)
|
|
57
|
-
return { settings: pkg[CONFIG_KEY], legacy: false, conflict: null };
|
|
58
|
-
if (legacy)
|
|
59
|
-
return { settings: pkg[LEGACY_CONFIG_KEY], legacy: true, conflict: null };
|
|
60
|
-
return { settings: undefined, legacy: false, conflict: null };
|
|
64
|
+
export function findProjectRoot(startDir, isFile = existsSync) {
|
|
65
|
+
const start = resolve(startDir);
|
|
66
|
+
const isPaper = (d) => PAPER_MARKERS.some((m) => isFile(join(d, m)));
|
|
67
|
+
const walk = (found) => {
|
|
68
|
+
for (let d = start; ; d = dirname(d)) {
|
|
69
|
+
if (found(d)) return d;
|
|
70
|
+
if (dirname(d) === d) return null;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return (
|
|
74
|
+
walk((d) => isFile(join(d, CONFIG_FILE)) && !isPaper(d)) ??
|
|
75
|
+
walk((d) => isFile(join(d, "package.json"))) ??
|
|
76
|
+
start
|
|
77
|
+
);
|
|
61
78
|
}
|
|
62
79
|
|
|
80
|
+
const isObject = (v) =>
|
|
81
|
+
v !== null && typeof v === "object" && !Array.isArray(v);
|
|
82
|
+
|
|
63
83
|
/**
|
|
64
|
-
* The settings object
|
|
65
|
-
*
|
|
84
|
+
* The project's settings: the object in `<root>/paperlint.json`, or undefined when there is no
|
|
85
|
+
* such file, or it does not parse, or it is not an object. For readers that only need one key and
|
|
86
|
+
* fall back to its default; `paperlint lint` parses the file strictly and names what is wrong.
|
|
66
87
|
*/
|
|
67
|
-
export function settingsOf(
|
|
68
|
-
|
|
69
|
-
|
|
88
|
+
export function settingsOf(root) {
|
|
89
|
+
let v;
|
|
90
|
+
try {
|
|
91
|
+
v = JSON.parse(readFileSync(join(root, CONFIG_FILE), "utf8"));
|
|
92
|
+
} catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
return isObject(v) ? v : undefined;
|
|
70
96
|
}
|
|
71
97
|
|
|
72
|
-
/** The
|
|
73
|
-
export const LEGACY_KEY_MESSAGE = `"${LEGACY_CONFIG_KEY}" in package.json is the old name of this package's key — rename it to "${CONFIG_KEY}". \`npx paperlint init\` does it for you.`;
|
|
74
|
-
|
|
75
|
-
/** Default: a consumer that declares nothing is assumed to keep papers in `papers/`. */
|
|
98
|
+
/** The default papers directory, used when the root `paperlint.json` declares none (or there is none). */
|
|
76
99
|
export const DEFAULT_PAPERS_ROOT = "papers";
|
|
77
100
|
|
|
78
101
|
/**
|
|
79
|
-
* The field
|
|
80
|
-
* `
|
|
102
|
+
* The field that names the papers directory: `{ "papersDir": "papers" }` in the root
|
|
103
|
+
* `paperlint.json`. Optional; absent means `DEFAULT_PAPERS_ROOT`.
|
|
81
104
|
*
|
|
82
105
|
* This is the one place the name is spelled. Code reads the field as
|
|
83
106
|
* `settings[PAPERS_DIR_FIELD]`, never by a literal, so renaming it again is a one-line change
|
|
@@ -87,51 +110,24 @@ export const DEFAULT_PAPERS_ROOT = "papers";
|
|
|
87
110
|
export const PAPERS_DIR_FIELD = "papersDir";
|
|
88
111
|
|
|
89
112
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*/
|
|
94
|
-
export const OLD_PAPERS_DIR_FIELD = "papers";
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* The error text for a config that still uses the old field name, or `null` when it does not.
|
|
113
|
+
* Every key a `paperlint.json` may hold — the root's and a paper's, ONE schema — with who reads it.
|
|
114
|
+
* A key not listed here is REFUSED with its name, because a typo in a key otherwise reads as "not
|
|
115
|
+
* set". The keys in `ROOT_ONLY_KEYS` describe the whole project and are refused in a paper's file.
|
|
98
116
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
settings,
|
|
104
|
-
where = `package.json → "${CONFIG_KEY}"`,
|
|
105
|
-
) {
|
|
106
|
-
if (settings === null || typeof settings !== "object") return null;
|
|
107
|
-
if (!Object.hasOwn(settings, OLD_PAPERS_DIR_FIELD)) return null;
|
|
108
|
-
return `"${OLD_PAPERS_DIR_FIELD}" was renamed to "${PAPERS_DIR_FIELD}" in ${where}`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Every key the settings object may hold, with who reads it. A key not listed here is REFUSED
|
|
113
|
-
* with its name (`paperlint lint`, `paperlint build`, `paperlint new`, `paperlint doctor` all read through the same
|
|
114
|
-
* parser), because a typo in a key otherwise reads as "not set" — the defect `src/types.ts` names
|
|
115
|
-
* in its first line.
|
|
116
|
-
*
|
|
117
|
-
* 🔴 ONE LIST, AND IT IS CHECKED AGAINST THE READERS, NOT TRUSTED. The settings object has readers
|
|
118
|
-
* all over the package (the CLI, the skill scripts, the trigger-case loader), each reading its own
|
|
119
|
-
* keys. `lib/paper-config.harness.mjs` walks the package's source, finds every
|
|
120
|
-
* `[CONFIG_KEY]?.<key>` read and every field of `PaperlintConfig`, and requires each to be here — so a
|
|
121
|
-
* new reader that forgets this list turns the harness red before a consumer's config does.
|
|
117
|
+
* 🔴 ONE LIST, AND IT IS CHECKED AGAINST THE READERS, NOT TRUSTED. `lib/paper-config.harness.mjs`
|
|
118
|
+
* walks the package's source, finds every `settingsOf(…)?.<key>` read and every field of
|
|
119
|
+
* `PaperlintConfig`, and requires each to be here — so a new reader that forgets this list turns
|
|
120
|
+
* the harness red before a consumer's config does.
|
|
122
121
|
*/
|
|
123
122
|
export const SETTINGS_KEYS = Object.freeze({
|
|
124
|
-
papersDir:
|
|
123
|
+
papersDir: `the papers directory, default "papers" — every command, the hooks`,
|
|
125
124
|
structure: "which files a paper directory must hold — paperlint lint",
|
|
126
|
-
authorListCommand: "paper/author-list — paperlint lint",
|
|
127
|
-
typographyDebt: "paper/typography — paperlint lint",
|
|
128
|
-
docFields: "doc/fields — paperlint lint",
|
|
129
|
-
reviewSince: "review/findings-cause — paperlint lint",
|
|
130
|
-
minFindings: "review/findings-cause — paperlint lint",
|
|
131
|
-
causeMarker: "review/findings-cause — paperlint lint",
|
|
132
125
|
rules:
|
|
133
|
-
"
|
|
134
|
-
|
|
126
|
+
"rule overrides: { id: severity } for every paper in scope, or ESLint blocks (files, ignores, rules) — paperlint lint",
|
|
127
|
+
extends:
|
|
128
|
+
"the venue preset: paperlint:<name> or ./path.jsonc — the pdf/ venue rules, paperlint build",
|
|
129
|
+
kind: "the kind of paper, whose page limit applies — pdf/limits",
|
|
130
|
+
pdf: "where the built PDF is, relative to the paper, when it is not paper.pdf — the facts",
|
|
135
131
|
ledger: "the run ledger — skills/paper-pipeline/scripts/consumer.mjs",
|
|
136
132
|
scripts: "how skill prose names the scripts — consumer.mjs",
|
|
137
133
|
timezone: "dates in the ledger — consumer.mjs",
|
|
@@ -139,4 +135,17 @@ export const SETTINGS_KEYS = Object.freeze({
|
|
|
139
135
|
citeChecks: "the consumer's citation checkers — run-mechanical.mjs",
|
|
140
136
|
triggerCases:
|
|
141
137
|
"the consumer's own skill trigger cases — lib/skill-trigger-cases.mjs",
|
|
138
|
+
$comment: "a note for humans (JSON Schema's own comment keyword); ignored",
|
|
142
139
|
});
|
|
140
|
+
|
|
141
|
+
/** The keys that only mean something for the whole project: refused in a paper's `paperlint.json`. */
|
|
142
|
+
export const ROOT_ONLY_KEYS = Object.freeze([
|
|
143
|
+
"papersDir",
|
|
144
|
+
"structure",
|
|
145
|
+
"ledger",
|
|
146
|
+
"scripts",
|
|
147
|
+
"timezone",
|
|
148
|
+
"contactEmail",
|
|
149
|
+
"citeChecks",
|
|
150
|
+
"triggerCases",
|
|
151
|
+
]);
|
|
@@ -117,8 +117,8 @@ process.exit(
|
|
|
117
117
|
edits: [
|
|
118
118
|
[
|
|
119
119
|
DOCTOR,
|
|
120
|
-
'import {\n papersRoot,\n
|
|
121
|
-
'import {\n papersRoot,\n DEFAULT_PAPERS_ROOT,\n PAPERS_DIR_FIELD,\n
|
|
120
|
+
'import {\n papersRoot,\n DEFAULT_PAPERS_ROOT,\n PAPERS_DIR_FIELD,\n CONFIG_FILE,\n} from "../hooks/paper-edit-guard.hook.mjs";',
|
|
121
|
+
'import {\n papersRoot,\n DEFAULT_PAPERS_ROOT,\n PAPERS_DIR_FIELD,\n} from "../hooks/paper-edit-guard.hook.mjs";\nconst CONFIG_FILE = "paperlint.json";',
|
|
122
122
|
],
|
|
123
123
|
],
|
|
124
124
|
},
|
package/lib/skill-checks.mjs
CHANGED
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
//
|
|
71
71
|
// The value now comes from the package, as the fourth carrier beside `papersRoot()` (where the
|
|
72
72
|
// papers are), `ledgerPath()` (where the journal is) and `citeChecks` (where the consumer's own
|
|
73
|
-
// citation checkers are). One declaration in `
|
|
73
|
+
// citation checkers are). One declaration in `paperlint.json`, default
|
|
74
74
|
// `.claude/skills/paper-pipeline/scripts`, and the resolver REFUSES a root that is not on disk
|
|
75
75
|
// or that resolves inside `node_modules`.
|
|
76
76
|
//
|
|
@@ -53,9 +53,12 @@ function loadAt(root) {
|
|
|
53
53
|
}).trim();
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
/** A consumer: a package.json, the root paperlint.json `settings` (none when null), a cases file. */
|
|
57
|
+
function fixture(settings, casesFile) {
|
|
57
58
|
const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-cases-")));
|
|
58
|
-
writeFileSync(join(root, "package.json"), JSON.stringify(
|
|
59
|
+
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "c" }));
|
|
60
|
+
if (settings)
|
|
61
|
+
writeFileSync(join(root, "paperlint.json"), JSON.stringify(settings));
|
|
59
62
|
if (casesFile) {
|
|
60
63
|
mkdirSync(join(root, ".claude", "lib"), { recursive: true });
|
|
61
64
|
writeFileSync(
|
|
@@ -114,7 +117,7 @@ soft("I. irrelevantFor still yields a full set for a one-collider case", () => {
|
|
|
114
117
|
soft(
|
|
115
118
|
"II. undeclared and absent is SILENT — a consumer with no skills of its own",
|
|
116
119
|
() => {
|
|
117
|
-
const out = loadAt(fixture(
|
|
120
|
+
const out = loadAt(fixture(null, null));
|
|
118
121
|
assert.match(out, /^OK \d+$/, `expected a clean load, got: ${out}`);
|
|
119
122
|
assert.equal(
|
|
120
123
|
Number(out.slice(3)),
|
|
@@ -125,7 +128,7 @@ soft(
|
|
|
125
128
|
);
|
|
126
129
|
|
|
127
130
|
soft("II. the DEFAULT path is picked up without any declaration", () => {
|
|
128
|
-
const out = loadAt(fixture(
|
|
131
|
+
const out = loadAt(fixture(null, LOCAL_ONE));
|
|
129
132
|
assert.equal(
|
|
130
133
|
out,
|
|
131
134
|
`OK ${CASES.length + 1}`,
|
|
@@ -137,9 +140,7 @@ soft("II. the DEFAULT path is picked up without any declaration", () => {
|
|
|
137
140
|
// is the same one `scriptsRoot()` draws: a path someone typed is a keystroke away from being wrong,
|
|
138
141
|
// and substituting silence for it hides the typo behind a working run.
|
|
139
142
|
soft("II. declared and missing THROWS, naming the declaration", () => {
|
|
140
|
-
const out = loadAt(
|
|
141
|
-
fixture({ paperlint: { triggerCases: "nope.mjs" } }, null),
|
|
142
|
-
);
|
|
143
|
+
const out = loadAt(fixture({ triggerCases: "nope.mjs" }, null));
|
|
143
144
|
assert.match(out, /^THROW /, `a typoed declaration loaded quietly: ${out}`);
|
|
144
145
|
assert.match(
|
|
145
146
|
out,
|
|
@@ -150,7 +151,7 @@ soft("II. declared and missing THROWS, naming the declaration", () => {
|
|
|
150
151
|
|
|
151
152
|
soft("II. a consumer may not redefine a case this package owns", () => {
|
|
152
153
|
const clash = `export const CASES = [{ skill: "${CASES[0].skill}", why: "x", prompts: ["a","b","c","d"], collides: [] }];\n`;
|
|
153
|
-
const out = loadAt(fixture(
|
|
154
|
+
const out = loadAt(fixture(null, clash));
|
|
154
155
|
assert.match(out, /^THROW /, `a shadowing case was accepted: ${out}`);
|
|
155
156
|
assert.match(
|
|
156
157
|
out,
|
|
@@ -334,8 +334,8 @@ const PACKAGE_CASES = [
|
|
|
334
334
|
// out of the code and into a declaration. This is the fifth of those carriers and reads the same
|
|
335
335
|
// key from the same file.
|
|
336
336
|
//
|
|
337
|
-
// // <consumer>/
|
|
338
|
-
//
|
|
337
|
+
// // <consumer>/paperlint.json
|
|
338
|
+
// { "triggerCases": ".claude/lib/skill-trigger-cases.local.mjs" }
|
|
339
339
|
//
|
|
340
340
|
// The module it names must export `CASES` in the shape documented at the top of this file.
|
|
341
341
|
//
|
|
@@ -347,8 +347,7 @@ const PACKAGE_CASES = [
|
|
|
347
347
|
// ⚠️ A CONSUMER MAY NOT REDEFINE A PACKAGE CASE. Two tables answering "what should fire on this
|
|
348
348
|
// prompt" is two truths; the override would live in the consumer and the original would keep
|
|
349
349
|
// looking authoritative here. Throws, naming the skill.
|
|
350
|
-
import {
|
|
351
|
-
import { join, resolve } from "node:path";
|
|
350
|
+
import { resolve } from "node:path";
|
|
352
351
|
import { pathToFileURL } from "node:url";
|
|
353
352
|
import {
|
|
354
353
|
CONFIG_KEY,
|
|
@@ -360,14 +359,7 @@ const DEFAULT_LOCAL_CASES = ".claude/lib/skill-trigger-cases.local.mjs";
|
|
|
360
359
|
|
|
361
360
|
async function consumerCases() {
|
|
362
361
|
const root = consumerRoot();
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
declared = settingsOf(
|
|
366
|
-
JSON.parse(readFileSync(join(root, "package.json"), "utf8")),
|
|
367
|
-
)?.triggerCases;
|
|
368
|
-
} catch {
|
|
369
|
-
declared = undefined;
|
|
370
|
-
}
|
|
362
|
+
const declared = settingsOf(root)?.triggerCases;
|
|
371
363
|
const rel = declared === undefined ? DEFAULT_LOCAL_CASES : declared;
|
|
372
364
|
if (typeof rel !== "string" || rel.length === 0)
|
|
373
365
|
throw new TypeError(
|
|
@@ -382,9 +374,9 @@ async function consumerCases() {
|
|
|
382
374
|
throw new Error(
|
|
383
375
|
`${CONFIG_KEY}: could not load the consumer's trigger cases from ${abs}` +
|
|
384
376
|
(declared === undefined
|
|
385
|
-
? ` (the default location; declare another in
|
|
386
|
-
`
|
|
387
|
-
: ` — it is declared in
|
|
377
|
+
? ` (the default location; declare another in paperlint.json as ` +
|
|
378
|
+
`{ "triggerCases": … } if the file lives elsewhere).`
|
|
379
|
+
: ` — it is declared in paperlint.json as "triggerCases": ${JSON.stringify(declared)}.`) +
|
|
388
380
|
`
|
|
389
381
|
A missing case set does not shrink the measurement, it CHANGES it: ` +
|
|
390
382
|
`\`irrelevantFor()\` builds the false-positive set out of a case's colliders, so an ` +
|
package/package.json
CHANGED
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
"why": "A *.harness.* file tests the agent surface and imports runHook, runHarnessTest or runEval from vigiles. Plain unit tests are *.test.ts, run by vitest. These are the harness files that existed on main on 2026-09-25 and import none of the three. The list only shrinks: rename a file to *.test.ts (issue #77), or make it a real harness, then delete its entry. Checked by scripts/harness-api.test.ts, part of npm test.",
|
|
4
4
|
"harnesses": [
|
|
5
5
|
"action.harness.mjs",
|
|
6
|
-
"eslint-rules/doc-fields.harness.mjs",
|
|
7
6
|
"eslint-rules/install-path-literals.harness.mjs",
|
|
8
7
|
"eslint-rules/latex-language.harness.mjs",
|
|
9
8
|
"eslint-rules/paper-research-question.harness.mjs",
|
|
10
9
|
"eslint-rules/paper-stages.harness.mjs",
|
|
11
|
-
"eslint-rules/paper-typography.harness.mjs",
|
|
12
10
|
"eslint-rules/papers.harness.mjs",
|
|
13
11
|
"eslint-rules/pdf-last-page-balance.harness.mjs",
|
|
14
|
-
"eslint-rules/review-findings-cause.harness.mjs",
|
|
15
12
|
"eslint-rules/temp-root-realpath.harness.mjs",
|
|
16
13
|
"eslint-rules/tex-build.harness.mjs",
|
|
17
14
|
"fixtures/real-markdown-paper/real-markdown-paper.harness.mjs",
|
|
@@ -5,9 +5,6 @@
|
|
|
5
5
|
"action.mutations.mjs": {
|
|
6
6
|
"cases": 6
|
|
7
7
|
},
|
|
8
|
-
"eslint-rules/doc-fields.mutations.mjs": {
|
|
9
|
-
"cases": 6
|
|
10
|
-
},
|
|
11
8
|
"eslint-rules/install-path-literals.mutations.mjs": {
|
|
12
9
|
"cases": 3
|
|
13
10
|
},
|
|
@@ -18,17 +15,11 @@
|
|
|
18
15
|
"cases": 6
|
|
19
16
|
},
|
|
20
17
|
"eslint-rules/paper-stages.mutations.mjs": {
|
|
21
|
-
"cases":
|
|
22
|
-
},
|
|
23
|
-
"eslint-rules/paper-typography.mutations.mjs": {
|
|
24
|
-
"cases": 6
|
|
18
|
+
"cases": 4
|
|
25
19
|
},
|
|
26
20
|
"eslint-rules/papers.mutations.mjs": {
|
|
27
21
|
"cases": 10
|
|
28
22
|
},
|
|
29
|
-
"eslint-rules/review-findings-cause.mutations.mjs": {
|
|
30
|
-
"cases": 4
|
|
31
|
-
},
|
|
32
23
|
"eslint-rules/temp-root-realpath.mutations.mjs": {
|
|
33
24
|
"cases": 5
|
|
34
25
|
},
|
|
@@ -36,7 +27,7 @@
|
|
|
36
27
|
"cases": 21
|
|
37
28
|
},
|
|
38
29
|
"fixtures/real-markdown-paper/real-markdown-paper.mutations.mjs": {
|
|
39
|
-
"cases":
|
|
30
|
+
"cases": 3
|
|
40
31
|
},
|
|
41
32
|
"hooks/hooks.mutations.mjs": {
|
|
42
33
|
"cases": 23
|
|
@@ -96,13 +87,13 @@
|
|
|
96
87
|
"cases": 11
|
|
97
88
|
},
|
|
98
89
|
"src/cli.mutations.mjs": {
|
|
99
|
-
"cases":
|
|
90
|
+
"cases": 25
|
|
100
91
|
},
|
|
101
92
|
"src/doctor.mutations.mjs": {
|
|
102
93
|
"cases": 10
|
|
103
94
|
},
|
|
104
95
|
"src/hooks-settings.mutations.mjs": {
|
|
105
|
-
"cases":
|
|
96
|
+
"cases": 6
|
|
106
97
|
},
|
|
107
98
|
"src/link-skills.mutations.mjs": {
|
|
108
99
|
"cases": 6
|
|
@@ -5,7 +5,7 @@ context: fork
|
|
|
5
5
|
allowed-tools: [WebSearch, WebFetch, Read, Write, Grep, Glob, Bash, Agent]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
<!-- vigiles:sha256:
|
|
8
|
+
<!-- vigiles:sha256:f528cbe9e892d6d0 compiled from skills/analyze-sibling-paper/SKILL.md.spec.ts -->
|
|
9
9
|
|
|
10
10
|
# analyze-sibling-paper — settle "isn't this just X?" before a reviewer does
|
|
11
11
|
|
|
@@ -44,9 +44,9 @@ far below the cost of a "novelty concern" reject.
|
|
|
44
44
|
abstract → intro → method → claims (as much as is accessible). An abstract hides exactly the overlap
|
|
45
45
|
that matters. Try the fetch routes in order — `arxiv.org/abs/<id>` for metadata, `arxiv.org/html/<id>v1`,
|
|
46
46
|
`ar5iv.labs.arxiv.org/html/<id>`, then the PDF — and **a 403/404 on one route is not "inaccessible"**;
|
|
47
|
-
switch channel before concluding. **Record what you actually read** in the card as
|
|
48
|
-
`**Read:**` line
|
|
49
|
-
verdict is PROVISIONAL and must say so
|
|
47
|
+
switch channel before concluding. **Record what you actually read** in the card's frontmatter as
|
|
48
|
+
`read: full | abstract | none`, and name in a `**Read:**` line the sections you covered and the
|
|
49
|
+
ones you could not — an abstract-only verdict is PROVISIONAL and must say so.
|
|
50
50
|
🔴 **Verify the abstract's numbers against the body.** They disagree more often than you would expect,
|
|
51
51
|
and a disagreement is itself a finding worth recording.
|
|
52
52
|
|
|
@@ -87,17 +87,21 @@ far below the cost of a "novelty concern" reject.
|
|
|
87
87
|
|
|
88
88
|
## Required shape of the saved card — two sections are machine-checked
|
|
89
89
|
|
|
90
|
-
The card is not free-form
|
|
91
|
-
(`checkSiblingCards`) and a card missing either is reported as an unfinished analysis:
|
|
90
|
+
The card is not free-form:
|
|
92
91
|
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
card that
|
|
92
|
+
- **`read:` in the frontmatter** — `full`, `abstract` or `none`: how much of the full text you
|
|
93
|
+
actually read. `paperlint lint` checks it (`sibling/frontmatter`, a warning on a card without it).
|
|
94
|
+
It exists because "I read the paper" is the single easiest thing to skip while producing a card that
|
|
95
|
+
looks complete. The `**Read:**` line under it names which sections.
|
|
96
96
|
- **`## Sibling's References`** — their bibliography mined and diffed against yours (step 2), with the
|
|
97
97
|
MUST CITE / SHOULD CONSIDER / NOT RELEVANT verdicts.
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
```yaml
|
|
100
|
+
---
|
|
101
|
+
title: "Smith et al. 2025 — <title>"
|
|
102
|
+
read: abstract # full | abstract | none
|
|
103
|
+
---
|
|
104
|
+
```
|
|
101
105
|
|
|
102
106
|
## Save + surface — COLOCATED with the paper (mandatory — do not leave it in context)
|
|
103
107
|
Each paper's competitive landscape lives **with that paper**, not in a shared research folder: write the
|
|
@@ -136,7 +140,7 @@ would have overwritten one another.
|
|
|
136
140
|
|
|
137
141
|
## Rules
|
|
138
142
|
- **Read the paper, not the abstract.** An abstract-only "delta" is not a verdict — and the card must
|
|
139
|
-
say
|
|
143
|
+
say so: `read: abstract` in its frontmatter, and which parts in its `**Read:**` line.
|
|
140
144
|
- **Mine their references.** A sibling analysis that never opened the sibling's bibliography is half done;
|
|
141
145
|
the scoop you missed is more likely to be in their reference list than in their results.
|
|
142
146
|
- **Classify by contribution type** before judging overlap — it's the difference between a shared concept and a collision.
|
|
@@ -59,9 +59,9 @@ far below the cost of a "novelty concern" reject.
|
|
|
59
59
|
abstract → intro → method → claims (as much as is accessible). An abstract hides exactly the overlap
|
|
60
60
|
that matters. Try the fetch routes in order — \`arxiv.org/abs/<id>\` for metadata, \`arxiv.org/html/<id>v1\`,
|
|
61
61
|
\`ar5iv.labs.arxiv.org/html/<id>\`, then the PDF — and **a 403/404 on one route is not "inaccessible"**;
|
|
62
|
-
switch channel before concluding. **Record what you actually read** in the card as
|
|
63
|
-
\`**Read:**\` line
|
|
64
|
-
verdict is PROVISIONAL and must say so
|
|
62
|
+
switch channel before concluding. **Record what you actually read** in the card's frontmatter as
|
|
63
|
+
\`read: full | abstract | none\`, and name in a \`**Read:**\` line the sections you covered and the
|
|
64
|
+
ones you could not — an abstract-only verdict is PROVISIONAL and must say so.
|
|
65
65
|
🔴 **Verify the abstract's numbers against the body.** They disagree more often than you would expect,
|
|
66
66
|
and a disagreement is itself a finding worth recording.
|
|
67
67
|
|
|
@@ -102,17 +102,21 @@ far below the cost of a "novelty concern" reject.
|
|
|
102
102
|
|
|
103
103
|
## Required shape of the saved card — two sections are machine-checked
|
|
104
104
|
|
|
105
|
-
The card is not free-form
|
|
106
|
-
(\`checkSiblingCards\`) and a card missing either is reported as an unfinished analysis:
|
|
105
|
+
The card is not free-form:
|
|
107
106
|
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
card that
|
|
107
|
+
- **\`read:\` in the frontmatter** — \`full\`, \`abstract\` or \`none\`: how much of the full text you
|
|
108
|
+
actually read. \`paperlint lint\` checks it (\`sibling/frontmatter\`, a warning on a card without it).
|
|
109
|
+
It exists because "I read the paper" is the single easiest thing to skip while producing a card that
|
|
110
|
+
looks complete. The \`**Read:**\` line under it names which sections.
|
|
111
111
|
- **\`## Sibling's References\`** — their bibliography mined and diffed against yours (step 2), with the
|
|
112
112
|
MUST CITE / SHOULD CONSIDER / NOT RELEVANT verdicts.
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
\`\`\`yaml
|
|
115
|
+
---
|
|
116
|
+
title: "Smith et al. 2025 — <title>"
|
|
117
|
+
read: abstract # full | abstract | none
|
|
118
|
+
---
|
|
119
|
+
\`\`\`
|
|
116
120
|
|
|
117
121
|
## Save + surface — COLOCATED with the paper (mandatory — do not leave it in context)
|
|
118
122
|
Each paper's competitive landscape lives **with that paper**, not in a shared research folder: write the
|
|
@@ -151,7 +155,7 @@ would have overwritten one another.
|
|
|
151
155
|
|
|
152
156
|
## Rules
|
|
153
157
|
- **Read the paper, not the abstract.** An abstract-only "delta" is not a verdict — and the card must
|
|
154
|
-
say
|
|
158
|
+
say so: \`read: abstract\` in its frontmatter, and which parts in its \`**Read:**\` line.
|
|
155
159
|
- **Mine their references.** A sibling analysis that never opened the sibling's bibliography is half done;
|
|
156
160
|
the scoop you missed is more likely to be in their reference list than in their results.
|
|
157
161
|
- **Classify by contribution type** before judging overlap — it's the difference between a shared concept and a collision.
|
|
@@ -5,7 +5,7 @@ context: fork
|
|
|
5
5
|
allowed-tools: [Bash, Read, Agent]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
<!-- vigiles:sha256:
|
|
8
|
+
<!-- vigiles:sha256:596fbd7e2eb85252 compiled from skills/cold-read-diff/SKILL.md.spec.ts -->
|
|
9
9
|
|
|
10
10
|
# cold-read-diff — the reader who cannot fake understanding
|
|
11
11
|
|
|
@@ -101,6 +101,25 @@ Two more things the same run proved:
|
|
|
101
101
|
comments, and hand the reader continuous prose. The findings that survive that are worth the run; the
|
|
102
102
|
ones that do not are noise, and noise is how this check gets muted.
|
|
103
103
|
|
|
104
|
+
## The report's frontmatter — the findings, as records
|
|
105
|
+
|
|
106
|
+
The report opens with YAML frontmatter that lists what it found, one record per finding.
|
|
107
|
+
`paperlint lint` validates it (`review/frontmatter`): an **open** finding must name the
|
|
108
|
+
pipeline `cause` that let it through — fix that, and the text edit falls out of running the
|
|
109
|
+
fixed tool.
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
---
|
|
113
|
+
findings:
|
|
114
|
+
- id: 1
|
|
115
|
+
status: open # open | fixed | wontfix
|
|
116
|
+
cause: missing-skill # skill-defect | missing-skill | hook | rule — required when open
|
|
117
|
+
title: "§3 opens on a term the reader has not met"
|
|
118
|
+
- id: 2
|
|
119
|
+
status: fixed
|
|
120
|
+
---
|
|
121
|
+
```
|
|
122
|
+
|
|
104
123
|
## Record the verdict
|
|
105
124
|
|
|
106
125
|
🔴 LAST step, once the deliverable exists:
|
|
@@ -106,6 +106,25 @@ Two more things the same run proved:
|
|
|
106
106
|
comments, and hand the reader continuous prose. The findings that survive that are worth the run; the
|
|
107
107
|
ones that do not are noise, and noise is how this check gets muted.
|
|
108
108
|
|
|
109
|
+
## The report's frontmatter — the findings, as records
|
|
110
|
+
|
|
111
|
+
The report opens with YAML frontmatter that lists what it found, one record per finding.
|
|
112
|
+
\`paperlint lint\` validates it (\`review/frontmatter\`): an **open** finding must name the
|
|
113
|
+
pipeline \`cause\` that let it through — fix that, and the text edit falls out of running the
|
|
114
|
+
fixed tool.
|
|
115
|
+
|
|
116
|
+
\`\`\`yaml
|
|
117
|
+
---
|
|
118
|
+
findings:
|
|
119
|
+
- id: 1
|
|
120
|
+
status: open # open | fixed | wontfix
|
|
121
|
+
cause: missing-skill # skill-defect | missing-skill | hook | rule — required when open
|
|
122
|
+
title: "§3 opens on a term the reader has not met"
|
|
123
|
+
- id: 2
|
|
124
|
+
status: fixed
|
|
125
|
+
---
|
|
126
|
+
\`\`\`
|
|
127
|
+
|
|
109
128
|
## Record the verdict
|
|
110
129
|
|
|
111
130
|
🔴 LAST step, once the deliverable exists:
|
|
@@ -4,7 +4,7 @@ description: "Discover and rank real venues for a given paper, scored by authors
|
|
|
4
4
|
allowed-tools: [Read, Write, Grep, Glob, WebSearch, WebFetch, Agent, 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:5efdcc0e1b178c1a compiled from skills/find-venue/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# find-venue — rank real venues by what earns the credit, then keep or switch
|
|
10
10
|
|
|
@@ -113,6 +113,10 @@ dropped and why, and a verdict with no table behind it cannot be re-checked when
|
|
|
113
113
|
## Compose with
|
|
114
114
|
- `research-ideate` (upstream) — consumes its candidate venue *types* and sharpest framing.
|
|
115
115
|
- `plan-paper-timeline` — feed it the chosen deadline to back-plan the work.
|
|
116
|
+
- once a venue is chosen, the paper declares it in `<paper>/paperlint.json`:
|
|
117
|
+
`{ "extends": "paperlint:<venue>", "kind": "<kind>" }` — a shipped preset, else a family
|
|
118
|
+
(`paperlint:acm-sigconf`) or the project's own `./venues/<name>.jsonc`; the `pdf/*` rules then
|
|
119
|
+
check the built PDF against its page limit and format.
|
|
116
120
|
- `submit-paper` venue data cards (`submit-paper/references/venues/<venue>.md`, e.g. `agenticdev.md`,
|
|
117
121
|
`aisec.md`) — the winner gets a venue-specific data card with its HotCRP quirks; save a new one per
|
|
118
122
|
venue as plain data, not a new skill.
|
|
@@ -129,6 +129,10 @@ dropped and why, and a verdict with no table behind it cannot be re-checked when
|
|
|
129
129
|
## Compose with
|
|
130
130
|
- \`research-ideate\` (upstream) — consumes its candidate venue *types* and sharpest framing.
|
|
131
131
|
- \`plan-paper-timeline\` — feed it the chosen deadline to back-plan the work.
|
|
132
|
+
- once a venue is chosen, the paper declares it in \`<paper>/paperlint.json\`:
|
|
133
|
+
\`{ "extends": "paperlint:<venue>", "kind": "<kind>" }\` — a shipped preset, else a family
|
|
134
|
+
(\`paperlint:acm-sigconf\`) or the project's own \`./venues/<name>.jsonc\`; the \`pdf/*\` rules then
|
|
135
|
+
check the built PDF against its page limit and format.
|
|
132
136
|
- \`submit-paper\` venue data cards (\`submit-paper/references/venues/<venue>.md\`, e.g. \`agenticdev.md\`,
|
|
133
137
|
\`aisec.md\`) — the winner gets a venue-specific data card with its HotCRP quirks; save a new one per
|
|
134
138
|
venue as plain data, not a new skill.
|