@josueavalosjim/taste-check 0.9.0 → 0.10.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/README.md +55 -9
- package/bin/taste-check.mjs +80 -4
- package/package.json +1 -1
- package/skills/taste-check-judge/SKILL.md +8 -2
- package/src/checklist.mjs +147 -0
- package/src/judge.mjs +6 -3
package/README.md
CHANGED
|
@@ -63,8 +63,9 @@ which is fine for advice and disqualifying for a build gate. So the measured
|
|
|
63
63
|
half here gates, the judged half does not, and the tool will not let you
|
|
64
64
|
confuse one for the other by accident.
|
|
65
65
|
|
|
66
|
-
It also
|
|
67
|
-
|
|
66
|
+
It also enforces no rule it wrote. Not a palette, not a class list, not a
|
|
67
|
+
contrast floor. It will scaffold you a starter checklist, and that file lands
|
|
68
|
+
in your repo: what runs is the copy you edited, never its own.
|
|
68
69
|
|
|
69
70
|
## What counts as a failure
|
|
70
71
|
|
|
@@ -284,11 +285,13 @@ told what changed, answer every line, prefer unsure to a guess, do not be
|
|
|
284
285
|
agreeable, and name the specific thing you are looking at. That part is method
|
|
285
286
|
and it is the same for everyone.
|
|
286
287
|
|
|
287
|
-
The checklist is yours
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
The checklist is yours, and there is a test asserting the framing mentions no
|
|
289
|
+
design vocabulary at all, because a rule that arrives at the moment of judgment
|
|
290
|
+
carries the tool's authority rather than its author's. Checklist lines are list
|
|
291
|
+
items in your file; a heading or a paragraph is prose and is not judged.
|
|
292
|
+
|
|
293
|
+
Starting from nothing is its own problem, so there is a scaffold and a linter
|
|
294
|
+
for the lines you write. Both are in the next section.
|
|
292
295
|
|
|
293
296
|
### Letting an agent carry the call
|
|
294
297
|
|
|
@@ -336,6 +339,48 @@ exits 1 whatever `failOn` says:
|
|
|
336
339
|
Without that split, "the judge did not run" and "the judge found nothing" print
|
|
337
340
|
the same thing.
|
|
338
341
|
|
|
342
|
+
## Starting a checklist
|
|
343
|
+
|
|
344
|
+
An empty file is the hardest part of the judge, so:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
taste-check checklist --new
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
It writes a starter into your repo. Six lines, and every one is about whether
|
|
351
|
+
the screen is broken rather than whether it is good, because that is the only
|
|
352
|
+
kind of line that is not somebody's taste. It refuses to overwrite a file that
|
|
353
|
+
exists, since the moment it lands it is yours.
|
|
354
|
+
|
|
355
|
+
That is a scaffold and not a default, and the difference is the whole reason it
|
|
356
|
+
is allowed to exist here. A default judges every user who never opened the file
|
|
357
|
+
against opinions they did not choose. A scaffold is a file you edited and
|
|
358
|
+
committed, and taste-check never reads its own copy. eslint is the same shape:
|
|
359
|
+
it ships rules and no default configuration, and `--init` writes you one.
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
taste-check checklist --lint
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
This checks your lines and never their content. It has no opinion about what
|
|
366
|
+
you ask for, only about whether asking it produces a verdict:
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
checklist, 3 to look at
|
|
370
|
+
unfalsifiable design-checklist.md:12 "Does it feel premium and modern"
|
|
371
|
+
"premium", "modern", "feel" describes a feeling rather than
|
|
372
|
+
something visible. A judge answers unsure, every run.
|
|
373
|
+
not-in-a-still design-checklist.md:13 "Is the hover animation smooth"
|
|
374
|
+
"animation", "hover" cannot be seen in a screenshot.
|
|
375
|
+
measurable design-checklist.md:14 "Body copy hits at least 4.5:1"
|
|
376
|
+
this asks for a number. Put it in a contrast pair instead.
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
It also flags a line asking two things at once, since one verdict cannot answer
|
|
380
|
+
both. "The primary action reads as the primary action" passes: it is an opinion,
|
|
381
|
+
a strong one, and none of the linter's business, because it can be settled by
|
|
382
|
+
looking.
|
|
383
|
+
|
|
339
384
|
## Config
|
|
340
385
|
|
|
341
386
|
Point your editor at `schema/config.schema.json` for completion and inline
|
|
@@ -455,8 +500,9 @@ the whole of your problem, it is the better fit.
|
|
|
455
500
|
|
|
456
501
|
**Checklist Design and similar agent skills.** They arrive with a hundred or
|
|
457
502
|
more published checklists and review conversationally. If you want good
|
|
458
|
-
opinions supplied, take theirs. taste-check
|
|
459
|
-
|
|
503
|
+
opinions supplied, take theirs, and they are good. taste-check scaffolds six
|
|
504
|
+
lines about whether a screen is broken and expects the rest to be yours, which
|
|
505
|
+
is more work and a different bargain.
|
|
460
506
|
|
|
461
507
|
What is left, and the reason this exists: nothing above draws a line between
|
|
462
508
|
the part that can gate a build and the part that cannot. The linters have no
|
package/bin/taste-check.mjs
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
* exist, or scopes a pair to a theme that is not defined is a failure here,
|
|
8
8
|
* not a quiet skip.
|
|
9
9
|
*/
|
|
10
|
-
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { resolve } from 'node:path';
|
|
11
12
|
import { fileURLToPath } from 'node:url';
|
|
12
13
|
|
|
13
14
|
import { load } from '../src/config.mjs';
|
|
15
|
+
import { STARTER, lintChecklist } from '../src/checklist.mjs';
|
|
16
|
+
import { checklistEntries } from '../src/judge.mjs';
|
|
14
17
|
import { gradeVerdict, judge, prepareJudge, run, runtime } from '../src/index.mjs';
|
|
15
18
|
import { failed, toJson, toText } from '../src/report.mjs';
|
|
16
19
|
import { toSarif } from '../src/sarif.mjs';
|
|
@@ -20,10 +23,15 @@ const USAGE = `taste-check
|
|
|
20
23
|
taste-check [options] Run the deterministic checks over your files
|
|
21
24
|
taste-check runtime [options] Measure contrast on a rendered page
|
|
22
25
|
taste-check judge [options] Ask a fresh-eyes judge about your screenshots
|
|
26
|
+
taste-check checklist --new Write a starter checklist you then own
|
|
27
|
+
taste-check checklist --lint Check your checklist lines can be answered
|
|
23
28
|
|
|
24
29
|
Options:
|
|
25
30
|
-c, --config <path> Config file (default: tastecheck.config.json)
|
|
26
31
|
--only <name> Run one check: contrast or treatments
|
|
32
|
+
--new [path] checklist only: scaffold a starter, refusing to
|
|
33
|
+
overwrite one that exists
|
|
34
|
+
--lint checklist only: report lines a judge cannot answer
|
|
27
35
|
--emit judge only: print the prompt and stop, for an agent
|
|
28
36
|
to carry to a model itself
|
|
29
37
|
--verdict <path> judge only: grade a reply from a file, or - for stdin
|
|
@@ -48,6 +56,16 @@ is already up. It measures what is actually painted, compositing every
|
|
|
48
56
|
background layer behind an element rather than stopping at the first opaque
|
|
49
57
|
one, and it can put the page into a state first.
|
|
50
58
|
|
|
59
|
+
checklist --new scaffolds; it does not default. The file it writes is yours,
|
|
60
|
+
and taste-check never reads its own copy of it. The six lines in it are about
|
|
61
|
+
whether a screen is broken rather than whether it is good, because that is the
|
|
62
|
+
only kind of line that is not somebody's taste.
|
|
63
|
+
|
|
64
|
+
checklist --lint is about the form of your lines and never their content. It
|
|
65
|
+
reports a line a judge will answer "unsure" to every run, a line asking two
|
|
66
|
+
things at once, a line about something a still image cannot show, and a line
|
|
67
|
+
asking for a number that belongs in a contrast pair instead.
|
|
68
|
+
|
|
51
69
|
An agent can carry the model call instead of a shell command. --emit prints
|
|
52
70
|
the prompt and the images; the agent asks a fresh context and pipes the JSON
|
|
53
71
|
back to --verdict -, which checks it against the checklist the same way. Run
|
|
@@ -68,10 +86,12 @@ function parseArgs(argv) {
|
|
|
68
86
|
emit: false,
|
|
69
87
|
verdict: null,
|
|
70
88
|
skill: false,
|
|
89
|
+
lint: false,
|
|
90
|
+
scaffold: null,
|
|
71
91
|
};
|
|
72
92
|
// One positional, and only in first position, so a stray argument is an
|
|
73
93
|
// error rather than something silently ignored.
|
|
74
|
-
if (argv[0] === 'judge' || argv[0] === 'runtime') {
|
|
94
|
+
if (argv[0] === 'judge' || argv[0] === 'runtime' || argv[0] === 'checklist') {
|
|
75
95
|
options.command = argv[0];
|
|
76
96
|
argv = argv.slice(1);
|
|
77
97
|
}
|
|
@@ -96,7 +116,12 @@ function parseArgs(argv) {
|
|
|
96
116
|
throw new Error(`--only takes "contrast" or "treatments", not "${options.only}"`);
|
|
97
117
|
}
|
|
98
118
|
} else if (arg === '--json') options.format = 'json';
|
|
99
|
-
else if (arg === '--
|
|
119
|
+
else if (arg === '--lint') options.lint = true;
|
|
120
|
+
else if (arg === '--new') {
|
|
121
|
+
// The path is optional, so peek rather than demanding a value.
|
|
122
|
+
const value = argv[i + 1];
|
|
123
|
+
options.scaffold = value !== undefined && !value.startsWith('-') ? (i += 1, value) : true;
|
|
124
|
+
} else if (arg === '--emit') options.emit = true;
|
|
100
125
|
else if (arg === '--skill') options.skill = true;
|
|
101
126
|
else if (arg === '--verdict') options.verdict = next();
|
|
102
127
|
else if (arg === '--format') {
|
|
@@ -138,6 +163,26 @@ if (options.skill) {
|
|
|
138
163
|
process.exit(0);
|
|
139
164
|
}
|
|
140
165
|
|
|
166
|
+
// checklist --new is the one command that has to work before a config exists,
|
|
167
|
+
// because not having anything yet is the situation it is for.
|
|
168
|
+
if (options.command === 'checklist' && options.scaffold) {
|
|
169
|
+
const named = typeof options.scaffold === 'string' ? options.scaffold : null;
|
|
170
|
+
const fromConfig = (() => {
|
|
171
|
+
const loadedForPath = load(options.config);
|
|
172
|
+
return loadedForPath.ok ? loadedForPath.config.judge?.checklist : null;
|
|
173
|
+
})();
|
|
174
|
+
const target = resolve(named ?? fromConfig ?? 'design-checklist.md');
|
|
175
|
+
if (existsSync(target)) {
|
|
176
|
+
die(`${target} already exists. It is yours now, so this will not overwrite it.`);
|
|
177
|
+
}
|
|
178
|
+
writeFileSync(target, STARTER);
|
|
179
|
+
const lines = checklistEntries(STARTER).length;
|
|
180
|
+
console.log(`wrote ${target}`);
|
|
181
|
+
console.log(`${lines} lines, all of them about whether a screen is broken. The ones worth`);
|
|
182
|
+
console.log('having are the ones you add. Run taste-check checklist --lint after editing.');
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
|
|
141
186
|
for (const flag of ['emit', 'verdict']) {
|
|
142
187
|
if (options[flag] && options.command !== 'judge') {
|
|
143
188
|
die(`--${flag} applies to \`taste-check judge\`, not to ${options.command}`);
|
|
@@ -155,11 +200,42 @@ if (!loaded.ok) {
|
|
|
155
200
|
if (options.command !== 'check' && options.only) {
|
|
156
201
|
die(`--only applies to the deterministic checks, not to ${options.command}`);
|
|
157
202
|
}
|
|
203
|
+
if (options.lint && options.command !== 'checklist') {
|
|
204
|
+
die('--lint applies to `taste-check checklist`, not to ' + options.command);
|
|
205
|
+
}
|
|
158
206
|
|
|
159
|
-
|
|
207
|
+
// checklist is the exception: its file lives under judge.checklist rather than
|
|
208
|
+
// in a block of its own, and it says so itself below.
|
|
209
|
+
if (options.command !== 'check' && options.command !== 'checklist' && !loaded.config[options.command]) {
|
|
160
210
|
die(`${options.config} defines no "${options.command}" block.`);
|
|
161
211
|
}
|
|
162
212
|
|
|
213
|
+
if (options.command === 'checklist') {
|
|
214
|
+
if (!options.lint) die('taste-check checklist takes --new or --lint');
|
|
215
|
+
const path = loaded.config.judge?.checklist;
|
|
216
|
+
if (!path) die(`${options.config} defines no judge.checklist to lint.`);
|
|
217
|
+
let entries;
|
|
218
|
+
try {
|
|
219
|
+
entries = checklistEntries(readFileSync(resolve(loaded.dir, path), 'utf8'));
|
|
220
|
+
} catch {
|
|
221
|
+
die(`cannot read the checklist at ${path}`);
|
|
222
|
+
}
|
|
223
|
+
if (!entries.length) {
|
|
224
|
+
die(`${path} has no checklist lines in it. Lines are list items ("- ..." or "1. ...").`);
|
|
225
|
+
}
|
|
226
|
+
const findings = lintChecklist(entries);
|
|
227
|
+
if (!findings.length) {
|
|
228
|
+
console.log(`checklist ok, ${entries.length} ${entries.length === 1 ? 'line' : 'lines'} a judge can answer`);
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
console.log(`checklist, ${findings.length} to look at`);
|
|
232
|
+
for (const f of findings) {
|
|
233
|
+
console.log(` ${f.kind.padEnd(16)}${path}:${f.line} ${JSON.stringify(f.text)}`);
|
|
234
|
+
console.log(` ${' '.repeat(16)}${f.detail}`);
|
|
235
|
+
}
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
163
239
|
// --emit stops before any model is involved, so it has no findings to report
|
|
164
240
|
// and no exit code to earn. It still refuses to hand back a prompt when the
|
|
165
241
|
// screenshots or the checklist are missing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josueavalosjim/taste-check",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Design review in CI with a line down the middle: measured checks that gate the build (WCAG contrast from your tokens or from a real rendered page, one-off values in your markup) and a fresh-eyes model judge whose verdicts stay advisory. Zero dependencies. Ships no design rules of its own.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -78,5 +78,11 @@ old screenshots is not a verdict on the new ones.
|
|
|
78
78
|
## What this skill does not contain
|
|
79
79
|
|
|
80
80
|
Any design rules. The checklist is the user's file, named in their config, and
|
|
81
|
-
if it is empty then this reports nothing and that is correct.
|
|
82
|
-
|
|
81
|
+
if it is empty then this reports nothing and that is correct.
|
|
82
|
+
|
|
83
|
+
`taste-check checklist --new` will scaffold a starter into their repo, six
|
|
84
|
+
lines about whether a screen is broken rather than whether it is good. Once it
|
|
85
|
+
is written it is theirs, and what gets judged is whatever they edited it into.
|
|
86
|
+
Do not add lines to it yourself, and do not judge against a rule that is not in
|
|
87
|
+
it. A rule that arrives at the moment of judgment, from the tool or from you,
|
|
88
|
+
is somebody else's taste wearing borrowed authority.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starting a checklist, and checking that the lines in one can be answered.
|
|
3
|
+
*
|
|
4
|
+
* Neither of these says what to value. The starter contains only lines about
|
|
5
|
+
* whether a screen is broken, which is not taste, and the linter is about the
|
|
6
|
+
* form of a line rather than its content.
|
|
7
|
+
*
|
|
8
|
+
* ── Why a starter at all, given this ships no design rules ──────────────
|
|
9
|
+
*
|
|
10
|
+
* There is a real difference between a tool that defaults to a checklist and a
|
|
11
|
+
* tool that writes one into your repo once. A default arrives with the tool's
|
|
12
|
+
* authority at the moment of judgment: every user who never opened the file is
|
|
13
|
+
* being judged against opinions they did not choose. A scaffold is a file you
|
|
14
|
+
* own, edited and committed by you, and what runs is yours. The tool never
|
|
15
|
+
* reads its own copy.
|
|
16
|
+
*
|
|
17
|
+
* eslint is the precedent. It ships rules and no default configuration, and
|
|
18
|
+
* `--init` writes you one. Nobody thinks eslint has opinions about their code
|
|
19
|
+
* until they enable some.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The starter.
|
|
24
|
+
*
|
|
25
|
+
* Six lines, and every one of them is about whether the screen is broken
|
|
26
|
+
* rather than whether it is good. That is deliberate and the header says so:
|
|
27
|
+
* these are the ones that are not anybody's taste, which means the lines worth
|
|
28
|
+
* having are the ones you add.
|
|
29
|
+
*/
|
|
30
|
+
export const STARTER = `# Design checklist
|
|
31
|
+
|
|
32
|
+
Lines here are judged one at a time against a screenshot, and each gets one
|
|
33
|
+
verdict: pass, fail, or unsure.
|
|
34
|
+
|
|
35
|
+
Everything below is about whether the screen is broken. None of it is taste,
|
|
36
|
+
which is the only reason it could ship inside a tool. The lines worth having
|
|
37
|
+
are the ones you add, and they should be the specific things your work gets
|
|
38
|
+
wrong, in your words.
|
|
39
|
+
|
|
40
|
+
Two things make a line answerable. It has to be settleable by looking, so
|
|
41
|
+
"nothing is cut off" works where "looks expensive" comes back unsure every
|
|
42
|
+
run. And it has to ask one thing, because a line joining two claims cannot be
|
|
43
|
+
answered by a single verdict.
|
|
44
|
+
|
|
45
|
+
Anything you can measure belongs in a contrast pair or a runtime target
|
|
46
|
+
instead, where the answer is a number rather than an opinion.
|
|
47
|
+
|
|
48
|
+
Note that only list items are judged. Everything above this is prose and is
|
|
49
|
+
ignored, so there is room here to write down why these lines and not others.
|
|
50
|
+
|
|
51
|
+
Run \`taste-check checklist --lint\` after editing.
|
|
52
|
+
|
|
53
|
+
- Nothing important is cut off at an edge of the frame
|
|
54
|
+
- Every piece of text is legible against what is directly behind it
|
|
55
|
+
- Nothing overlaps something it was not meant to overlap
|
|
56
|
+
- Nothing is obviously misaligned with the things beside it
|
|
57
|
+
- Nothing is still showing a loading, empty, or placeholder state
|
|
58
|
+
- Nothing appears twice that was meant to appear once
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
/** Words that describe a feeling rather than something visible in an image. */
|
|
62
|
+
const UNFALSIFIABLE = [
|
|
63
|
+
'premium', 'modern', 'clean', 'professional', 'polished', 'elegant',
|
|
64
|
+
'beautiful', 'delightful', 'intuitive', 'slick', 'crisp', 'tasteful',
|
|
65
|
+
'cohesive', 'harmonious', 'balanced', 'pleasing', 'appealing', 'nice',
|
|
66
|
+
'good', 'bad', 'ugly', 'feel', 'feels', 'vibe', 'aesthetic',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/** Things a still image cannot show. */
|
|
70
|
+
const NOT_IN_A_STILL = [
|
|
71
|
+
'animation', 'animate', 'transition', 'hover', 'scroll', 'scrolling',
|
|
72
|
+
'load time', 'performance', 'sound', 'audio', 'click', 'tap', 'drag',
|
|
73
|
+
'keyboard', 'focus ring', 'video plays',
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const MEASURABLE = /\b\d+(\.\d+)?\s*(:\s*1|px|rem|em|%|pt)\b|\bcontrast ratio\b|\b\d+:\d+\b/i;
|
|
77
|
+
const COMPOUND = /\b(and|or)\b/i;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check a single line for whether a judge could answer it.
|
|
81
|
+
*
|
|
82
|
+
* Never about content. A line can ask for anything at all; these findings are
|
|
83
|
+
* about whether asking it produces a verdict or a shrug.
|
|
84
|
+
*/
|
|
85
|
+
export function lintLine(text) {
|
|
86
|
+
const findings = [];
|
|
87
|
+
const lower = text.toLowerCase();
|
|
88
|
+
const words = text.trim().split(/\s+/);
|
|
89
|
+
|
|
90
|
+
const vague = UNFALSIFIABLE.filter((w) => new RegExp(`\\b${w}\\b`, 'i').test(lower));
|
|
91
|
+
if (vague.length) {
|
|
92
|
+
findings.push({
|
|
93
|
+
kind: 'unfalsifiable',
|
|
94
|
+
detail:
|
|
95
|
+
`"${vague.join('", "')}" describes a feeling rather than something visible. ` +
|
|
96
|
+
`A judge answers unsure, every run.`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const unseen = NOT_IN_A_STILL.filter((w) => lower.includes(w));
|
|
101
|
+
if (unseen.length) {
|
|
102
|
+
findings.push({
|
|
103
|
+
kind: 'not-in-a-still',
|
|
104
|
+
detail: `"${unseen.join('", "')}" cannot be seen in a screenshot. The judge only gets images.`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (MEASURABLE.test(text)) {
|
|
109
|
+
findings.push({
|
|
110
|
+
kind: 'measurable',
|
|
111
|
+
detail:
|
|
112
|
+
'this asks for a number. Put it in a contrast pair or a runtime target, ' +
|
|
113
|
+
'where the answer is measured rather than judged.',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Only compound when both halves look like claims, so "black and white" or
|
|
118
|
+
// "cut off at the top or bottom" are left alone.
|
|
119
|
+
if (COMPOUND.test(text)) {
|
|
120
|
+
// Three words a side, so "black and white" and "the top or bottom" are
|
|
121
|
+
// left alone while two real claims are caught.
|
|
122
|
+
const halves = text.split(/\b(?:and|or)\b/i).map((h) => h.trim().split(/\s+/).filter(Boolean).length);
|
|
123
|
+
if (halves.length > 1 && halves.every((n) => n >= 3)) {
|
|
124
|
+
findings.push({
|
|
125
|
+
kind: 'compound',
|
|
126
|
+
detail: 'two claims in one line. One verdict cannot answer both, so split it.',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (words.length < 4) {
|
|
132
|
+
findings.push({ kind: 'too-short', detail: 'too short to say what is being asked.' });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return findings;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Lint every line of a checklist, keeping the line numbers. */
|
|
139
|
+
export function lintChecklist(entries) {
|
|
140
|
+
const findings = [];
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
for (const finding of lintLine(entry.text)) {
|
|
143
|
+
findings.push({ ...finding, line: entry.line, text: entry.text });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return findings;
|
|
147
|
+
}
|
package/src/judge.mjs
CHANGED
|
@@ -18,9 +18,12 @@
|
|
|
18
18
|
* every line, prefer unsure to a guess, quote what you actually see. That part
|
|
19
19
|
* is method and it is portable.
|
|
20
20
|
*
|
|
21
|
-
* The CHECKLIST is yours. taste-check
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* The CHECKLIST is yours. taste-check enforces no rule it wrote: what runs is
|
|
22
|
+
* the file in your repo. `checklist --new` will scaffold you a starting point,
|
|
23
|
+
* and the distinction is not a dodge, it is the difference between a default
|
|
24
|
+
* and a scaffold. A default judges every user who never opened the file
|
|
25
|
+
* against opinions they did not choose. A scaffold is a file you edited and
|
|
26
|
+
* committed, and this never reads its own copy of it.
|
|
24
27
|
*
|
|
25
28
|
* ── Where the determinism line falls ────────────────────────────────────
|
|
26
29
|
*
|