paperlint 11.1.2 → 11.1.3
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/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/submission-confirm-hook.d.ts +27 -19
- package/dist/submission-confirm-hook.d.ts.map +1 -1
- package/dist/submission-confirm-hook.js +302 -95
- package/dist/submission-confirm-hook.js.map +1 -1
- package/docs/layout.md +22 -22
- package/docs/submission.md +32 -8
- package/package.json +1 -1
- package/skills/submission-portal/SKILL.md +18 -12
- package/skills/submission-portal/SKILL.md.spec.ts +17 -11
- package/src/cli.test.ts +3 -3
- package/src/cli.ts +2 -2
- package/src/submission-confirm-hook.test.ts +493 -60
- package/src/submission-confirm-hook.ts +448 -116
package/docs/submission.md
CHANGED
|
@@ -111,14 +111,38 @@ The command refuses one thing by itself: `--save` when no build facts describe t
|
|
|
111
111
|
of other bytes) exits 2 and sends nothing. Rebuild (`paperlint build`) and run the dry run again.
|
|
112
112
|
Everything else, including over the limit, is the person's call.
|
|
113
113
|
|
|
114
|
-
**The `submission-confirm` hook** (installed by `paperlint init`)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
send.
|
|
114
|
+
**The `submission-confirm` hook** (installed by `paperlint init`) stops any `submission new|update`
|
|
115
|
+
with `--save` or `--submit` until the person has said yes to that exact PDF in that exact folder. The flow:
|
|
116
|
+
|
|
117
|
+
1. Run the dry run (the same command without `--save`) and read its venue check.
|
|
118
|
+
2. Ask the person with Claude Code's question tool (`AskUserQuestion`), with the options "Yes, send
|
|
119
|
+
it" and "No". The question names the paper folder (its full path, as the hook prints it), the PDF and the
|
|
120
|
+
first 12 characters of its sha256, the body pages, and quotes the dry run's venue check.
|
|
121
|
+
3. On a yes, run the send.
|
|
122
|
+
|
|
123
|
+
Without a yes, the hook stops the send and tells the agent what to ask, with the folder and the sha256 filled in.
|
|
124
|
+
It reads the answer from the session's transcript. The question must contain the folder's full path
|
|
125
|
+
and the PDF's sha256, so a yes to an older build, another PDF, or the same bytes in another folder
|
|
126
|
+
(the same manuscript for another venue) does not count. One yes lets one send through; the next send
|
|
127
|
+
needs a new question. That holds inside one command too: each send in a chain (`… --save && … --save`)
|
|
128
|
+
takes a yes of its own, so one yes naming two shas, or two sends of the same PDF, covers only one. A send whose PDF cannot be found or told for certain is stopped, and so is
|
|
129
|
+
any send when the transcript cannot be read.
|
|
130
|
+
|
|
131
|
+
A command that names a portal's host (a HotCRP-hosted site, or a host a venue preset declares),
|
|
132
|
+
whatever program it runs, may send around the venue check. It runs only after a yes to a question
|
|
133
|
+
that contains that host and the command's first 60 characters; the hook prints both when it stops
|
|
134
|
+
the command, the start compared as written (the host in any case). One yes lets one such command through, and a command that also sends a paper needs a separate yes for each. If the venue presets cannot be read, only
|
|
135
|
+
HotCRP-hosted sites and paperlint sends are recognised, and the hook says so when it stops one;
|
|
136
|
+
other commands run as usual. The hook does not see an upload from a script file that names the
|
|
137
|
+
host only inside the file.
|
|
138
|
+
|
|
139
|
+
The agent writes the question, so a deliberately misleading question still passes. The hook
|
|
140
|
+
catches an agent that sends without asking, or asks about the wrong PDF; it is not a security
|
|
141
|
+
boundary against an agent that deliberately disguises a send.
|
|
142
|
+
|
|
143
|
+
Why a question and not Claude Code's own permission prompt: in the Claude mobile app that prompt
|
|
144
|
+
shows only the command line, not which paper or PDF it sends. The question shows everything, on
|
|
145
|
+
every client.
|
|
122
146
|
|
|
123
147
|
## What it does not do
|
|
124
148
|
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ description: "Create, read or change a paper's submission on the venue's HotCRP
|
|
|
4
4
|
allowed-tools: [Read, Grep, Glob, Bash, Edit, AskUserQuestion]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:8b1352dd55af2d2b compiled from skills/submission-portal/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# submission-portal — create, read and update the submission on the portal, then prove it landed
|
|
10
10
|
|
|
@@ -62,25 +62,31 @@ A missing key or token stops the command with exit 2 and a line naming what to s
|
|
|
62
62
|
`\begin{abstract}`) and each `field` it sends. Read them with the author; an abstract the
|
|
63
63
|
command cannot read, or one that reads wrong, is given as a plain-text file with
|
|
64
64
|
`--abstract <file>`.
|
|
65
|
-
2. **
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
2. **Ask, then send.** Ask the author with `AskUserQuestion` — options "Yes, send it" and "No" —
|
|
66
|
+
naming the paper folder (its full path, as the hook prints it), the PDF and its sha256, the body
|
|
67
|
+
pages, and quoting the dry run's venue
|
|
68
|
+
check (its lint findings, or "venue rules at their default options: no errors"), plus what goes
|
|
69
|
+
(title, authors, abstract, each field). On a yes, run
|
|
70
|
+
`npx paperlint submission new papers/my-paper --save`. Then the printed "still to do" list
|
|
71
|
+
with the author.
|
|
69
72
|
3. **Read it.** `npx paperlint submission show papers/my-paper` — status, the PDF the portal holds,
|
|
70
73
|
and `match`: whether it is the local build. Rebuild first if the local PDF is stale.
|
|
71
74
|
4. **Dry run the change.** `npx paperlint submission update papers/my-paper` (`--abstract <file>`,
|
|
72
75
|
`--submit` to mark it submitted); it sends the paper's `"fields"` too. Exit 1 means the portal
|
|
73
76
|
would refuse it, or did not take a topic — report why, stop.
|
|
74
|
-
5. **
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
5. **Ask, then send.** Ask the author with `AskUserQuestion` as in step 2: the question names the
|
|
78
|
+
paper folder (full path), the PDF's sha256 and quotes the dry run's venue check, and says what changes. On a yes, run the same
|
|
79
|
+
command with `--save`. Where the `submission-confirm` hook is installed, a send without that
|
|
80
|
+
yes is stopped, and the hook says what to ask, with the folder and the sha256 filled in; ask exactly that. One
|
|
81
|
+
yes covers one send: to send again, or to send a second paper in the same command, ask again. A refusal by the command itself (exit 2) means
|
|
82
|
+
the last build is not of this PDF: rebuild, dry run again.
|
|
77
83
|
6. **Prove it landed.** `show` again: `match` must read `yes — the portal holds this build`, and
|
|
78
84
|
the status must be what was intended. Anything else is not done — say so.
|
|
79
85
|
|
|
80
86
|
🔴 `--save` creates or changes a real submission that reviewers will see: never without the
|
|
81
87
|
author's own yes, never "to test", never because a dry run passed. Before every `--save`, ask the
|
|
82
|
-
author with `AskUserQuestion`,
|
|
83
|
-
hook is installed,
|
|
88
|
+
author with `AskUserQuestion`, naming the paper folder, the PDF's sha256 and quoting the dry run's venue check; where
|
|
89
|
+
the `submission-confirm` hook is installed, it stops any send without that yes.
|
|
84
90
|
|
|
85
91
|
```bash
|
|
86
92
|
npx paperlint build papers/my-paper
|
|
@@ -88,11 +94,11 @@ npx paperlint submission topics papers/my-paper
|
|
|
88
94
|
# … AskUserQuestion: which topics; the chosen ones into "fields" …
|
|
89
95
|
# only while the paper has no submission id:
|
|
90
96
|
npx paperlint submission new papers/my-paper
|
|
91
|
-
# … the
|
|
97
|
+
# … AskUserQuestion naming the folder, the PDF's sha256 and the venue check; the author says yes …
|
|
92
98
|
npx paperlint submission new papers/my-paper --save
|
|
93
99
|
npx paperlint submission show papers/my-paper
|
|
94
100
|
npx paperlint submission update papers/my-paper --submit
|
|
95
|
-
# … the
|
|
101
|
+
# … AskUserQuestion naming the folder, the PDF's sha256 and the venue check; the author says yes …
|
|
96
102
|
npx paperlint submission update papers/my-paper --submit --save
|
|
97
103
|
npx paperlint submission show papers/my-paper
|
|
98
104
|
```
|
|
@@ -66,25 +66,31 @@ A missing key or token stops the command with exit 2 and a line naming what to s
|
|
|
66
66
|
\`\\begin{abstract}\`) and each \`field\` it sends. Read them with the author; an abstract the
|
|
67
67
|
command cannot read, or one that reads wrong, is given as a plain-text file with
|
|
68
68
|
\`--abstract <file>\`.
|
|
69
|
-
2. **
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
2. **Ask, then send.** Ask the author with \`AskUserQuestion\` — options "Yes, send it" and "No" —
|
|
70
|
+
naming the paper folder (its full path, as the hook prints it), the PDF and its sha256, the body
|
|
71
|
+
pages, and quoting the dry run's venue
|
|
72
|
+
check (its lint findings, or "venue rules at their default options: no errors"), plus what goes
|
|
73
|
+
(title, authors, abstract, each field). On a yes, run
|
|
74
|
+
\`npx paperlint submission new papers/my-paper --save\`. Then the printed "still to do" list
|
|
75
|
+
with the author.
|
|
73
76
|
3. **Read it.** \`npx paperlint submission show papers/my-paper\` — status, the PDF the portal holds,
|
|
74
77
|
and \`match\`: whether it is the local build. Rebuild first if the local PDF is stale.
|
|
75
78
|
4. **Dry run the change.** \`npx paperlint submission update papers/my-paper\` (\`--abstract <file>\`,
|
|
76
79
|
\`--submit\` to mark it submitted); it sends the paper's \`"fields"\` too. Exit 1 means the portal
|
|
77
80
|
would refuse it, or did not take a topic — report why, stop.
|
|
78
|
-
5. **
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
5. **Ask, then send.** Ask the author with \`AskUserQuestion\` as in step 2: the question names the
|
|
82
|
+
paper folder (full path), the PDF's sha256 and quotes the dry run's venue check, and says what changes. On a yes, run the same
|
|
83
|
+
command with \`--save\`. Where the \`submission-confirm\` hook is installed, a send without that
|
|
84
|
+
yes is stopped, and the hook says what to ask, with the folder and the sha256 filled in; ask exactly that. One
|
|
85
|
+
yes covers one send: to send again, or to send a second paper in the same command, ask again. A refusal by the command itself (exit 2) means
|
|
86
|
+
the last build is not of this PDF: rebuild, dry run again.
|
|
81
87
|
6. **Prove it landed.** \`show\` again: \`match\` must read \`yes — the portal holds this build\`, and
|
|
82
88
|
the status must be what was intended. Anything else is not done — say so.
|
|
83
89
|
|
|
84
90
|
🔴 \`--save\` creates or changes a real submission that reviewers will see: never without the
|
|
85
91
|
author's own yes, never "to test", never because a dry run passed. Before every \`--save\`, ask the
|
|
86
|
-
author with \`AskUserQuestion\`,
|
|
87
|
-
hook is installed,
|
|
92
|
+
author with \`AskUserQuestion\`, naming the paper folder, the PDF's sha256 and quoting the dry run's venue check; where
|
|
93
|
+
the \`submission-confirm\` hook is installed, it stops any send without that yes.
|
|
88
94
|
|
|
89
95
|
\`\`\`bash
|
|
90
96
|
npx paperlint build papers/my-paper
|
|
@@ -92,11 +98,11 @@ npx paperlint submission topics papers/my-paper
|
|
|
92
98
|
# … AskUserQuestion: which topics; the chosen ones into "fields" …
|
|
93
99
|
# only while the paper has no submission id:
|
|
94
100
|
npx paperlint submission new papers/my-paper
|
|
95
|
-
# … the
|
|
101
|
+
# … AskUserQuestion naming the folder, the PDF's sha256 and the venue check; the author says yes …
|
|
96
102
|
npx paperlint submission new papers/my-paper --save
|
|
97
103
|
npx paperlint submission show papers/my-paper
|
|
98
104
|
npx paperlint submission update papers/my-paper --submit
|
|
99
|
-
# … the
|
|
105
|
+
# … AskUserQuestion naming the folder, the PDF's sha256 and the venue check; the author says yes …
|
|
100
106
|
npx paperlint submission update papers/my-paper --submit --save
|
|
101
107
|
npx paperlint submission show papers/my-paper
|
|
102
108
|
\`\`\`
|
package/src/cli.test.ts
CHANGED
|
@@ -242,7 +242,7 @@ test("runHook paper-folder-layout: in-process, on the real disk — a refusal on
|
|
|
242
242
|
);
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
-
test("runHook submission-confirm: in-process —
|
|
245
|
+
test("runHook submission-confirm: in-process — a refusal on stdout for a send with no yes, nothing for anything else, and a refusal when its input cannot be read", () => {
|
|
246
246
|
const hook = (stdin: () => string) => {
|
|
247
247
|
const out: string[] = [];
|
|
248
248
|
const code = runHook("submission-confirm", {
|
|
@@ -260,7 +260,7 @@ test("runHook submission-confirm: in-process — an ask on stdout for a send, no
|
|
|
260
260
|
assert.equal(send.code, 0);
|
|
261
261
|
assert.match(
|
|
262
262
|
send.out.join(""),
|
|
263
|
-
/^\{"hookSpecificOutput":\{"hookEventName":"PreToolUse","permissionDecision":"
|
|
263
|
+
/^\{"hookSpecificOutput":\{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Not run: this sends the paper to the venue's portal/,
|
|
264
264
|
);
|
|
265
265
|
assert.deepEqual(hook(command("npx paperlint submission show nope")), {
|
|
266
266
|
code: 0,
|
|
@@ -272,7 +272,7 @@ test("runHook submission-confirm: in-process — an ask on stdout for a send, no
|
|
|
272
272
|
assert.equal(unreadable.code, 0);
|
|
273
273
|
assert.match(
|
|
274
274
|
unreadable.out.join(""),
|
|
275
|
-
/"permissionDecision":"
|
|
275
|
+
/"permissionDecision":"deny","permissionDecisionReason":"Not run: paperlint could not read this command/,
|
|
276
276
|
);
|
|
277
277
|
});
|
|
278
278
|
|
package/src/cli.ts
CHANGED
|
@@ -1247,7 +1247,7 @@ const IN_PROCESS_HOOKS: Readonly<Record<string, (io: HookIo) => number>> = {
|
|
|
1247
1247
|
}
|
|
1248
1248
|
return 0;
|
|
1249
1249
|
},
|
|
1250
|
-
// Never advisory-silent: an input it cannot read is
|
|
1250
|
+
// Never advisory-silent: an input it cannot read is refused, not let through.
|
|
1251
1251
|
"submission-confirm": ({ stdin, out }) => {
|
|
1252
1252
|
const said = ((): ReturnType<typeof submissionConfirm> => {
|
|
1253
1253
|
try {
|
|
@@ -1717,7 +1717,7 @@ function projectPresets(cwd: string): readonly Preset[] {
|
|
|
1717
1717
|
|
|
1718
1718
|
/**
|
|
1719
1719
|
* The host of every venue portal paperlint knows of from `cwd` — the shipped presets', and those the
|
|
1720
|
-
* project's papers extend: what `submission-confirm`
|
|
1720
|
+
* project's papers extend: what `submission-confirm` refuses when a request goes straight there.
|
|
1721
1721
|
*/
|
|
1722
1722
|
export function portalHosts(cwd: string): readonly string[] {
|
|
1723
1723
|
return [...resolveShipped(PRESET_DEPS), ...projectPresets(cwd)].flatMap(
|