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 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 **four gates**, not one, and `validate.py` alone is about a third
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) {