sheleg-design-skill 1.61.2 → 1.62.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/CHANGELOG.md +104 -0
- package/README.md +28 -2
- package/bin/cli.js +62 -1
- package/bin/lint.js +744 -0
- package/cursor/rules/sheleg-design.mdc +20 -7
- package/package.json +3 -3
- package/plugins/sheleg-design/.claude-plugin/plugin.json +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/CREATIVE_DIRECTOR.md +34 -12
- package/plugins/sheleg-design/skills/sheleg-design/KNOWLEDGE_PROVENANCE.md +206 -10
- package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +24 -3
- package/plugins/sheleg-design/skills/sheleg-design/MOTION_DOCTRINE.md +61 -6
- package/plugins/sheleg-design/skills/sheleg-design/MOTION_PRODUCTION.md +6 -0
- package/plugins/sheleg-design/skills/sheleg-design/SHELEG_DESIGN.md +18 -29
- package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +6 -8
- package/plugins/sheleg-design/skills/sheleg-design/SLOP_MARKERS.md +223 -0
- package/plugins/sheleg-design/skills/sheleg-design/VISUAL_REVIEW.md +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,107 @@
|
|
|
1
|
+
## 1.62.0 — a visual floor with reasons and exceptions, and a linter that reads a project against it
|
|
2
|
+
|
|
3
|
+
Generated design kept shipping through reviews that had nothing to point at.
|
|
4
|
+
`CREATIVE_DIRECTOR.md` had refused impeccable's fixed style bans, rightly, because a
|
|
5
|
+
ban that cannot lose an argument contradicts "an explicit user requirement beats the
|
|
6
|
+
baseline"; what it left was no floor at all. Two private production codebases read
|
|
7
|
+
on 2026-10-07 carried emoji standing in for icons, overshoot curves, a side-stripe
|
|
8
|
+
alert card and layout-property transitions, all found by audits after the fact.
|
|
9
|
+
|
|
10
|
+
- **`SLOP_MARKERS.md`, the visual floor (catalogue version 2026-10-07).** 49 markers
|
|
11
|
+
`V001`–`V049` in eight groups (color, type, layout, icon-decor, content, motion,
|
|
12
|
+
mobile, platform), each with severity S1–S3, the tell, why it reads as a default,
|
|
13
|
+
what a designer does instead, how it is checked, the exception under which it is
|
|
14
|
+
not slop, and a commit-pinned or dated source. The floor holds with `Style pack:
|
|
15
|
+
none`; a pack may tighten it and never loosen it; a brief that asks for a marked
|
|
16
|
+
pattern wins once the director record names the marker. The three default looks
|
|
17
|
+
are markers V012, V013 and V026, and the `SHELEG_DESIGN.md` section now points
|
|
18
|
+
there. Copy tells stay with `copywriting`; no punctuation rule is adopted (in
|
|
19
|
+
Russian the em dash is normative).
|
|
20
|
+
- **`npx sheleg-design-skill --lint <dir> [--json] [--ratchet <budget.json>]
|
|
21
|
+
[--include-tests]`.** Twenty rules, one per `lint:` row: purple gradients,
|
|
22
|
+
gradient text, decorative glass, glows, the side stripe, grid and stripe
|
|
23
|
+
backgrounds, grey on colour, uniform card chrome, raw colour (under `--ratchet`),
|
|
24
|
+
default faces, emoji as icons (in markup and in data such as `icon: "📊"`, never
|
|
25
|
+
in prose), sparkles, fade-in on every section, overshoot curves, `transition:
|
|
26
|
+
all`, layout-property animation, `scale(0)`, `ease-in` and a missing reduced-motion
|
|
27
|
+
path. Exit 0 no S1, 1 an S1 or a file over its budget, 2 a usage error; S2 and S3
|
|
28
|
+
print and never block. Tests, dependencies, build output, hidden directories,
|
|
29
|
+
comments and a copied pack token layer's measured tokens are not reported. A
|
|
30
|
+
recorded exception is `sheleg-lint-allow: V0NN <reason>`, and a bare id waives
|
|
31
|
+
nothing. Written in Node like the installer, so it runs wherever `npx` does.
|
|
32
|
+
- **D9, decided.** `CREATIVE_DIRECTOR.md` records markers-not-bans in place of the
|
|
33
|
+
old refusal, and the director's record gains a `Markers` line (the lint run and
|
|
34
|
+
each waived marker with the line that earned it).
|
|
35
|
+
- **The lane references follow the umbrella's design pack.** A new design enters at
|
|
36
|
+
`references`; a redesign at `verify` + `visual-qa` + `a11y`; an update at
|
|
37
|
+
`tokens`, then `visual-qa`; an audit runs `a11y → verify → visual-qa → speed`; the
|
|
38
|
+
`--lane` list names `references`, `critique`, `visual-qa` and `speed`.
|
|
39
|
+
- **Provenance, 16 → 20 rows**, all `derived`: impeccable's detector catalogue,
|
|
40
|
+
craft floor and motion reference at `12b25ae`, and `anthropics/skills`
|
|
41
|
+
`frontend-design` at `41bbe19`. Nothing was copied.
|
|
42
|
+
- **Gates.** `validate_slop_markers()` holds the catalogue's shape and required
|
|
43
|
+
markers, and rule, id and severity in both directions between `bin/lint.js` and the
|
|
44
|
+
catalogue, with three plants; `test/lint_test.js` runs the real CLI on throwaway
|
|
45
|
+
trees (every S1 rule blocking, every quiet case quiet, the ratchet, the exit
|
|
46
|
+
contract); `--self-test` plants a defect and a clean twin for every rule.
|
|
47
|
+
|
|
48
|
+
Calibrated on the two private codebases (read-only): 107 S1 / 18 S2 and 83 S1 / 5 S2,
|
|
49
|
+
every listed defect found, the two test-fixture hits impeccable's detector reported
|
|
50
|
+
absent by default and present under `--include-tests`. One false-positive class found
|
|
51
|
+
there (a bare `"back"` string read as an ease) was fixed with a regression case. The
|
|
52
|
+
same run over this repository's kits found two layout transitions of its own (B-140).
|
|
53
|
+
The `SKILL.md` body is 4742/4750 tokens. Whether models produce fewer markers with the
|
|
54
|
+
floor loaded was not measured (plan task D8).
|
|
55
|
+
|
|
56
|
+
## 1.61.3 — every borrowed rule names its source, and the motion tools agree with the doctrine
|
|
57
|
+
|
|
58
|
+
`MOTION_DOCTRINE.md` §1–4 is Emil Kowalski's animation doctrine almost word for word
|
|
59
|
+
(the frequency table with its Raycast example, the easing tree, the three curves with
|
|
60
|
+
their comments, the duration rows, both spring notations), and `KNOWLEDGE_PROVENANCE.md`
|
|
61
|
+
had no row for `emilkowalski/skills`. The same was true of the three calibration dials
|
|
62
|
+
(`Leonxlnx/taste-skill`) and the "three default looks" paragraph (`anthropics/skills`
|
|
63
|
+
`frontend-design`), while row 6 pointed at `julianoczkowski/designer-skills`, whose file
|
|
64
|
+
carries none of those looks. The file's own rule is "a source path, a SHA and a
|
|
65
|
+
permalink, or it is not adopted"; it was not being kept.
|
|
66
|
+
|
|
67
|
+
- **Provenance, 6 → 16 rows.** Rows 1–5 re-pinned to `pbakaus/impeccable@12b25ae` (only
|
|
68
|
+
`SKILL.md` changed bytes); row 6 kept and re-verified, with a note on what it did and
|
|
69
|
+
did not inform; rows 7–9 for the impeccable type, layout and finish-review files
|
|
70
|
+
`CREATIVE_DIRECTOR.md` already cited (now linked at the same commit); rows 10–14 for
|
|
71
|
+
`emilkowalski/skills@e8a175d`; row 15 for `taste-skill@e3c9203`; row 16 for
|
|
72
|
+
`anthropics/skills@2235be7`, the version installed when the looks section was
|
|
73
|
+
written. Three rows are `adapted`, so the MIT notices (Emil Kowalski, Leonxlnx) and an
|
|
74
|
+
Apache-2.0 notice now travel in the file, and each adapted passage names its source
|
|
75
|
+
where it sits, the condensed Cursor rule included.
|
|
76
|
+
- **Emil's skills are tools inside the lanes, not a second entry point.**
|
|
77
|
+
`MOTION_DOCTRINE.md` §11 names `review-animations`, `improve-animations`,
|
|
78
|
+
`find-animation-opportunities`, `animate`, `animate-expo` and `apple-design`, and says
|
|
79
|
+
that `emil-design-eng` loads by itself; `MOBILE_SURFACES.md` names `mobile-native` and
|
|
80
|
+
`animate-expo`; `VISUAL_REVIEW.md` names `break-ui` for worst-case data before a
|
|
81
|
+
populated capture; `MOTION_PRODUCTION.md` says none of them reads a render timeline.
|
|
82
|
+
- **Eleven rules reconciled, one value each** (table in `KNOWLEDGE_PROVENANCE.md`). The
|
|
83
|
+
doctrine changes four: no `ease-in` exit exception; the default spring is
|
|
84
|
+
`duration 0.4, bounce 0` with bounce only after a flick; `transition: all` and
|
|
85
|
+
entrances from `scale(0)` are banned; under reduced motion an opacity or colour change
|
|
86
|
+
that carries state may stay. It keeps its modal and drawer ceiling (200–300 ms;
|
|
87
|
+
500 ms only for a mobile sheet) against the tool's 200–500 ms, which contradicts the
|
|
88
|
+
tool's own under-300 rule, and the Cursor rule's stale `200–500ms` line is corrected
|
|
89
|
+
to match. `MOBILE_SURFACES.md` rule 1 now gives a hero `100svh` and an app shell
|
|
90
|
+
`100dvh`; the 28 packs that write `100dvh` are B-139.
|
|
91
|
+
- **D7 text defects.** A bold lead sentence in `SKILL.md` said twice in a row, and the
|
|
92
|
+
"Three looks" section of `SHELEG_DESIGN.md` pasted twice with two Contents lines, the
|
|
93
|
+
second copy linking to itself. One copy of each is kept.
|
|
94
|
+
- **The class, not only the instances.** `scripts/gen_contents.py` refuses a repeated
|
|
95
|
+
`## ` heading (a derived list derives the duplicate too, so drift never saw it), with a
|
|
96
|
+
validator plant; `test/sloplint.py` refuses the same sentence twice in a row, watched
|
|
97
|
+
failing on the pre-fix `SKILL.md`, and requires the `transition: all`, `scale(0)` and
|
|
98
|
+
"never a second entry point" text; `test/audit_regressions/xd-01.01.py` adds four
|
|
99
|
+
checks (permalink agrees with its row, adapted rows carry a notice, the reconciliation
|
|
100
|
+
is recorded, the looks name their source), three watched failing on the pre-fix files.
|
|
101
|
+
|
|
102
|
+
The `SKILL.md` body is 4746/4750 tokens after the edit (house auditor at `5ca5c36`).
|
|
103
|
+
Whether models follow the reconciled values was not measured.
|
|
104
|
+
|
|
1
105
|
## 1.61.2 — rendered copy is part of visual review
|
|
2
106
|
|
|
3
107
|
Visual review now inspects headings, hero fragments, captions and labels against
|
package/README.md
CHANGED
|
@@ -175,6 +175,7 @@ skills.
|
|
|
175
175
|
| `SKILL.md` | The agent-facing skill: discovery triggers, the principles, how to apply them, quick-reference rules, common mistakes |
|
|
176
176
|
| `SHELEG_DESIGN.md` | The full reference: architecture, layer-by-layer mechanics with code, the exact morph math, the DOM↔WebGL projection bridge, a build-from-scratch recipe, and why each piece works |
|
|
177
177
|
| `SURFACE_COMPOSITION.md` | Two decisions the pack layer does not make: the six depth layers of a scene, read before writing CSS for a cinematic page; and the handoff to `dataviz`, read before drawing a chart in any pack — token names are not uniform across the thirteen and an undefined custom property fails silently |
|
|
178
|
+
| `SLOP_MARKERS.md` | The visual floor: 49 markers of generated design (purple gradients, emoji as icons, default faces, the side stripe, the three default looks, banned motion forms…), each with severity, reason, what to do instead and the exception under which it is not slop. Holds with or without a pack; twenty of them are checked by `--lint` |
|
|
178
179
|
| `MOTION_DOCTRINE.md` | Whether to animate at all, before how: the frequency table that kills motion on high-repetition paths, the easing tree and the `ease-in` ban, the duration ceiling, the forbidden forms, and the reduced-motion contract. `SKILL.md` marks it required before any animation |
|
|
179
180
|
| `MOTION_PRODUCTION.md` | What changes when motion leaves the page as a file: the four things a rendered asset gives up, the two programmatic-video tools measured side by side with the licence threshold and the pre-1.0 risk stated, which one this library recommends and the three conditions that reverse it, how a pack's token layer crosses the render seam, and where the reduced-motion obligation lands once there is no user to read a media query |
|
|
180
181
|
| `DESIGN_SYNC_BRIDGE.md` | The Claude Design contract: what a pack sends to claude.ai/design and in what shape, the rule for each of the four reference types, and the border motion does not cross |
|
|
@@ -184,6 +185,29 @@ skills.
|
|
|
184
185
|
| `styles/tokens/*.css` | The ready-made token layer per pack, copied verbatim instead of transcribed (`workbench` and `field-notes` each ship a light `:root` plus a `data-theme="dark"` twin) |
|
|
185
186
|
| `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
|
|
186
187
|
|
|
188
|
+
### Check a project against the floor
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npx sheleg-design-skill --lint ./src # human report, exit 1 on any S1
|
|
192
|
+
npx sheleg-design-skill --lint ./src --json # [{id, rule, severity, file, line, snippet}]
|
|
193
|
+
npx sheleg-design-skill --lint ./src --ratchet sloplint-budget.json # per-file budgets that only fall
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The linter reads `html`, `css`, `scss`, `js`, `jsx`, `ts`, `tsx`, `vue`, `svelte`
|
|
197
|
+
and `astro`, skips dependencies, build output, hidden directories and tests
|
|
198
|
+
(`--include-tests` reads them), and checks the `lint:` rows of
|
|
199
|
+
[`SLOP_MARKERS.md`](plugins/sheleg-design/skills/sheleg-design/SLOP_MARKERS.md):
|
|
200
|
+
emoji used as icons (in markup and in data such as `icon: "📊"`), purple
|
|
201
|
+
gradients, gradient text, a default face with no declared reason, decorative
|
|
202
|
+
glass, the side stripe, glows, grid backgrounds, grey on colour, `transition: all`,
|
|
203
|
+
layout-property animation, `scale(0)`, `ease-in`, overshoot curves and a missing
|
|
204
|
+
`prefers-reduced-motion` path. Exit 0 means no S1 finding, 1 an S1 finding or a
|
|
205
|
+
file over its budget, 2 a usage error; S2 and S3 print and never block. A
|
|
206
|
+
recorded exception is a comment, `sheleg-lint-allow: V015 <reason>`, and the
|
|
207
|
+
reason is required. `--self-test` plants a defect for every rule. It is a pattern
|
|
208
|
+
reader, not a renderer: the catalogue's `review` rows stay with the render
|
|
209
|
+
critique.
|
|
210
|
+
|
|
187
211
|
## What you get out of it
|
|
188
212
|
|
|
189
213
|
- **A motion methodology, not a component dump.** Scroll-linked animation,
|
|
@@ -282,8 +306,8 @@ These run **in a clone of this repository**. The published npm package ships no
|
|
|
282
306
|
python3 test/validate.py # or: npm test
|
|
283
307
|
```
|
|
284
308
|
|
|
285
|
-
`npm test` is **
|
|
286
|
-
of the contract:
|
|
309
|
+
`npm test` is **several gates**, not one, and `validate.py` alone is about a
|
|
310
|
+
third of the contract:
|
|
287
311
|
|
|
288
312
|
| Gate | What it decides |
|
|
289
313
|
|---|---|
|
|
@@ -291,6 +315,8 @@ of the contract:
|
|
|
291
315
|
| `test/validate_palette.py` | contrast floors and semantic separation per theme, including three simulated dichromacies · AI-default-cluster provenance · **every contrast ratio the docs state, recomputed from the token layer** |
|
|
292
316
|
| `test/sloplint.py` | the bundle obeying its own bans, in token layers, fenced examples **and the inline CSS the packs prescribe in prose** · doctrine completeness · pack origin addressability |
|
|
293
317
|
| `node --check bin/cli.js` | the installer parses |
|
|
318
|
+
| `node test/installer_test.js` | both installers against throwaway HOMEs, above all the refusal to shadow an installed plugin |
|
|
319
|
+
| `node test/lint_test.js` | the project linter end to end: every S1 rule blocks on its plant, every quiet case stays quiet, the ratchet and the exit contract; `--self-test` checks the catalogue against the rules |
|
|
294
320
|
|
|
295
321
|
Each gate ships a `--self-test` that plants a defect it must catch (`npm run
|
|
296
322
|
selftest`), rejects an unknown argument instead of silently running the normal
|
package/bin/cli.js
CHANGED
|
@@ -155,10 +155,17 @@ function parseArgs(argv) {
|
|
|
155
155
|
version: false,
|
|
156
156
|
kit: null, // --kit <pack>: materialize a reference kit instead of installing
|
|
157
157
|
out: null, // --out <path>: where the kit goes
|
|
158
|
+
lint: null, // --lint <dir>: run the project linter (bin/lint.js) on a tree
|
|
159
|
+
json: false, // --json: lint findings as a JSON array
|
|
160
|
+
ratchet: null, // --ratchet <budget.json>: per-file budgets that only fall
|
|
161
|
+
includeTests: false, // --include-tests: lint test files and fixtures too
|
|
162
|
+
selfTest: false, // --self-test: every lint rule against its planted defect
|
|
158
163
|
error: null, // set → print help and exit non-zero
|
|
159
164
|
};
|
|
165
|
+
const seen = [];
|
|
160
166
|
for (let i = 0; i < argv.length; i++) {
|
|
161
167
|
const a = argv[i];
|
|
168
|
+
seen.push(a.split("=")[0]);
|
|
162
169
|
if (a === "--help" || a === "-h") opts.help = true;
|
|
163
170
|
else if (a === "--version" || a === "-v") opts.version = true;
|
|
164
171
|
else if (a === "--force" || a === "-f") opts.force = true;
|
|
@@ -190,10 +197,38 @@ function parseArgs(argv) {
|
|
|
190
197
|
const value = a.slice("--out=".length);
|
|
191
198
|
if (!value) opts.error = "--out needs a path";
|
|
192
199
|
else opts.out = value;
|
|
193
|
-
} else {
|
|
200
|
+
} else if (a === "--lint" || a.startsWith("--lint=")) {
|
|
201
|
+
const value = a === "--lint" ? argv[++i] : a.slice("--lint=".length);
|
|
202
|
+
if (!value || value.startsWith("-")) opts.error = "--lint needs a directory";
|
|
203
|
+
else opts.lint = value;
|
|
204
|
+
} else if (a === "--ratchet" || a.startsWith("--ratchet=")) {
|
|
205
|
+
const value = a === "--ratchet" ? argv[++i] : a.slice("--ratchet=".length);
|
|
206
|
+
if (!value || value.startsWith("-")) opts.error = "--ratchet needs a budget file";
|
|
207
|
+
else opts.ratchet = value;
|
|
208
|
+
} else if (a === "--json") opts.json = true;
|
|
209
|
+
else if (a === "--include-tests") opts.includeTests = true;
|
|
210
|
+
else if (a === "--self-test") opts.selfTest = true;
|
|
211
|
+
else {
|
|
194
212
|
opts.error = `unknown argument: ${a}`;
|
|
195
213
|
}
|
|
196
214
|
}
|
|
215
|
+
if (opts.error) return opts;
|
|
216
|
+
// The linter reads a tree and writes nothing, so any install or kit flag beside
|
|
217
|
+
// it is a request for two different things at once.
|
|
218
|
+
const LINT_ONLY = ["--json", "--ratchet", "--include-tests"];
|
|
219
|
+
const OTHERS = ["--cursor", "--claude", "--dir", "--force", "-f", "--kit", "--out"];
|
|
220
|
+
if (opts.selfTest && seen.some((x) => x !== "--self-test")) {
|
|
221
|
+
opts.error = "--self-test runs on its own";
|
|
222
|
+
return opts;
|
|
223
|
+
}
|
|
224
|
+
if (!opts.lint && seen.some((x) => LINT_ONLY.includes(x))) {
|
|
225
|
+
opts.error = `${seen.find((x) => LINT_ONLY.includes(x))} is only meaningful with --lint <dir>`;
|
|
226
|
+
return opts;
|
|
227
|
+
}
|
|
228
|
+
if (opts.lint && seen.some((x) => OTHERS.includes(x))) {
|
|
229
|
+
opts.error = `--lint reads a project and installs nothing — ${seen.find((x) => OTHERS.includes(x))} does not combine with it`;
|
|
230
|
+
return opts;
|
|
231
|
+
}
|
|
197
232
|
if (opts.flavor && opts.target) {
|
|
198
233
|
opts.error = "--dir cannot be combined with --cursor / --claude";
|
|
199
234
|
}
|
|
@@ -284,6 +319,20 @@ ${c("bold", "Claude Design")} ${c("dim", "(Claude Code only)")}
|
|
|
284
319
|
come out only when asked for by name. Then: npm install && npm run build,
|
|
285
320
|
and run /design-sync in that directory.
|
|
286
321
|
|
|
322
|
+
${c("bold", "Project linter")}
|
|
323
|
+
--lint <dir> Check a project against the visual floor in
|
|
324
|
+
SLOP_MARKERS.md (emoji icons, purple gradients, default
|
|
325
|
+
faces, banned motion forms and the rest of the lint: rows)
|
|
326
|
+
--json Findings as [{id, rule, severity, file, line, snippet}]
|
|
327
|
+
--ratchet <budget> Per-file budgets ({"<file>": count}) that may only fall;
|
|
328
|
+
also counts raw palette classes and hex outside tokens
|
|
329
|
+
--include-tests Read tests and fixtures too (skipped by default)
|
|
330
|
+
--self-test Every rule against its planted defect, and the
|
|
331
|
+
catalogue against the rules
|
|
332
|
+
|
|
333
|
+
Exit: 0 no S1 finding, 1 an S1 finding or a file over budget, 2 usage error.
|
|
334
|
+
S2 and S3 findings print and do not change the exit code.
|
|
335
|
+
|
|
287
336
|
${c("bold", "Default")}
|
|
288
337
|
Auto-detects: uses .cursor/ if present, else .claude/ if present,
|
|
289
338
|
otherwise creates .cursor/skills/${SKILL_SLUG}/.
|
|
@@ -540,6 +589,18 @@ function main() {
|
|
|
540
589
|
return;
|
|
541
590
|
}
|
|
542
591
|
|
|
592
|
+
if (opts.selfTest || opts.lint) {
|
|
593
|
+
const lint = require("./lint.js");
|
|
594
|
+
const io = { out: (l) => console.log(l), err: (l) => console.error(l) };
|
|
595
|
+
process.exitCode = opts.selfTest
|
|
596
|
+
? lint.selfTest(io.out)
|
|
597
|
+
: lint.runLint(
|
|
598
|
+
{ dir: opts.lint, json: opts.json, ratchet: opts.ratchet, includeTests: opts.includeTests },
|
|
599
|
+
io,
|
|
600
|
+
);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
543
604
|
const cwd = process.cwd();
|
|
544
605
|
|
|
545
606
|
if (opts.kit) {
|