paperlint 2.1.0 → 3.1.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/README.md +243 -235
- 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 +6 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +15 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +60 -7
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +94 -30
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +500 -288
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +40 -72
- package/dist/doctor.js.map +1 -1
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -0
- package/dist/engine.js.map +1 -1
- package/dist/facts-file.d.ts +2 -2
- package/dist/facts-file.js +2 -2
- 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 +39 -29
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +144 -173
- 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 +8 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +49 -25
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +85 -0
- package/dist/paper-files.d.ts.map +1 -0
- package/dist/paper-files.js +96 -0
- package/dist/paper-files.js.map +1 -0
- package/dist/paper-settings.d.ts +13 -41
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +71 -71
- package/dist/paper-settings.js.map +1 -1
- 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 +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +4 -6
- package/dist/presets.js.map +1 -1
- 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 +5 -3
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +15 -6
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.js +3 -3
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.js +2 -2
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts +5 -1
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +22 -5
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +26 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +12 -10
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +163 -104
- package/docs/e2e.md +1 -1
- package/docs/install.md +25 -40
- package/docs/optional-rules.md +5 -4
- package/docs/rules.md +65 -24
- package/docs/skills.md +80 -0
- 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 +306 -191
- package/eslint-rules/paper-typography.test.mjs +337 -0
- package/eslint-rules/papers.harness.mjs +8 -12
- package/eslint-rules/papers.mjs +17 -31
- package/eslint-rules/pdf-last-page-balance.mjs +4 -7
- 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/real-markdown-paper/baseline.json +6 -7
- package/fixtures/toolchain-mirror/catalog.txt +1 -0
- package/fixtures/toolchain-mirror/install-tl +4 -1
- package/fixtures/toolchain-mirror/stub-fmtutil-sys +4 -0
- package/fixtures/toolchain-mirror/stub-tlmgr +15 -0
- 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 -25
- package/lib/paper-config.harness.mjs +18 -35
- package/lib/paper-config.mjs +83 -105
- 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 +5 -14
- 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/grade-paper-writing/SKILL.md +20 -1
- package/skills/grade-paper-writing/SKILL.md.spec.ts +19 -0
- 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/render-paper.harness.mjs +2 -2
- 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 +6 -1
- package/src/build.harness.mjs +54 -6
- package/src/build.ts +89 -11
- package/src/cli.harness.mjs +149 -294
- package/src/cli.mutations.mjs +35 -44
- package/src/cli.ts +637 -328
- package/src/doctor.harness.mjs +41 -84
- package/src/doctor.mutations.mjs +9 -9
- package/src/doctor.ts +44 -89
- package/src/engine.harness.mjs +50 -0
- package/src/engine.ts +35 -0
- package/src/facts-file.test.ts +0 -7
- package/src/facts-file.ts +2 -2
- 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 +187 -231
- 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/linted-set.test.ts +118 -0
- package/src/new-paper.harness.mjs +2 -0
- package/src/new-paper.mutations.mjs +0 -14
- package/src/new-paper.test.ts +173 -14
- package/src/new-paper.ts +82 -30
- package/src/paper-files.test.ts +395 -0
- package/src/paper-files.ts +200 -0
- package/src/paper-settings-commands.test.ts +121 -122
- package/src/paper-settings.test.ts +93 -64
- package/src/paper-settings.ts +99 -97
- package/src/parse-args.test.ts +48 -0
- package/src/ports/check-references.ts +28 -0
- package/src/presets.ts +5 -15
- 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 +19 -7
- package/src/rules-off.test.ts +111 -0
- package/src/structure.harness.mjs +4 -2
- package/src/structure.ts +3 -3
- package/src/tex-requirements.ts +2 -2
- package/src/toolchain.harness.mjs +41 -6
- package/src/toolchain.ts +29 -5
- package/src/types.ts +36 -18
- package/src/venue-rules.test.ts +23 -11
- package/src/venue-rules.ts +12 -14
- package/templates/paper/paperlint.json +1 -1
- 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/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
|
@@ -36,7 +36,6 @@ const {
|
|
|
36
36
|
shippedWiring,
|
|
37
37
|
wireHooks,
|
|
38
38
|
wiredCounts,
|
|
39
|
-
pluginEnabledHere,
|
|
40
39
|
doctorHooks,
|
|
41
40
|
readSettings,
|
|
42
41
|
MANAGED_BY,
|
|
@@ -83,36 +82,26 @@ try {
|
|
|
83
82
|
// ── which spelling runs which hook ─────────────────────────────────────────────────────
|
|
84
83
|
const ours = `node "\${CLAUDE_PROJECT_DIR}/${MANAGED_BY}" hook paper-edit-guard`;
|
|
85
84
|
const cases = [
|
|
86
|
-
[ours, { name: "paper-edit-guard", ours: true
|
|
85
|
+
[ours, { name: "paper-edit-guard", ours: true }],
|
|
87
86
|
[
|
|
88
87
|
`node "$CLAUDE_PROJECT_DIR/${MANAGED_BY}" hook paper-status-gates`,
|
|
89
|
-
{ name: "paper-status-gates", ours: true
|
|
90
|
-
],
|
|
91
|
-
// Guards: what an older install wrote is recognised as ours-but-stale. Spelled literally, not
|
|
92
|
-
// built from a constant: these are what 1.x and 2.0.0 actually put in users' settings files.
|
|
93
|
-
[
|
|
94
|
-
`node "$CLAUDE_PROJECT_DIR/node_modules/research-paper-pipeline/bin/rpp.mjs" hook paper-edit-guard`,
|
|
95
|
-
{ name: "paper-edit-guard", ours: false, legacy: true },
|
|
96
|
-
],
|
|
97
|
-
[
|
|
98
|
-
`node "\${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/rpp.mjs" hook paper-status-gates`,
|
|
99
|
-
{ name: "paper-status-gates", ours: false, legacy: true },
|
|
88
|
+
{ name: "paper-status-gates", ours: true },
|
|
100
89
|
],
|
|
101
90
|
[
|
|
102
91
|
`node "$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program "$CLAUDE_PROJECT_DIR/node_modules/paperlint/hooks/paper-edit-guard.hook.mjs"`,
|
|
103
|
-
{ name: "paper-edit-guard", ours: false
|
|
92
|
+
{ name: "paper-edit-guard", ours: false },
|
|
104
93
|
],
|
|
105
94
|
[
|
|
106
95
|
`npx paperlint hook paper-skills-nudge`,
|
|
107
|
-
{ name: "paper-skills-nudge", ours: false
|
|
96
|
+
{ name: "paper-skills-nudge", ours: false },
|
|
108
97
|
],
|
|
109
98
|
[
|
|
110
99
|
`npx paperlint hook paper-skills-nudge`,
|
|
111
|
-
{ name: "paper-skills-nudge", ours: false
|
|
100
|
+
{ name: "paper-skills-nudge", ours: false },
|
|
112
101
|
],
|
|
113
102
|
[
|
|
114
103
|
`node /abs/proj/node_modules/paperlint/bin/paperlint.mjs hook paper-edit-guard`,
|
|
115
|
-
{ name: "paper-edit-guard", ours: false
|
|
104
|
+
{ name: "paper-edit-guard", ours: false },
|
|
116
105
|
],
|
|
117
106
|
[`node my-own-lint.mjs`, null],
|
|
118
107
|
[`node node_modules/paperlint/bin/paperlint.mjs lint`, null],
|
|
@@ -158,46 +147,6 @@ try {
|
|
|
158
147
|
);
|
|
159
148
|
}
|
|
160
149
|
|
|
161
|
-
// ── what an older install wrote is migrated, the user's own command kept ─────────────────
|
|
162
|
-
// Two real histories, spelled literally: 1.x wrote the old package directory, 2.0.0 wrote the
|
|
163
|
-
// new directory with the old entry file name. Neither file exists after an upgrade.
|
|
164
|
-
for (const [release, stale] of [
|
|
165
|
-
["1.x", "node_modules/research-paper-pipeline/bin/rpp.mjs"],
|
|
166
|
-
["2.0.0", "node_modules/paperlint/bin/rpp.mjs"],
|
|
167
|
-
]) {
|
|
168
|
-
const legacyWired = JSON.parse(
|
|
169
|
-
JSON.stringify(merge({}, wiring.compiled, MANAGED_BY)).replaceAll(
|
|
170
|
-
MANAGED_BY,
|
|
171
|
-
stale,
|
|
172
|
-
),
|
|
173
|
-
);
|
|
174
|
-
legacyWired.hooks.PostToolUse[0].hooks.push({
|
|
175
|
-
type: "command",
|
|
176
|
-
command: "node my-own-lint.mjs",
|
|
177
|
-
});
|
|
178
|
-
const dir = project(`legacy-${release}`, legacyWired);
|
|
179
|
-
const before = doctorHooks(dir, wiring).join("\n");
|
|
180
|
-
check(
|
|
181
|
-
`🔴 doctor names hook commands ${release} left behind — the missing file, and the fix`,
|
|
182
|
-
before.includes(`run ${stale}, which this version does not install`) &&
|
|
183
|
-
/npx paperlint init` replaces them/.test(before),
|
|
184
|
-
);
|
|
185
|
-
const r = wireHooks(dir, merge, wiring);
|
|
186
|
-
const s = JSON.parse(text(dir));
|
|
187
|
-
const counts = [...wiredCounts(s, wiring.names).values()];
|
|
188
|
-
check(
|
|
189
|
-
`🔴 init replaces what ${release} wrote: every hook wired once, none left at the old path`,
|
|
190
|
-
r.status === "written" &&
|
|
191
|
-
r.replaced === wiring.names.length &&
|
|
192
|
-
counts.every((c) => c.ours === 1 && c.legacy === 0 && c.other === 0) &&
|
|
193
|
-
!text(dir).includes(stale),
|
|
194
|
-
);
|
|
195
|
-
check(
|
|
196
|
-
`and the user's own command in the same matcher survives the ${release} migration`,
|
|
197
|
-
text(dir).includes("node my-own-lint.mjs"),
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
150
|
// ── the user's own hooks and keys survive ──────────────────────────────────────────────
|
|
202
151
|
{
|
|
203
152
|
const dir = project("user", {
|
|
@@ -250,7 +199,7 @@ try {
|
|
|
250
199
|
{
|
|
251
200
|
type: "command",
|
|
252
201
|
command:
|
|
253
|
-
'node "$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program "$CLAUDE_PROJECT_DIR/node_modules/
|
|
202
|
+
'node "$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program "$CLAUDE_PROJECT_DIR/node_modules/paperlint/hooks/paper-edit-guard.hook.mjs"',
|
|
254
203
|
},
|
|
255
204
|
],
|
|
256
205
|
},
|
|
@@ -296,25 +245,6 @@ try {
|
|
|
296
245
|
);
|
|
297
246
|
}
|
|
298
247
|
|
|
299
|
-
// ── the plugin, enabled by the project ─────────────────────────────────────────────────
|
|
300
|
-
check(
|
|
301
|
-
"the project's enabledPlugins entry for this package is found",
|
|
302
|
-
pluginEnabledHere({
|
|
303
|
-
enabledPlugins: {
|
|
304
|
-
"research-paper-pipeline@research-paper-pipeline": true,
|
|
305
|
-
"other@x": true,
|
|
306
|
-
},
|
|
307
|
-
}).join() === "research-paper-pipeline@research-paper-pipeline",
|
|
308
|
-
);
|
|
309
|
-
check(
|
|
310
|
-
"a disabled entry does not count",
|
|
311
|
-
pluginEnabledHere({
|
|
312
|
-
enabledPlugins: {
|
|
313
|
-
"research-paper-pipeline@research-paper-pipeline": false,
|
|
314
|
-
},
|
|
315
|
-
}).length === 0,
|
|
316
|
-
);
|
|
317
|
-
|
|
318
248
|
// ── doctor's section ───────────────────────────────────────────────────────────────────
|
|
319
249
|
const doc = (settings) => {
|
|
320
250
|
const dir = project(`doc-${String(n)}`, settings);
|
|
@@ -361,21 +291,6 @@ try {
|
|
|
361
291
|
handPartial,
|
|
362
292
|
) && /writes nothing then/.test(handPartial),
|
|
363
293
|
);
|
|
364
|
-
const pluginText = doc({
|
|
365
|
-
...wiredOnce,
|
|
366
|
-
enabledPlugins: { "research-paper-pipeline@research-paper-pipeline": true },
|
|
367
|
-
});
|
|
368
|
-
check(
|
|
369
|
-
"the project also enabling the plugin → told to uninstall it, with the command",
|
|
370
|
-
/ALSO enables the plugin/.test(pluginText) &&
|
|
371
|
-
/\/plugin uninstall research-paper-pipeline@research-paper-pipeline/.test(
|
|
372
|
-
pluginText,
|
|
373
|
-
),
|
|
374
|
-
);
|
|
375
|
-
check(
|
|
376
|
-
"🔴 and it says it cannot see a USER-scope plugin, rather than implying there is none",
|
|
377
|
-
/USER scope is not visible from here/.test(doc({})),
|
|
378
|
-
);
|
|
379
294
|
check(
|
|
380
295
|
"an unparsable file is named in doctor, not crashed on",
|
|
381
296
|
/does not parse/.test(
|
|
@@ -24,12 +24,12 @@ process.exit(
|
|
|
24
24
|
harness: HARNESS,
|
|
25
25
|
// The spelling truth table is the first owner: `npx paperlint hook …` must read as NOT ours,
|
|
26
26
|
// and that row is where it goes red.
|
|
27
|
-
expect: '→ {"name":"paper-
|
|
27
|
+
expect: '→ {"name":"paper-skills-nudge","ours":false}',
|
|
28
28
|
disables:
|
|
29
29
|
"the duplicate guard for every bin spelling but ours. A hook wired by hand as " +
|
|
30
30
|
"`npx paperlint hook …` or through an absolute path to the bin reads as ours, init merges its own " +
|
|
31
31
|
"copy beside it, and the hook runs twice per event",
|
|
32
|
-
edits: [[SRC, "
|
|
32
|
+
edits: [[SRC, "ours: t === MANAGED_BY,", "ours: true,"]],
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
name: "another spelling no longer stops the write",
|
|
@@ -94,21 +94,6 @@ process.exit(
|
|
|
94
94
|
"same thing again — a loop with no exit",
|
|
95
95
|
edits: [[SRC, " const remedy = handWired", " const remedy = false"]],
|
|
96
96
|
},
|
|
97
|
-
{
|
|
98
|
-
name: "a disabled plugin counts as enabled",
|
|
99
|
-
harness: HARNESS,
|
|
100
|
-
expect: "a disabled entry does not count",
|
|
101
|
-
disables:
|
|
102
|
-
"the plugin warning's precision. A project that already switched the plugin off would " +
|
|
103
|
-
"be told to uninstall it",
|
|
104
|
-
edits: [
|
|
105
|
-
[
|
|
106
|
-
SRC,
|
|
107
|
-
' ([id, on]) => on === true && id.split("@")[0] === LEGACY_PACKAGE_NAME,',
|
|
108
|
-
' ([id]) => id.split("@")[0] === LEGACY_PACKAGE_NAME,',
|
|
109
|
-
],
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
97
|
],
|
|
113
98
|
}),
|
|
114
99
|
);
|
package/src/hooks-settings.ts
CHANGED
|
@@ -28,7 +28,6 @@ import { basename, dirname, join, posix } from "node:path";
|
|
|
28
28
|
import { fileURLToPath } from "node:url";
|
|
29
29
|
import {
|
|
30
30
|
BIN_FILE,
|
|
31
|
-
LEGACY_PACKAGE_NAME,
|
|
32
31
|
PACKAGE_NAME,
|
|
33
32
|
} from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
34
33
|
|
|
@@ -47,9 +46,6 @@ export const WIRING_FILE = fileURLToPath(
|
|
|
47
46
|
*/
|
|
48
47
|
export const MANAGED_BY = `node_modules/${PACKAGE_NAME}/${BIN_FILE}`;
|
|
49
48
|
|
|
50
|
-
/** The names the package's directory under `node_modules` has had. */
|
|
51
|
-
const PKG_DIRS: readonly string[] = [PACKAGE_NAME, LEGACY_PACKAGE_NAME];
|
|
52
|
-
|
|
53
49
|
export interface HookCommand {
|
|
54
50
|
readonly type: string;
|
|
55
51
|
readonly command: string;
|
|
@@ -80,7 +76,6 @@ function bare(token: string): string {
|
|
|
80
76
|
interface Run {
|
|
81
77
|
readonly name: string;
|
|
82
78
|
readonly ours: boolean;
|
|
83
|
-
readonly legacy: boolean;
|
|
84
79
|
readonly path: string;
|
|
85
80
|
}
|
|
86
81
|
|
|
@@ -90,19 +85,11 @@ interface Run {
|
|
|
90
85
|
* Recognised spellings — each is ONE lexeme of a shell command, which is what a command is:
|
|
91
86
|
* `node <…>/node_modules/paperlint/bin/paperlint.mjs hook <name>`
|
|
92
87
|
* ours when the path is exactly MANAGED_BY
|
|
93
|
-
* `
|
|
94
|
-
*
|
|
95
|
-
* this version does not install — what an older `init` left behind
|
|
96
|
-
* (1.x: `research-paper-pipeline/bin/rpp.mjs`; 2.0.0: `paperlint/bin/rpp.mjs`)
|
|
97
|
-
* `npx paperlint hook <name>`, `node_modules/.bin/paperlint hook <name>` (or under the old package name),
|
|
98
|
-
* an absolute path into the package's `bin/`
|
|
88
|
+
* `npx paperlint hook <name>`, `node_modules/.bin/paperlint hook <name>`, an absolute path into
|
|
89
|
+
* the package's `bin/`
|
|
99
90
|
* another spelling
|
|
100
|
-
* `vigiles … run-program <…>/node_modules
|
|
91
|
+
* `vigiles … run-program <…>/node_modules/paperlint/hooks/<name>.hook.mjs`
|
|
101
92
|
* another spelling
|
|
102
|
-
* The last is how the one real consumer wired all three by hand before `init` could.
|
|
103
|
-
*
|
|
104
|
-
* `legacy` is decided by the SHAPE `init` writes, not by a list of past file names: any file in
|
|
105
|
-
* our `bin/` other than BIN_FILE is one that no longer ships, so a future rename is covered too.
|
|
106
93
|
*/
|
|
107
94
|
function parseRun(command: string): Run | null {
|
|
108
95
|
const tokens = command.trim().split(/\s+/).map(bare);
|
|
@@ -113,54 +100,37 @@ function parseRun(command: string): Run | null {
|
|
|
113
100
|
return null;
|
|
114
101
|
}
|
|
115
102
|
|
|
116
|
-
/** The path segments after `node_modules
|
|
117
|
-
function insidePackage(parts: readonly string[]): {
|
|
118
|
-
readonly inside: readonly string[];
|
|
119
|
-
readonly relative: boolean;
|
|
120
|
-
} {
|
|
103
|
+
/** The path segments after `node_modules/paperlint/`. */
|
|
104
|
+
function insidePackage(parts: readonly string[]): readonly string[] {
|
|
121
105
|
const at = parts.findIndex(
|
|
122
|
-
(p, j) => p === "node_modules" &&
|
|
106
|
+
(p, j) => p === "node_modules" && parts[j + 1] === PACKAGE_NAME,
|
|
123
107
|
);
|
|
124
|
-
return
|
|
125
|
-
inside: at === -1 ? [] : parts.slice(at + 2),
|
|
126
|
-
relative: at === 0,
|
|
127
|
-
};
|
|
108
|
+
return at === -1 ? [] : parts.slice(at + 2);
|
|
128
109
|
}
|
|
129
110
|
|
|
130
111
|
/** The run token `i` starts, if it names a paperlint hook. */
|
|
131
112
|
function runAt(tokens: readonly string[], i: number): Run | null {
|
|
132
113
|
const t = posix.normalize(tokens[i] ?? "");
|
|
133
|
-
const
|
|
114
|
+
const inside = insidePackage(t.split("/"));
|
|
134
115
|
const inBin = inside[0] === "bin" && inside.length === 2;
|
|
135
116
|
const name = tokens[i + 2];
|
|
136
117
|
if (
|
|
137
|
-
(inBin ||
|
|
118
|
+
(inBin || basename(t) === PACKAGE_NAME) &&
|
|
138
119
|
tokens[i + 1] === "hook" &&
|
|
139
120
|
name
|
|
140
121
|
)
|
|
141
|
-
return {
|
|
142
|
-
name,
|
|
143
|
-
ours: t === MANAGED_BY,
|
|
144
|
-
legacy: inBin && relative && t !== MANAGED_BY,
|
|
145
|
-
path: t,
|
|
146
|
-
};
|
|
122
|
+
return { name, ours: t === MANAGED_BY, path: t };
|
|
147
123
|
if (inside[0] === "hooks" && inside[1]?.endsWith(".hook.mjs"))
|
|
148
|
-
return {
|
|
149
|
-
name: basename(inside[1], ".hook.mjs"),
|
|
150
|
-
ours: false,
|
|
151
|
-
legacy: false,
|
|
152
|
-
path: t,
|
|
153
|
-
};
|
|
124
|
+
return { name: basename(inside[1], ".hook.mjs"), ours: false, path: t };
|
|
154
125
|
return null;
|
|
155
126
|
}
|
|
156
127
|
|
|
157
128
|
export function hookRun(command: string): {
|
|
158
129
|
readonly name: string;
|
|
159
130
|
readonly ours: boolean;
|
|
160
|
-
readonly legacy: boolean;
|
|
161
131
|
} | null {
|
|
162
132
|
const run = parseRun(command);
|
|
163
|
-
return run && { name: run.name, ours: run.ours
|
|
133
|
+
return run && { name: run.name, ours: run.ours };
|
|
164
134
|
}
|
|
165
135
|
|
|
166
136
|
/** Every command in a settings object, with the event it hangs off. */
|
|
@@ -205,69 +175,18 @@ export function shippedWiring(file: string = WIRING_FILE): Wiring {
|
|
|
205
175
|
export function wiredCounts(
|
|
206
176
|
settings: Settings,
|
|
207
177
|
names: readonly string[],
|
|
208
|
-
): Map<string, { ours: number;
|
|
209
|
-
const counts = new Map(
|
|
210
|
-
names.map((n) => [n, { ours: 0, legacy: 0, other: 0 }]),
|
|
211
|
-
);
|
|
178
|
+
): Map<string, { ours: number; other: number }> {
|
|
179
|
+
const counts = new Map(names.map((n) => [n, { ours: 0, other: 0 }]));
|
|
212
180
|
for (const { command } of commandsIn(settings)) {
|
|
213
181
|
const run = hookRun(command);
|
|
214
182
|
const slot = run ? counts.get(run.name) : undefined;
|
|
215
183
|
if (run && slot)
|
|
216
184
|
if (run.ours) slot.ours++;
|
|
217
|
-
else if (run.legacy) slot.legacy++;
|
|
218
185
|
else slot.other++;
|
|
219
186
|
}
|
|
220
187
|
return counts;
|
|
221
188
|
}
|
|
222
189
|
|
|
223
|
-
/**
|
|
224
|
-
* The settings with every command an install under the old package name wrote removed, and the
|
|
225
|
-
* matcher blocks and events left empty by that removal dropped. The user's own commands stay.
|
|
226
|
-
*/
|
|
227
|
-
export function withoutLegacy(settings: Settings): {
|
|
228
|
-
readonly settings: Settings;
|
|
229
|
-
readonly removed: number;
|
|
230
|
-
} {
|
|
231
|
-
const hooks = settings["hooks"];
|
|
232
|
-
if (!hooks || typeof hooks !== "object") return { settings, removed: 0 };
|
|
233
|
-
let removed = 0;
|
|
234
|
-
const next: Record<string, HookEntry[]> = {};
|
|
235
|
-
for (const [event, entries] of Object.entries(hooks as HooksMap)) {
|
|
236
|
-
const kept: HookEntry[] = [];
|
|
237
|
-
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
238
|
-
const list: readonly HookCommand[] = Array.isArray(entry?.hooks)
|
|
239
|
-
? entry.hooks
|
|
240
|
-
: [];
|
|
241
|
-
const own = list.filter((h) => {
|
|
242
|
-
const legacy =
|
|
243
|
-
typeof h?.command === "string" && hookRun(h.command)?.legacy === true;
|
|
244
|
-
if (legacy) removed++;
|
|
245
|
-
return !legacy;
|
|
246
|
-
});
|
|
247
|
-
if (own.length > 0 || list.length === 0)
|
|
248
|
-
kept.push({ ...entry, hooks: own });
|
|
249
|
-
}
|
|
250
|
-
if (kept.length > 0) next[event] = kept;
|
|
251
|
-
}
|
|
252
|
-
return removed === 0
|
|
253
|
-
? { settings, removed }
|
|
254
|
-
: { settings: { ...settings, hooks: next }, removed };
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Plugin ids the PROJECT enables for this package. A user-scope plugin lives in the user's own
|
|
259
|
-
* settings, which this cannot see — callers must say so rather than print "not enabled".
|
|
260
|
-
*/
|
|
261
|
-
export function pluginEnabledHere(settings: Settings): string[] {
|
|
262
|
-
const enabled = settings["enabledPlugins"];
|
|
263
|
-
if (!enabled || typeof enabled !== "object") return [];
|
|
264
|
-
return Object.entries(enabled as Record<string, unknown>)
|
|
265
|
-
.filter(
|
|
266
|
-
([id, on]) => on === true && id.split("@")[0] === LEGACY_PACKAGE_NAME,
|
|
267
|
-
)
|
|
268
|
-
.map(([id]) => id);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
190
|
export type SettingsRead =
|
|
272
191
|
| { readonly status: "absent"; readonly path: string; readonly settings: {} }
|
|
273
192
|
| {
|
|
@@ -301,9 +220,6 @@ export type WireResult =
|
|
|
301
220
|
readonly status: "written" | "present";
|
|
302
221
|
readonly path: string;
|
|
303
222
|
readonly names: readonly string[];
|
|
304
|
-
readonly plugin: readonly string[];
|
|
305
|
-
/** Commands an install under the old package name wrote, removed before the merge. */
|
|
306
|
-
readonly replaced: number;
|
|
307
223
|
}
|
|
308
224
|
| {
|
|
309
225
|
readonly status: "foreign";
|
|
@@ -316,7 +232,6 @@ export type WireResult =
|
|
|
316
232
|
}[];
|
|
317
233
|
/** Shipped hooks that no command runs in ANY spelling — left unwired as well. */
|
|
318
234
|
readonly missing: readonly string[];
|
|
319
|
-
readonly plugin: readonly string[];
|
|
320
235
|
}
|
|
321
236
|
| {
|
|
322
237
|
readonly status: "unparsable";
|
|
@@ -342,11 +257,7 @@ export function wireHooks(
|
|
|
342
257
|
): WireResult {
|
|
343
258
|
const read = readSettings(root);
|
|
344
259
|
if (read.status === "unparsable") return read;
|
|
345
|
-
const { path } = read;
|
|
346
|
-
// Commands from an install under the old package name point at a directory that is gone after
|
|
347
|
-
// the upgrade. They are ours, so they are replaced rather than counted as someone else's.
|
|
348
|
-
const { settings, removed } = withoutLegacy(read.settings);
|
|
349
|
-
const plugin = pluginEnabledHere(settings);
|
|
260
|
+
const { path, settings } = read;
|
|
350
261
|
const found = commandsIn(settings)
|
|
351
262
|
.map(({ command }) => ({ command, run: hookRun(command) }))
|
|
352
263
|
.filter(({ run }) => run && !run.ours && wiring.names.includes(run.name))
|
|
@@ -356,63 +267,20 @@ export function wireHooks(
|
|
|
356
267
|
const missing = wiring.names.filter(
|
|
357
268
|
(n) => (counts.get(n)?.ours ?? 0) + (counts.get(n)?.other ?? 0) === 0,
|
|
358
269
|
);
|
|
359
|
-
return {
|
|
360
|
-
status: "foreign",
|
|
361
|
-
path,
|
|
362
|
-
names: wiring.names,
|
|
363
|
-
found,
|
|
364
|
-
missing,
|
|
365
|
-
plugin,
|
|
366
|
-
};
|
|
270
|
+
return { status: "foreign", path, names: wiring.names, found, missing };
|
|
367
271
|
}
|
|
368
272
|
|
|
369
273
|
const next = merge(settings, wiring.compiled, MANAGED_BY);
|
|
370
274
|
if (JSON.stringify(next) === JSON.stringify(read.settings))
|
|
371
|
-
return {
|
|
372
|
-
status: "present",
|
|
373
|
-
path,
|
|
374
|
-
names: wiring.names,
|
|
375
|
-
plugin,
|
|
376
|
-
replaced: 0,
|
|
377
|
-
};
|
|
275
|
+
return { status: "present", path, names: wiring.names };
|
|
378
276
|
mkdirSync(dirname(path), { recursive: true });
|
|
379
277
|
writeFileSync(path, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
380
|
-
return {
|
|
381
|
-
status: "written",
|
|
382
|
-
path,
|
|
383
|
-
names: wiring.names,
|
|
384
|
-
plugin,
|
|
385
|
-
replaced: removed,
|
|
386
|
-
};
|
|
278
|
+
return { status: "written", path, names: wiring.names };
|
|
387
279
|
}
|
|
388
280
|
|
|
389
281
|
/** The consequence every report of a written hook must carry, in one place. */
|
|
390
282
|
export const FRESH_CLONE_NOTE = `the commands point into node_modules/${PACKAGE_NAME}/ — in a fresh clone they cannot run until \`npm install\` has run there`;
|
|
391
283
|
|
|
392
|
-
/** The instruction for a project that also enables the plugin (it only ever existed under the old name). */
|
|
393
|
-
export const UNINSTALL_PLUGIN = `/plugin uninstall ${LEGACY_PACKAGE_NAME}@${LEGACY_PACKAGE_NAME}`;
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* The doctor lines for hook commands an older install left behind: each names the file it runs,
|
|
397
|
-
* which this version does not install, so the hook silently does nothing.
|
|
398
|
-
*/
|
|
399
|
-
function legacyLines(settings: Settings): string[] {
|
|
400
|
-
const stale = new Map<string, number>();
|
|
401
|
-
for (const { command } of commandsIn(settings)) {
|
|
402
|
-
const run = parseRun(command);
|
|
403
|
-
if (run?.legacy) stale.set(run.path, (stale.get(run.path) ?? 0) + 1);
|
|
404
|
-
}
|
|
405
|
-
return stale.size === 0
|
|
406
|
-
? []
|
|
407
|
-
: [
|
|
408
|
-
...[...stale].map(
|
|
409
|
-
([path, n]) =>
|
|
410
|
-
` ⚠ ${String(n)} hook command(s) run ${path}, which this version does not install — they no longer run.`,
|
|
411
|
-
),
|
|
412
|
-
` \`npx ${PACKAGE_NAME} init\` replaces them`,
|
|
413
|
-
];
|
|
414
|
-
}
|
|
415
|
-
|
|
416
284
|
/**
|
|
417
285
|
* `paperlint doctor`'s section. ADVISORY — it never fails the run: the hooks are an in-editor guard,
|
|
418
286
|
* `--no-hooks` is a legitimate choice, and a doctor that exits non-zero on a choice gets muted.
|
|
@@ -461,17 +329,5 @@ export function doctorHooks(
|
|
|
461
329
|
` ✓ wired — ${wiring.names.join(", ")}, once each`,
|
|
462
330
|
` ${FRESH_CLONE_NOTE}`,
|
|
463
331
|
);
|
|
464
|
-
out.push(...legacyLines(read.settings));
|
|
465
|
-
const plugin = pluginEnabledHere(read.settings);
|
|
466
|
-
if (plugin.length > 0) {
|
|
467
|
-
out.push(
|
|
468
|
-
` ⚠ this project ALSO enables the plugin (${plugin.join(", ")}) — every hook would run twice.`,
|
|
469
|
-
` the plugin no longer carries the hooks: ${UNINSTALL_PLUGIN}`,
|
|
470
|
-
` and remove it from "enabledPlugins" in ${SETTINGS_PATH}`,
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
out.push(
|
|
474
|
-
` · a plugin installed at USER scope is not visible from here — \`/plugin\` inside Claude Code shows it`,
|
|
475
|
-
);
|
|
476
332
|
return out;
|
|
477
333
|
}
|