paperlint 13.0.3 → 13.2.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 +9 -1
- package/dist/adapters/git/index.d.ts.map +1 -1
- package/dist/adapters/git/index.js +13 -0
- package/dist/adapters/git/index.js.map +1 -1
- package/dist/adapters/study-registry/index.d.ts +142 -1
- package/dist/adapters/study-registry/index.d.ts.map +1 -1
- package/dist/adapters/study-registry/index.js +160 -1
- package/dist/adapters/study-registry/index.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/domain/decide-first.d.ts +43 -0
- package/dist/domain/decide-first.d.ts.map +1 -1
- package/dist/domain/decide-first.js +45 -2
- package/dist/domain/decide-first.js.map +1 -1
- package/dist/domain/study-gates.d.ts +96 -0
- package/dist/domain/study-gates.d.ts.map +1 -0
- package/dist/domain/study-gates.js +56 -0
- package/dist/domain/study-gates.js.map +1 -0
- package/dist/domain/study-mode.d.ts +6 -0
- package/dist/domain/study-mode.d.ts.map +1 -1
- package/dist/domain/study-mode.js.map +1 -1
- package/dist/domain/study-registry.d.ts +24 -4
- package/dist/domain/study-registry.d.ts.map +1 -1
- package/dist/domain/study-registry.js +39 -2
- package/dist/domain/study-registry.js.map +1 -1
- package/dist/domain/study-result.d.ts +151 -0
- package/dist/domain/study-result.d.ts.map +1 -0
- package/dist/domain/study-result.js +175 -0
- package/dist/domain/study-result.js.map +1 -0
- package/dist/outputs-before-decision-rule.d.ts.map +1 -1
- package/dist/outputs-before-decision-rule.js +37 -3
- package/dist/outputs-before-decision-rule.js.map +1 -1
- package/dist/ports/git-history.d.ts +12 -0
- package/dist/ports/git-history.d.ts.map +1 -1
- package/dist/ports/study-registry-codec.d.ts +3 -0
- package/dist/ports/study-registry-codec.d.ts.map +1 -1
- package/dist/skills/build-label-page/scripts/label-page-cli.d.ts +6 -0
- package/dist/skills/build-label-page/scripts/label-page-commands.d.ts +14 -0
- package/dist/skills/build-label-page/scripts/label-page-core.d.ts +149 -0
- package/dist/skills/build-label-page/scripts/label-page-html.d.ts +5 -0
- package/dist/skills/build-label-page/scripts/label-page.d.ts +2 -0
- package/dist/study-modes.d.ts +3 -0
- package/dist/study-modes.d.ts.map +1 -1
- package/dist/study-modes.js +8 -1
- package/dist/study-modes.js.map +1 -1
- package/dist/study-registry-rule.d.ts.map +1 -1
- package/dist/study-registry-rule.js +1 -0
- package/dist/study-registry-rule.js.map +1 -1
- package/dist/study-result-rule.d.ts +12 -0
- package/dist/study-result-rule.d.ts.map +1 -0
- package/dist/study-result-rule.js +87 -0
- package/dist/study-result-rule.js.map +1 -0
- package/docs/design/study-gates.md +208 -0
- package/docs/rules/repro/outputs-before-decision.md +8 -0
- package/docs/rules/repro/study-registered.md +38 -15
- package/docs/rules/repro/study-result.md +100 -0
- package/docs/rules.md +1 -0
- package/eslint.config.mjs +1 -1
- package/hooks/hooks.harness.ts +51 -13
- package/hooks/paper-skills-nudge.hook.d.mts +22 -0
- package/hooks/paper-skills-nudge.hook.mjs +85 -23
- package/hooks/paper-skills-nudge.test.ts +82 -4
- package/package.json +2 -1
- package/plugin/hooks/hooks.json +7 -2
- package/schemas/plan.v2.schema.json +341 -0
- package/skills/argument-arc/SKILL.md.spec.ts +1 -1
- package/skills/build-label-page/SKILL.md +85 -54
- package/skills/build-label-page/SKILL.md.spec.ts +85 -53
- package/skills/build-label-page/build-label-page.test.ts +10 -941
- package/skills/build-label-page/references/checklist.md +45 -3
- package/skills/build-label-page/references/closed-choice-generator.md +88 -0
- package/skills/build-label-page/scripts/label-page-cli.js +72 -0
- package/skills/build-label-page/scripts/label-page-cli.ts +85 -0
- package/skills/build-label-page/scripts/label-page-commands.js +14 -0
- package/skills/build-label-page/scripts/label-page-commands.ts +14 -0
- package/skills/build-label-page/scripts/label-page-core.js +850 -0
- package/skills/build-label-page/scripts/label-page-core.test.ts +1164 -0
- package/skills/build-label-page/scripts/label-page-core.ts +1210 -0
- package/skills/build-label-page/scripts/label-page-html.js +33 -0
- package/skills/build-label-page/scripts/label-page-html.test.ts +413 -0
- package/skills/build-label-page/scripts/label-page-html.ts +43 -0
- package/skills/build-label-page/scripts/label-page.js +70 -0
- package/skills/build-label-page/scripts/label-page.test.ts +71 -0
- package/skills/build-label-page/scripts/label-page.ts +71 -0
- package/skills/build-label-page/scripts/page-template.html +513 -0
- package/skills/find-venue/SKILL.md.spec.ts +1 -1
- package/skills/paper-status/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/src/adapters/git/index.test.ts +33 -0
- package/src/adapters/git/index.ts +19 -0
- package/src/adapters/study-registry/gates.test.ts +181 -0
- package/src/adapters/study-registry/index.ts +219 -1
- package/src/adapters/study-registry/mode.test.ts +6 -0
- package/src/cli.ts +3 -0
- package/src/domain/decide-first.test.ts +131 -0
- package/src/domain/decide-first.ts +92 -2
- package/src/domain/study-gates.ts +125 -0
- package/src/domain/study-measurements.test.ts +71 -0
- package/src/domain/study-mode.ts +6 -0
- package/src/domain/study-registry.ts +64 -2
- package/src/domain/study-result.test.ts +300 -0
- package/src/domain/study-result.ts +358 -0
- package/src/outputs-before-decision-rule.test.ts +115 -2
- package/src/outputs-before-decision-rule.ts +50 -2
- package/src/ports/git-history.ts +12 -0
- package/src/ports/study-registry-codec.ts +3 -0
- package/src/rule-cases.test.ts +74 -0
- package/src/studies.test.ts +3 -0
- package/src/study-modes.ts +10 -1
- package/src/study-registry-rule.ts +1 -0
- package/src/study-result-rule.test.ts +281 -0
- package/src/study-result-rule.ts +123 -0
- package/skills/build-label-page/references/example-generator.py +0 -803
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Labelling-page checklist
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Eight rules for a page where a person labels items from a paper's study (gold labels, a blind review of
|
|
4
4
|
model outputs, "is this a real boundary case"). Each rule says what to do, why, and how to check it.
|
|
5
5
|
They come from four such pages built in one week; each rule is a defect one of them shipped, or a
|
|
6
6
|
failure the next answer would have hidden.
|
|
7
7
|
|
|
8
|
-
The
|
|
9
|
-
|
|
8
|
+
The rules hold for every labelling page, generated or written by hand. The skill's generator,
|
|
9
|
+
`scripts/label-page.ts`, does rules 2 to 5, 7 and the page half of 8 by construction
|
|
10
|
+
([`closed-choice-generator.md`](closed-choice-generator.md)); a page written by hand meets each rule
|
|
11
|
+
itself, and each rule's **Check** line is its acceptance list. Rules 1 and 6, and the rewording in 8,
|
|
12
|
+
are things the agent does. Read the rule before changing the part of the generator that
|
|
10
13
|
carries it.
|
|
11
14
|
|
|
12
15
|
## 1. Test it in the claude.ai phone viewer before sending the link
|
|
@@ -140,6 +143,45 @@ not run: banner, items readable, buttons off.
|
|
|
140
143
|
|
|
141
144
|
---
|
|
142
145
|
|
|
146
|
+
## 8. The labeller understands each question before the real items
|
|
147
|
+
|
|
148
|
+
**Do.**
|
|
149
|
+
|
|
150
|
+
- Write each question in plain words, and give every answer option one `example`: a real item of
|
|
151
|
+
the study, in a line, that takes that answer. The page shows them on its first screen ("How to
|
|
152
|
+
answer"). The build refuses an option without one.
|
|
153
|
+
- Write 3 or more practice items whose answers are known (`practice`, each with `expected` for every
|
|
154
|
+
question and a one-line `why`). The page shows them before the real items; after each answer it
|
|
155
|
+
shows the expected answer and the reason. The real items open once all are answered.
|
|
156
|
+
- Make the answers tell apart at a glance: give each option an `icon` (an emoji), never a coloured
|
|
157
|
+
side stripe on a button (it reads as decoration; the look test refuses one), and put code, configs and identifiers in
|
|
158
|
+
`backticks` or the item's `code` field, so they render as code. During the practice each button also
|
|
159
|
+
shows its example; on the real items the buttons stay short.
|
|
160
|
+
- Code is shown so it can be judged, not counted: a `code` block marks leading and trailing
|
|
161
|
+
whitespace (a space as ·, a tab as →) and prints each line's length at its end.
|
|
162
|
+
- One question per screen: an item card asks one question at a time, and the answered ones stay as a
|
|
163
|
+
line with "Change".
|
|
164
|
+
- A practice miss does not lock the page, but it stops the agent: ask the labeller which question
|
|
165
|
+
was unclear, reword it (and its examples), raise `practice_round`, rebuild, and have the labeller
|
|
166
|
+
take the practice again before their real answers count. The page keeps only the FIRST answer to a
|
|
167
|
+
practice item, so the score is not a retry.
|
|
168
|
+
- Keep a record in the study folder: the practice file `export` writes
|
|
169
|
+
(`<study>-practice-r<round>-<date>.json`: the round, who answered, what matched), and one line in the
|
|
170
|
+
study's README per round on what was reworded after it.
|
|
171
|
+
|
|
172
|
+
**Why.** A labeller who misreads the question gives labels that agree with nothing, and the agreement
|
|
173
|
+
statistic then blames the items. Pages that passed rules 1 to 7 (blind, phone-tested, saved on click)
|
|
174
|
+
still left their labeller unable to say what was being asked. A pilot round before the real labelling
|
|
175
|
+
is ordinary practice in labelling studies: one rule-mining study labelled 30 pilot samples, resolved
|
|
176
|
+
the disagreements, aligned its criteria, and only then labelled the full set.
|
|
177
|
+
|
|
178
|
+
**Check.** The page opens on "How to answer" with an example under every option, then "Practice: 0
|
|
179
|
+
of N answered", and no real item. Answer the practice wrongly once: the expected answer and the
|
|
180
|
+
reason appear, and the banner says to tell the agent. `export` writes the labels without any
|
|
181
|
+
practice answer and the practice file beside them.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
143
185
|
## Who can answer
|
|
144
186
|
|
|
145
187
|
A page with the `db` capability opens only for people with access to the artifact: the owner, or a
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# The closed-choice generator
|
|
2
|
+
|
|
3
|
+
The page shape this generator builds: the labeller looks at one item and picks from a closed list of
|
|
4
|
+
answers, one question at a time, with an optional reason box and code blocks. For any other shape,
|
|
5
|
+
see "Which page you need" in `SKILL.md`: the agent writes that page itself.
|
|
6
|
+
|
|
7
|
+
The generator is `scripts/label-page.ts` in this skill, compiled beside itself to `label-page.js`.
|
|
8
|
+
It runs from the skill and is never copied into a study, so a fix to it reaches every page built
|
|
9
|
+
after the fix. The commands are in `SKILL.md` (procedure steps 3 and 8); its third subcommand,
|
|
10
|
+
`schema`, prints the declared schema of `items.json`. Its header holds the full `items.json`
|
|
11
|
+
example; this file says how to fill it.
|
|
12
|
+
|
|
13
|
+
It is two parts. The core, `label-page-core.ts`, has no page layout: the `items.json` schema and its
|
|
14
|
+
checks, the leak guard, the neutral ids, the key with its `allowed` and `needs_why` lists, the check
|
|
15
|
+
of the store's dump, and the export of labels and practice. The page, `label-page-html.ts`, fills the
|
|
16
|
+
one closed-choice template (`page-template.html`, its style and browser script kept as exact bytes)
|
|
17
|
+
with the core's payload. Another page shape would be another template on the same core.
|
|
18
|
+
|
|
19
|
+
## What it does by construction
|
|
20
|
+
|
|
21
|
+
Rules 2 to 5, 7 and the page half of 8 of [`checklist.md`](checklist.md): a filter instead of
|
|
22
|
+
scrolling, complete-item counting, neutral ids in a seeded order, the leak guard (an item field
|
|
23
|
+
outside the allow-list stops the build), save on click with a time, the store-unavailable banner,
|
|
24
|
+
the "How to answer" screen, the practice before the real items, one question per screen. Its look:
|
|
25
|
+
one dark palette, one column, answer buttons as full-width rows with space between them, an optional
|
|
26
|
+
icon per answer and never a coloured side stripe, `backticks` and the `code` field shown as code,
|
|
27
|
+
code blocks that show leading and trailing whitespace (a space as ·, a tab as →) and each line's
|
|
28
|
+
length.
|
|
29
|
+
|
|
30
|
+
## items.json
|
|
31
|
+
|
|
32
|
+
- **`questions`**: each with a closed set of `options`. Every option has a `value`, a `label` and a
|
|
33
|
+
required `example`: a real case of the study, in a line, that takes that answer. `icon` (an
|
|
34
|
+
emoji) is optional. Two labels that read the same, or two equal values, stop the build.
|
|
35
|
+
- **`practice`**: 3 or more real cases whose answer you are sure of, each with `expected` (one answer
|
|
36
|
+
per question) and a one-line `why`. Never an item that is also on the page to be labelled.
|
|
37
|
+
- **`practice_round`**: raise it after rewording a question; the practice reopens and the answers of
|
|
38
|
+
the earlier round are kept but not counted.
|
|
39
|
+
- **`items`**: `source_id` (the study's own, never shown), `group`, `body`, and optionally `title`,
|
|
40
|
+
`code`, `context`. Nothing else: the allow-list is fixed in the generator, and a field outside it
|
|
41
|
+
is taken out of the item, not added to the list to make a build pass.
|
|
42
|
+
- **`seed`**: fixes the order. Rebuilding with the same seed keeps every id, and a new item gets the
|
|
43
|
+
next one; a different seed is refused once a key exists.
|
|
44
|
+
- **`ask_why`** on an option: a prompt. That answer is incomplete, on the page and in the export,
|
|
45
|
+
until a reason is saved.
|
|
46
|
+
|
|
47
|
+
## The approval page
|
|
48
|
+
|
|
49
|
+
The page on which the author checks a drawn sample before the runs (the plan's `author_check`: "is
|
|
50
|
+
this what you meet in practice?") is this generator's shape: one question whose answers are keep and
|
|
51
|
+
drop, the drop carrying `ask_why`.
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"id": "keep",
|
|
56
|
+
"text": "Is this what you meet in practice?",
|
|
57
|
+
"short": "keep?",
|
|
58
|
+
"options": [
|
|
59
|
+
{
|
|
60
|
+
"value": "keep",
|
|
61
|
+
"label": "Keep",
|
|
62
|
+
"icon": "👍",
|
|
63
|
+
"example": "`Use 2-space indent.`"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"value": "drop",
|
|
67
|
+
"label": "Drop",
|
|
68
|
+
"icon": "🗑️",
|
|
69
|
+
"example": "`Be nice to reviewers.`",
|
|
70
|
+
"ask_why": "Why drop it? (not a rule, a process rule, a misreading…)"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The items are the drawn units verbatim. The practice is three units whose fate is obvious. Each
|
|
77
|
+
dropped unit carries its reason in the labels file. The author's words, with the date, then go into
|
|
78
|
+
`author_check` of the plan; a dropped unit is replaced by the next one in the draw.
|
|
79
|
+
|
|
80
|
+
## The export
|
|
81
|
+
|
|
82
|
+
`export` reads the store's three collections (`answers`, `groups`, `practice`) from `dump.json`, in
|
|
83
|
+
the shape the generator's header gives. It refuses, naming the path, any name the key does not know
|
|
84
|
+
or any document of the wrong type. An answer or familiarity value outside the offered options is not
|
|
85
|
+
counted: the item is marked incomplete and printed (same rule as on the page); correct it in the dump
|
|
86
|
+
and export again. It also prints every item that is missing an answer or a reason, and every group
|
|
87
|
+
with no familiarity answer. The practice of the current round is scored into its own file and never
|
|
88
|
+
into the labels.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command line of build-label-page's generator, as a function: one argv in, what it printed and
|
|
3
|
+
* its exit code out. `label-page.ts` runs it on the process's own argv.
|
|
4
|
+
*/
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { COMMANDS } from "./label-page-commands.js";
|
|
7
|
+
import { CONFIG_SCHEMA, exportLabels, fail, Refusal, say, takePrinted, } from "./label-page-core.js";
|
|
8
|
+
import { build } from "./label-page-html.js";
|
|
9
|
+
const USAGE = "usage: label-page.js build <items.json> <out-dir> | export <dump.json> <key.json> <data-dir> [--date YYYY-MM-DD] | schema";
|
|
10
|
+
const usage = () => {
|
|
11
|
+
throw new Refusal(2, USAGE);
|
|
12
|
+
};
|
|
13
|
+
const today = () => {
|
|
14
|
+
const d = new Date();
|
|
15
|
+
const two = (n) => String(n).padStart(2, "0");
|
|
16
|
+
return `${String(d.getFullYear())}-${two(d.getMonth() + 1)}-${two(d.getDate())}`;
|
|
17
|
+
};
|
|
18
|
+
/** How many paths each subcommand takes, and whether it takes `--date`. */
|
|
19
|
+
const ARITY = {
|
|
20
|
+
[COMMANDS.schema]: { paths: 0, dated: false },
|
|
21
|
+
[COMMANDS.build]: { paths: 2, dated: false },
|
|
22
|
+
[COMMANDS.export]: { paths: 3, dated: true },
|
|
23
|
+
};
|
|
24
|
+
const parsedArgs = (argv) => {
|
|
25
|
+
try {
|
|
26
|
+
return parseArgs({
|
|
27
|
+
args: [...argv],
|
|
28
|
+
options: { date: { type: "string" } },
|
|
29
|
+
allowPositionals: true,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return usage();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function exportCommand(paths, date) {
|
|
37
|
+
const [dump = "", key = "", out = ""] = paths;
|
|
38
|
+
const day = date ?? today();
|
|
39
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(day))
|
|
40
|
+
fail("--date must be YYYY-MM-DD");
|
|
41
|
+
exportLabels(dump, key, out, day);
|
|
42
|
+
}
|
|
43
|
+
function main(argv) {
|
|
44
|
+
const { values, positionals } = parsedArgs(argv);
|
|
45
|
+
const [cmd = "", ...paths] = positionals;
|
|
46
|
+
const arity = ARITY[cmd];
|
|
47
|
+
const fits = arity !== undefined &&
|
|
48
|
+
arity.paths === paths.length &&
|
|
49
|
+
(arity.dated || values.date === undefined);
|
|
50
|
+
if (!fits)
|
|
51
|
+
usage();
|
|
52
|
+
if (cmd === COMMANDS.schema)
|
|
53
|
+
say(JSON.stringify(CONFIG_SCHEMA, null, 2));
|
|
54
|
+
else if (cmd === COMMANDS.build)
|
|
55
|
+
build(String(paths[0]), String(paths[1]));
|
|
56
|
+
else
|
|
57
|
+
exportCommand(paths, values.date);
|
|
58
|
+
}
|
|
59
|
+
/** One command line: what it printed to stdout and to stderr, and its exit code. */
|
|
60
|
+
export function cli(argv) {
|
|
61
|
+
takePrinted(); // nothing left over from an earlier run in the same process
|
|
62
|
+
try {
|
|
63
|
+
main(argv);
|
|
64
|
+
return { status: 0, stdout: takePrinted(), stderr: "" };
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
// An unexpected error (a file that cannot be written) prints its message, not a stack.
|
|
68
|
+
const message = String(Reflect.get(Object(e), "message"));
|
|
69
|
+
const status = e instanceof Refusal ? e.exitCode : 1;
|
|
70
|
+
return { status, stdout: takePrinted(), stderr: `${message}\n` };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command line of build-label-page's generator, as a function: one argv in, what it printed and
|
|
3
|
+
* its exit code out. `label-page.ts` runs it on the process's own argv.
|
|
4
|
+
*/
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { COMMANDS } from "./label-page-commands.ts";
|
|
7
|
+
import {
|
|
8
|
+
CONFIG_SCHEMA,
|
|
9
|
+
exportLabels,
|
|
10
|
+
fail,
|
|
11
|
+
Refusal,
|
|
12
|
+
say,
|
|
13
|
+
takePrinted,
|
|
14
|
+
} from "./label-page-core.ts";
|
|
15
|
+
import { build } from "./label-page-html.ts";
|
|
16
|
+
|
|
17
|
+
const USAGE =
|
|
18
|
+
"usage: label-page.js build <items.json> <out-dir> | export <dump.json> <key.json> <data-dir> [--date YYYY-MM-DD] | schema";
|
|
19
|
+
const usage = (): never => {
|
|
20
|
+
throw new Refusal(2, USAGE);
|
|
21
|
+
};
|
|
22
|
+
const today = (): string => {
|
|
23
|
+
const d = new Date();
|
|
24
|
+
const two = (n: number): string => String(n).padStart(2, "0");
|
|
25
|
+
return `${String(d.getFullYear())}-${two(d.getMonth() + 1)}-${two(d.getDate())}`;
|
|
26
|
+
};
|
|
27
|
+
/** How many paths each subcommand takes, and whether it takes `--date`. */
|
|
28
|
+
const ARITY: Readonly<
|
|
29
|
+
Record<string, { readonly paths: number; readonly dated: boolean }>
|
|
30
|
+
> = {
|
|
31
|
+
[COMMANDS.schema]: { paths: 0, dated: false },
|
|
32
|
+
[COMMANDS.build]: { paths: 2, dated: false },
|
|
33
|
+
[COMMANDS.export]: { paths: 3, dated: true },
|
|
34
|
+
};
|
|
35
|
+
const parsedArgs = (argv: readonly string[]) => {
|
|
36
|
+
try {
|
|
37
|
+
return parseArgs({
|
|
38
|
+
args: [...argv],
|
|
39
|
+
options: { date: { type: "string" } },
|
|
40
|
+
allowPositionals: true,
|
|
41
|
+
});
|
|
42
|
+
} catch {
|
|
43
|
+
return usage();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
function exportCommand(
|
|
47
|
+
paths: readonly string[],
|
|
48
|
+
date: string | undefined,
|
|
49
|
+
): void {
|
|
50
|
+
const [dump = "", key = "", out = ""] = paths;
|
|
51
|
+
const day = date ?? today();
|
|
52
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) fail("--date must be YYYY-MM-DD");
|
|
53
|
+
exportLabels(dump, key, out, day);
|
|
54
|
+
}
|
|
55
|
+
function main(argv: readonly string[]): void {
|
|
56
|
+
const { values, positionals } = parsedArgs(argv);
|
|
57
|
+
const [cmd = "", ...paths] = positionals;
|
|
58
|
+
const arity = ARITY[cmd];
|
|
59
|
+
const fits =
|
|
60
|
+
arity !== undefined &&
|
|
61
|
+
arity.paths === paths.length &&
|
|
62
|
+
(arity.dated || values.date === undefined);
|
|
63
|
+
if (!fits) usage();
|
|
64
|
+
if (cmd === COMMANDS.schema) say(JSON.stringify(CONFIG_SCHEMA, null, 2));
|
|
65
|
+
else if (cmd === COMMANDS.build) build(String(paths[0]), String(paths[1]));
|
|
66
|
+
else exportCommand(paths, values.date);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** One command line: what it printed to stdout and to stderr, and its exit code. */
|
|
70
|
+
export function cli(argv: readonly string[]): {
|
|
71
|
+
readonly status: number;
|
|
72
|
+
readonly stdout: string;
|
|
73
|
+
readonly stderr: string;
|
|
74
|
+
} {
|
|
75
|
+
takePrinted(); // nothing left over from an earlier run in the same process
|
|
76
|
+
try {
|
|
77
|
+
main(argv);
|
|
78
|
+
return { status: 0, stdout: takePrinted(), stderr: "" };
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// An unexpected error (a file that cannot be written) prints its message, not a stack.
|
|
81
|
+
const message = String(Reflect.get(Object(e), "message"));
|
|
82
|
+
const status = e instanceof Refusal ? e.exitCode : 1;
|
|
83
|
+
return { status, stdout: takePrinted(), stderr: `${message}\n` };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subcommands of `label-page`, by name. One table, read twice: the dispatcher in `label-page.ts`
|
|
3
|
+
* compares against these, and `SKILL.md.spec.ts` interpolates them into every command it instructs
|
|
4
|
+
* (a name that is not here is a type error there). So the skill cannot instruct a subcommand the
|
|
5
|
+
* script does not have.
|
|
6
|
+
*
|
|
7
|
+
* A module of its own, with no side effects: the spec imports it at compile time, and importing
|
|
8
|
+
* `label-page.ts` itself would run the command line.
|
|
9
|
+
*/
|
|
10
|
+
export const COMMANDS = {
|
|
11
|
+
build: "build",
|
|
12
|
+
export: "export",
|
|
13
|
+
schema: "schema",
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subcommands of `label-page`, by name. One table, read twice: the dispatcher in `label-page.ts`
|
|
3
|
+
* compares against these, and `SKILL.md.spec.ts` interpolates them into every command it instructs
|
|
4
|
+
* (a name that is not here is a type error there). So the skill cannot instruct a subcommand the
|
|
5
|
+
* script does not have.
|
|
6
|
+
*
|
|
7
|
+
* A module of its own, with no side effects: the spec imports it at compile time, and importing
|
|
8
|
+
* `label-page.ts` itself would run the command line.
|
|
9
|
+
*/
|
|
10
|
+
export const COMMANDS = {
|
|
11
|
+
build: "build",
|
|
12
|
+
export: "export",
|
|
13
|
+
schema: "schema",
|
|
14
|
+
} as const;
|