jig-ui 0.7.1 → 0.8.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 ADDED
@@ -0,0 +1,762 @@
1
+ # Changelog
2
+
3
+ ## 0.8.0
4
+
5
+ Everything here was found by handing Jig to agents that had never seen it and
6
+ asking them to build something real — its own documentation site. None of it
7
+ was found by the test suite, which passed throughout.
8
+
9
+ Minor rather than patch: `explain` prints lines it did not print before, and
10
+ `init` can write a file it did not write before.
11
+
12
+ ### Fixed
13
+
14
+ - **`jig explain` discarded most of the reasoning in the rules.** The parser
15
+ kept the first `❌` and the first `✅` and threw away everything else. **41 of
16
+ 104 rules carry prose outside that pair — 103 lines.** `A-04` explains why
17
+ shadow-only definition cannot hold 3:1; `C-22` loses sixteen lines; `C-49`
18
+ leads with a three-row table distinguishing when a link needs colour, when it
19
+ needs an underline, and when neither is required — and its pair alone says
20
+ "keep the underline". All of it shipped in the tarball, installed into every
21
+ skill directory, and was unreachable through the command built to read it.
22
+
23
+ - **`02-tokens.md` documented a Tailwind arrangement that fails.** It told every
24
+ reader to write `@theme { @import "./jig/theme.css"; }` and claimed it yields
25
+ `bg-surface`, `rounded-surface`, `p-card` and `text-body` as utilities.
26
+ Tailwind 4 rejects it: *"@theme blocks must only contain custom properties or
27
+ @keyframes."* The tokens cannot move into `@theme` regardless — it requires
28
+ them top-level and unnested, while they live in `:root` and are redeclared
29
+ under `[data-theme="dark"]` and a `prefers-color-scheme` query, which is what
30
+ makes dark mode work. The flat import is what works, and is what `init`
31
+ already wired without being told.
32
+
33
+ - **`02-tokens.md` named a token location the tool had stopped using.** It said
34
+ tokens live at `.jig/tokens/`, that this was "the only location, in every
35
+ scope and every project", and that "nothing relocates them" — while `init`
36
+ printed `Token layer: src/styles/jig/` in the same run. `check-tokens` rule 4
37
+ hardcoded the same path in a comment anticipating and forbidding this exact
38
+ change, so the guard held the claim in place and would have failed the build
39
+ on anyone correcting it. Shipped in 0.7.1.
40
+
41
+ - **A section headed "Notes for the author (not for the agent)" shipped to every
42
+ agent.** The heading was a label, not a mechanism: the file is in the tarball
43
+ and installs into every skill directory. A probe read it, quoted the
44
+ `A-07`/`A-08` line back as "the author's own notes… license to go tighter than
45
+ the shared default", and halved the radius scale on that authority. Moved to
46
+ `docs/house-positions.md`.
47
+
48
+ - **The published package shipped no changelog**, despite the repo holding 32KB
49
+ of one. Staging and `files` now change together, which the tarball guard
50
+ already enforced.
51
+
52
+ - **`A-01` had no stated scope for "then ask".** Two agents given the same brief
53
+ split on it — one shipped the unbranded default and deferred, the other
54
+ proposed a hue and argued the proposal *was* the ask — both citing the same
55
+ conflict-resolution clause. The rule now says a proposal is a question with a
56
+ suggested answer, not a decision.
57
+
58
+ - **`B-11` capped line length and stated no floor**, so a column could be halved
59
+ indefinitely and still pass. Measured on the documentation site: two
60
+ side-by-side panels rendering prose at 41 characters, in a mode whose
61
+ `--measure-prose` selects 68. The 40–80 band existed in `02-tokens.md` but not
62
+ in the file an agent reads for a line-length decision.
63
+
64
+ ### Added
65
+
66
+ - **Three detectors for the anti-slop rules, and a self-check that names them.**
67
+ Section A is Jig's answer to "what is AI slop" — fourteen rules for the
68
+ defaults a model reaches for when nothing was specified. **Three had
69
+ detectors.** The other eleven were enforced by one self-check question —
70
+ *"would this look different from a generic template?"* — which an agent that
71
+ has just produced a generic template answers yes to, and whose `A-01 → A-10`
72
+ range silently excluded `A-58`, `A-59`, `A-60` and `A-67`.
73
+
74
+ Jig's own documentation site shipped `<span aria-hidden="true">❌</span> Don't`
75
+ in the chrome of all 104 rule pages with `check --all` reporting nothing.
76
+
77
+ `A-05` (emoji as iconography) and `A-10` (placeholder content) were never
78
+ judgment calls — an emoji in a text node is a regex, and so is "lorem ipsum".
79
+ `A-09` (marketing voice) is the first **mode-gated** detector: it fires in
80
+ `product` and `operator`, stays silent in `editorial` where that register
81
+ belongs, and stays silent when no mode is declared rather than guessing.
82
+
83
+ The remaining eight stay judgment. Detecting "a container around every group"
84
+ needs to know what an element is and how heavy it looks, and six noisy checks
85
+ that fire on correct code is how a linter gets switched off. What reaches them
86
+ is the self-check, rewritten to name each tell the way every other item does.
87
+
88
+ - **`check` reports what it examined.** `0 errors · 104 rules, 0 fired` was
89
+ byte-identical whether forty components were examined and found clean or
90
+ nothing was examined at all. It now reads `· 26 files, 4 with styles`, the
91
+ `JIG_CHECK:` record carries `files=` and `styled=`, and a run where nothing
92
+ carried a style region says **"Nothing inspected."** rather than "No
93
+ findings." Both counts, because `.ts` and `.tsx` are style-bearing by
94
+ extension and the file count includes parsers and configs that can never
95
+ produce a finding.
96
+
97
+ Worth knowing: `check` defaults to changed files, so on a clean tree it scans
98
+ almost nothing. That was always true and always looked like a pass.
99
+
100
+ - **`init` offers a Tailwind v4 alias block.** The flat import gives you tokens,
101
+ not utility classes — Tailwind only generates those for names declared in
102
+ `@theme`. `init` can now generate that block, and **asks first**: it changes
103
+ how every component in a project is written, both styles are correct, and
104
+ silence is no. Under `--yes` it declines and says how to get it.
105
+
106
+ Only the 18 Tailwind namespaces are aliased. `--size-*`, `--measure-*`,
107
+ `--focus-ring-*`, `--border-width-*` and `--opacity-*` have none, and aliasing
108
+ one emits a declaration that generates nothing. 189 declared names filter to
109
+ 87 — and the filter also excludes every primitive `02-tokens.md` says never to
110
+ consume directly.
111
+
112
+ The generated file explains why `--radius-surface: var(--radius-surface)`
113
+ appears beside Jig's own declaration in the compiled CSS: Tailwind's lands in
114
+ `@layer theme`, Jig's is unlayered, and unlayered beats layered regardless of
115
+ source order. Without that note, someone finds it and "fixes" it.
116
+
117
+ - **The skill treats Tailwind setup as an ask.** Finding Tailwind in a project
118
+ is not permission to change how every component in it is written. The agent
119
+ reports what it found, shows both arrangements, and waits — and is told never
120
+ to write `@import "tailwindcss"` into a project that does not already have it.
121
+
122
+ - **A guard that staged assets stay out of git.** Adding `CHANGELOG.md` to the
123
+ prepack staging list committed a build artifact, because
124
+ `packages/cli/.gitignore` is a hand-maintained list that nothing forced into
125
+ step. On its first run the new guard caught a second, pre-existing hole:
126
+ `references` had been staged and unignored since it was added, saved only by
127
+ the directory not existing yet.
128
+
129
+ ### Corrected
130
+
131
+ - **0.7.0's notes said the non-TTY refusal "names the three ways out".** It
132
+ names three; two work. The guard runs before any config is read, so
133
+ `jig.config.json` alone still exits 1 — it selects the mode once you are past
134
+ the guard, it does not get you past it. The message is unchanged here and
135
+ recorded in `docs/known-follow-ups.md`, because the right wording depends on
136
+ whether the guard should consult the config first, which is a behaviour
137
+ question rather than a copy one.
138
+
139
+ ## 0.7.1
140
+
141
+ A rule file that contradicted the tool, and the guard that kept it that way.
142
+
143
+ Patch: the shipped rules change, but no command writes anything different.
144
+
145
+ ### Fixed
146
+
147
+ - **`02-tokens.md` named a token location that 0.7.0 stopped using.** It said
148
+ tokens live at `.jig/tokens/`, that this was "the only location, in every
149
+ scope and every project", and that "nothing relocates them". Since 0.7.0 the
150
+ layer follows the project: `init` puts it beside the stylesheet it wires and
151
+ prints the path it chose. So a single `init` run told you `Token layer:
152
+ src/styles/jig/` while the rule file an agent is explicitly told to load
153
+ before writing token code insisted that directory could not exist.
154
+
155
+ The file also disagreed with itself — four absolute examples, and one
156
+ relative one added when the Tailwind section was written.
157
+
158
+ It now describes the real behaviour, says pre-0.7.0 projects keep their
159
+ layout, and points at the `theme.css` barrel so relocating the layer costs
160
+ one line instead of every stylesheet.
161
+
162
+ - **`check-tokens` rule 4 was holding the claim in place.** It hardcoded the
163
+ same path, in a comment that anticipated and forbade this exact change:
164
+ "nothing — including a future `init` — relocates them". It was checking the
165
+ documentation against itself rather than against the CLI, so it could not
166
+ see the drift and would have failed the build on anyone correcting it.
167
+
168
+ It now asserts what stays true as the layer moves: token imports shown in
169
+ the rules must be relative. Mutation-tested in both directions.
170
+
171
+ ### Corrected
172
+
173
+ - **0.7.0's notes said the non-TTY refusal "names the three ways out:
174
+ `--yes`, a real terminal, or writing `jig.config.json` first."** The third
175
+ is not a way out on its own. The guard runs before any config is read, so a
176
+ config alone changes nothing — it selects the *mode* once you are past the
177
+ guard. `jig.config.json` plus `--yes` works; `jig.config.json` alone exits 1
178
+ with the same message that suggested it.
179
+
180
+ The message itself is unchanged in this release and still reads as though
181
+ the config were a third alternative. It is recorded in
182
+ `docs/known-follow-ups.md` rather than reworded here, because the wording
183
+ that is actually right depends on whether the guard should consult the
184
+ config first — a behaviour question, not a copy question.
185
+
186
+ ### How these were found
187
+
188
+ Not by the test suite, which passes 631 tests either way. By handing the rules
189
+ to agents that had never seen this repository and asking them to build
190
+ something real. Everyone who could have caught the token-location defect
191
+ already knew where the tokens were.
192
+
193
+ ## 0.7.0
194
+
195
+ A silent no-op, fixed, plus the missing half of 0.6.0's token-layer move.
196
+
197
+ Minor rather than patch, and the call is arguable. The non-TTY fix is a
198
+ correction; the relocation offer is new behaviour that can move files, though
199
+ only interactively and only with consent. Taking the higher of the two is the
200
+ reading that does not understate the change, and anyone who might be prompted
201
+ to move their token layer should read these notes.
202
+
203
+ ### Fixed
204
+
205
+ - **`jig init` without `--yes` exited 0 having written nothing when stdin was
206
+ not a terminal.** It printed the first prompt, read EOF, and stopped. Every CI
207
+ step, every piped invocation, and every agent shelling out without a TTY got a
208
+ silent no-op — and exit 0 having done nothing is the worst outcome available,
209
+ because the caller cannot tell it from success and then acts on a token layer
210
+ that was never created. Confirmed identical in 0.5.0, so this predates the
211
+ prompts added since. It now fails where the cause is still visible and names
212
+ the three ways out: `--yes`, a real terminal, or writing `jig.config.json`
213
+ first.
214
+
215
+ ### Added
216
+
217
+ - **`init` offers to move a legacy `.jig/tokens/` layout** to the location your
218
+ project's own structure suggests. 0.6.0 deliberately left an upgraded project
219
+ where it was — a silent relocation could break an import you wrote that `init`
220
+ knows nothing about — but that meant the improvement only ever reached new
221
+ projects, and the way out was one line of output that is easy to miss.
222
+
223
+ Interactively it asks; under `--yes` it declines and says how. Moving is all
224
+ four halves or none: write at the new location, remove the old copies, strip
225
+ the stale import before wiring the new one, and update `jig.config.json` so
226
+ the next run does not go back for them. A file you have edited is never
227
+ removed — it is named and left for you to clear.
228
+
229
+ ```text
230
+ BEFORE @import "../../.jig/tokens/brand.acme.css";
231
+ @import "../../.jig/tokens/mode.product.css";
232
+ AFTER @import "./jig/theme.css";
233
+ ```
234
+
235
+ ## 0.6.0
236
+
237
+ The token layer stops hiding in a dotfolder, and `check` starts reading it back.
238
+
239
+ Both came from the same question: who should write the tokens. The answer turned
240
+ out to depend on something that did not exist — **nothing validated the token
241
+ layer's own declarations.** `init` checked a brand colour once, at write time,
242
+ and no command ever looked again. A generated file edited afterwards, by a person
243
+ or an agent, went unexamined: `--color-text-weak` dropped to 22% opacity and
244
+ `check` reported "No findings". With that closed, where the files live and who
245
+ writes them become ordinary decisions rather than load-bearing ones.
246
+
247
+ **Upgrading:** run `npx jig-ui@latest update`, then `npx jig-ui@latest init`.
248
+ Existing installs keep their `.jig/tokens/` layout — nothing moves unless you
249
+ move it, because relocating files could break an import you wrote yourself.
250
+
251
+ ### Added
252
+
253
+ - **`check` validates the token layer** (`check/token-audit.ts`). Text roles to
254
+ 4.5:1, interface strokes to 3:1, **in both themes**, plus `--text-prose` at
255
+ 18px and `--size-touch-target` at 48px. Alpha foregrounds are composited over
256
+ each surface first — Jig's foregrounds are alpha by design, so a ratio before
257
+ compositing means nothing. Only floors, never density: `--size-control` at 28px
258
+ is a deliberate `operator` choice, and reporting it would teach you to ignore
259
+ the ones that matter.
260
+ - **`H-47` enforces the half of itself it only stated.** Its correction always
261
+ read "consume the semantic role, not the primitive"; the detector caught raw
262
+ hex and raw px and nothing else. `color: var(--brand-l)` was silent — and it is
263
+ the worse case, because it looks exactly like correct token usage while reading
264
+ a bare number and bypassing every theme override.
265
+ - **`exempt` in `jig.config.json`.** Some surfaces render outside the cascade: an
266
+ OG card in an SVG `foreignObject` carries no stylesheet, a PDF renderer never
267
+ sees CSS. Those files were in permanent violation, which is an adoption blocker
268
+ — a check that cannot pass is a check people switch off.
269
+
270
+ Nothing is exempt by default and no filename is baked in; this list is the only
271
+ source. Every run reports the **pattern** and what it excused, because an
272
+ over-broad glob does not fail — `check` simply gets quieter, and quieter looks
273
+ like progress:
274
+
275
+ ```text
276
+ 5 file(s) exempt via jig.config.json and not scanned:
277
+ src/*-card.css (5 files — likely too broad, review it)
278
+ src/nope.css (matches nothing — check the path)
279
+ ```
280
+
281
+ Prefer an exact path. An exemption is a claim about one file's rendering
282
+ context, and that is usually literally true of one file. `src/cv/pdf/**` is a
283
+ fact about a tree; `**/*-card.tsx` is a naming coincidence that would also
284
+ excuse every real card component in the project.
285
+ - **One import per surface, through a barrel.** `jig/theme.css` imports the brand
286
+ file and one mode file; your stylesheet imports that single line and then never
287
+ changes again. Switching mode rewrites Jig's file, not yours.
288
+
289
+ ### Changed
290
+
291
+ - **The token layer follows your project's layout.** `.jig/tokens/` was right
292
+ everywhere by being right nowhere — a tool dotdir holding product source,
293
+ reached from a Rails stylesheet by `@import "../../../.jig/tokens/…"`. The
294
+ default is now a `jig/` directory beside the stylesheet being wired, so the
295
+ import is `./jig/theme.css` in every ecosystem. `jig.config.json`'s `brand`
296
+ now decides placement, not just wiring — it previously honoured the path only
297
+ when the file already existed, which is the one case where it does not matter.
298
+ - **The mode is chosen before `init` runs.** It is the most consequential thing
299
+ `init` writes and the thing it is worst at choosing: `--yes` took `'/' → product`
300
+ without reading the project, and that config then outranks every agent's later
301
+ inference. The command file now tells the agent to ask what the product is, map
302
+ each surface, write the config, and only then run. `init` also reports the
303
+ surfaces it **used** rather than the ones it defaulted to — with a config
304
+ present it had been announcing a default it was about to ignore.
305
+ - A barrel holds exactly one mode, never a merge. Importing all three into one
306
+ document leaves only the last; verified in a browser, it yields `operator`
307
+ throughout. `01-modes.md` already said why that is not a loss: density switches
308
+ at the route boundary, never inside one view.
309
+
310
+ ### Fixed
311
+
312
+ - `--text-prose`, `--size-touch-target` and every semantic colour are now held to
313
+ their floors after `init` as well as during it.
314
+ - **`H-47`'s built-in token-layer skip was over-broad.** It matched
315
+ `(brand|mode).*.css` with the directory optional, so a project's own
316
+ `src/legacy/brand.colors.css` was silently exempt from the primitive check
317
+ wherever it sat. Scoped to the token layer's directory now — Jig owns `jig/`
318
+ outright, and a filename that merely looks like one of ours is a coincidence.
319
+ - The token audit follows the token layer instead of reading a fixed directory.
320
+ It was written when `.jig/tokens/` was the only possible answer, so moving the
321
+ layer made it find nothing and report nothing — caught in a pre-release smoke
322
+ test, where a brand file edited to 22% opacity passed cleanly. A check that
323
+ goes quiet when its subject moves is worse than one that was never written,
324
+ because the silence reads as a pass.
325
+ - Tailwind v4 does not scan workspace packages — documented, with the `@source`
326
+ directive it needs. Nothing errors when this bites: the class lands on the
327
+ element, no rule exists to match it, and the style simply does not apply.
328
+
329
+ ## 0.5.0
330
+
331
+ Four things shipped in 0.4.0 were broken in ways that reported success. `/jig
332
+ update` refreshed to the version already installed and said "Updated Jig →
333
+ 0.4.0". Dark mode could not be reached by choosing it. `check` skipped nearly
334
+ every colour in a project that had not run `init`. And the reconciliation of
335
+ every numeric default against an external reference is finished — 0 rows open —
336
+ which is where most of the rest of this release came from.
337
+
338
+ **If you are on 0.4.0, run `npx jig-ui@latest update` from a terminal.** The
339
+ `/jig update` fix cannot deliver itself: your command file is the broken one, so
340
+ the slash command will keep reporting a successful no-op until the CLI replaces
341
+ it. Once, from the terminal, and the slash command works from then on.
342
+
343
+ ### Fixed
344
+
345
+ - **`/jig update` could never upgrade anyone.** Every subcommand is invoked at
346
+ the installed pin so the CLI and the vendored rules agree; `update` is the one
347
+ exception, because its job is to move that pin. The skill file knew that and
348
+ the slash command did not, so it ran `npx jig-ui@<installed> update` — a no-op
349
+ that reports success, which is worse than an error.
350
+ - **Dark mode was unreachable by explicit choice.** `brand.default.css` had one
351
+ dark block, inside `@media (prefers-color-scheme: dark)`. A selector inside a
352
+ media query cannot match when the query is false, so `data-theme="dark"` on a
353
+ light-mode system produced no dark tokens at all. A second, unmediated block
354
+ now carries the same declarations, and a check keeps the two identical.
355
+ - **`check` did not resolve the project's own tokens.** The token map held only
356
+ Jig's vendored `.jig/tokens/*.css`, so any `var(--your-token)` was
357
+ unresolvable and skipped. On a project that had never run `jig init`,
358
+ `contrast-floor` and `violet-band-hue` evaluated very nearly nothing. A name
359
+ declared in two places with different values is still skipped — which value a
360
+ browser uses depends on import order, and a wrong guess reports a finding
361
+ against a value the page never renders.
362
+ - **`operator` prose text was 16px** against the 18px floor `B-75` states and
363
+ `02-tokens.md` repeats. It is the mode most likely to be read for hours.
364
+ - **Rules cited tokens that do not exist** — the `--color-danger` family,
365
+ `--color-surface`, `--leading-heading`, `--leading-display`,
366
+ `--font-weight-body`, `--spacing-unit`. An agent following those wrote a
367
+ `var()` resolving to nothing, with no error anywhere.
368
+ - **`C-19` named the same token twice**, once "for large text only".
369
+ `--color-text-weak` clears 4.5:1 at any size, and there is no lighter grey.
370
+ - **The error message in `P-03` moved above the input**, where autofill menus
371
+ and on-screen keyboards do not cover it.
372
+ - **CSS nesting and line endings.** `install` and `update` had three near-copies
373
+ of one write helper and one had lost its line-ending handling, so `update`
374
+ flattened a CRLF token file to LF while leaving the rule files beside it
375
+ alone. All three now share `install/writer.ts`, and writes are atomic.
376
+
377
+ ### Added
378
+
379
+ - **`P-13 · Ambient motion`** — a third category of motion the system lacked.
380
+ Interaction and transition motion are milliseconds; ambient motion is slow,
381
+ looping and decorative, and must never be noticed. `G-44` forbade all of it by
382
+ stating a 300ms ceiling it had never scoped, so an agent asked for a slow
383
+ decorative loop would have refused, citing a rule about something else.
384
+ - **`--duration-ambient-fast/base/slow`** (3s / 4.7s / 7.1s), `editorial` only.
385
+ The values are mutually prime in tenths of a second so layered loops do not
386
+ re-align into one visible pulse.
387
+ - **Fluid headings.** `--text-h1` and `--text-h2` are `clamp()` in `editorial`
388
+ and `product`, reaching their minimum at a 360px viewport and their maximum at
389
+ 1024px. Every term is `rem`-based: a `px` or bare-`vw` bound ignores a
390
+ reader's font-size preference, which is a WCAG 1.4.4 failure.
391
+ - **`explain` finds rules you cannot name.** A word searches every title and
392
+ body; one match prints in full. `--list` prints every id, or one section's.
393
+ - **Easing direction** — `--ease-out` on entry, `--ease-in-out` on exit, never
394
+ linear for anything that moves. Both tokens shipped with no rule for choosing.
395
+
396
+ ### Changed
397
+
398
+ - **`--text-h1` resolves to 32px on a phone**, not 48px. This is the change most
399
+ likely to be visible in an existing project, and it is the point: at a fixed
400
+ 48px, a 45-character headline set as four lines and 211px of headline on a
401
+ 360px screen.
402
+ - **Line heights are documented per mode.** They always were per mode; the docs
403
+ quoted one mode's values as though they were everyone's, and were wrong for
404
+ two modes out of three.
405
+ - **The README covers installing and using Jig, and nothing else.** How to
406
+ change a rule and how to test that a rule earns its context cost moved to
407
+ `AGENTS.md`, where an agent working on this repository will read them.
408
+
409
+ ### Reconciliation
410
+
411
+ `RECONCILE.md` is at **0 open rows**. Every numeric default is either adopted
412
+ from the reference or deliberately kept with its argument stated in one line.
413
+ The motion rows are the exception worth knowing: that reference has no motion
414
+ chapter, so those values were settled against separate sources, and where no
415
+ source gave a number they are kept as ours on stated reasoning rather than
416
+ adopted.
417
+
418
+ `check-tokens` grew from 5 rules to 12, each mutation-tested. Two of the new
419
+ ones exist because a guard had been passing vacuously: rule 6's regex was
420
+ line-anchored and so covered 103 of 133 tokens while claiming to cover all of
421
+ them, and nothing at all checked that a token cited in the rules exists.
422
+
423
+ ## 0.4.0
424
+
425
+ Jig stops copying itself into your project. It is a skill an agent reads, and
426
+ 0.3.0 wrote 220KB across 17 files into every consuming repo to deliver it —
427
+ roughly 200KB of that Jig's own property, read by an agent that already had it
428
+ from the skill install. A single-mode project now gets **three** files, all of
429
+ them its own.
430
+
431
+ ### Changed
432
+
433
+ - **`install` writes one place: the harness's skill directory.** `SKILL.md`, the
434
+ rules, `rules.index.json` and attribution live beside each other at
435
+ `<harness>/skills/jig/`, project or global. Nothing lands in `.jig/` any more.
436
+
437
+ The rules are not a build input — nothing compiles them, and the agent reading
438
+ them already has them. The genuine exception is CSS: a stylesheet `@import` is
439
+ an edge in a build graph and must resolve inside the project, on every machine
440
+ that builds it. That is the one category `init` still copies.
441
+
442
+ - **`.jig/` holds only what belongs to the project** — the brand file, the mode
443
+ files for the modes actually declared (not all three), and `state.json`.
444
+
445
+ - **A project install refuses when a global one exists.** Two `jig` skills
446
+ registered with the same harness, whose rule paths point at different places,
447
+ is exactly the incoherence this layout exists to prevent.
448
+
449
+ - **Every harness comes from one table.** Five adapters — Claude Code, Cursor,
450
+ opencode, Gemini CLI, and a generic `.agents/skills` — share the
451
+ `<harness>/skills/<name>/` convention, so adding one is a row rather than a
452
+ file. Codex keeps a bespoke implementation because `AGENTS.md` genuinely is a
453
+ different mechanism.
454
+
455
+ ### Fixed
456
+
457
+ - **The skill told agents `check` was planned.** It shipped in 0.3.0. A baseline
458
+ run reported "the CLI is ahead of the doc" and worked by hand rather than
459
+ running it. The guard that should have caught this compared the metadata
460
+ against a hand-maintained list, so when `check` landed and neither was
461
+ updated, the two agreed and the test stayed green. The list is now read out of
462
+ the CLI source, and agreement is asserted in both directions.
463
+
464
+ - **The skill sent agents to an unpinned CLI.** `npx jig-ui` resolves to whatever
465
+ is latest on npm, which need not be the version that wrote the bundle. Two
466
+ baselines hit this and fell back to working by hand. The skill now names the
467
+ version that built it, and `jig update` moves both together.
468
+
469
+ - **`JIG_CHECK:` named two different records.** The CLI emitted `version=
470
+ mechanical= judgment=`; the skill told the agent to emit `version= mode=
471
+ self_check=` — disjoint fields under one label. There is now one field set,
472
+ `version mode mechanical judgment`, asserted against both sources. An emitter
473
+ that cannot determine a field says so in the value rather than dropping it.
474
+
475
+ - **Every vendored file cited a licence path that no longer exists.** The header
476
+ hardcoded `.jig/LICENSE`, true only while install vendored into `.jig/`. In
477
+ the new layout the one line whose job is directing a reader to the licence
478
+ directed them nowhere. It is now computed from the file's depth in the bundle.
479
+
480
+ - **`update` refreshed only one harness.** A project can hold several installs,
481
+ each with its own manifest; the rest stayed pinned at their install version
482
+ with nothing said about them. It now refreshes every one and reports each.
483
+
484
+ - **`update` wrote files before checking the path.** `assertSafeRelPath` covered
485
+ adapter-rendered files but not `referenceDir`, which the harness table derives
486
+ just as directly. The shipped table is asserted at module load, so a bad entry
487
+ fails at import rather than at whichever command writes first.
488
+
489
+ - **A project-scope Codex install wrote Jig's rules into your project's
490
+ `.jig/`.** Codex has no skill-directory convention, so its bundle went to a
491
+ bare `.jig` — the one directory this release reserves for the project's own
492
+ material. Install plus `init` left 13 files there, Jig's rules and manifest
493
+ interleaved with your tokens and state, and the harness that most needed the
494
+ 0.4.0 separation was the only one that did not get it.
495
+
496
+ Two concrete harms beyond the untidiness: `init`'s legacy scanner looks in
497
+ `.jig/` for exactly those artifacts and offers to remove them, so it could
498
+ offer to delete a live install; and one directory holding two manifests is
499
+ what let a stale `.jig/manifest.json` hijack `update` in the first place.
500
+
501
+ Codex now uses `.codex/.jig/` at both scopes, mirroring the path its global
502
+ install already used. Verified against the real Codex CLI: it reads
503
+ `AGENTS.md`, resolves every rule path under the new location, and picks up the
504
+ declared mode. Upgrading leaves the old bundle in place — nothing is deleted —
505
+ and `update` now names it rather than reporting a bare "Jig is not installed"
506
+ to someone looking straight at the files.
507
+
508
+ - **`update` could never move an install forward.** Pinning the CLI fixed version
509
+ skew, but applied to every command it trapped the install: `npx jig-ui@0.4.0
510
+ update` refreshes to 0.4.0, reports success, and changes nothing. `update` is
511
+ the one command whose job is moving the pin, so it is the one that does not
512
+ carry it — the skill renders `npx jig-ui@latest update`.
513
+
514
+ - **`init --yes` chose a mode silently.** It takes `'/' → product` without
515
+ inferring anything, and `01-modes.md` rule 1 makes the config authoritative
516
+ over an agent's own inference — so the default is not a neutral placeholder,
517
+ it binds every agent that reads the project afterwards. Two baseline runs on
518
+ an `ops-console` project read every signal as `operator`, found `product`, and
519
+ correctly deferred to it; one noted the density difference is expensive to
520
+ reverse. The brand colour already stated its default and why. The mode now
521
+ does too, and says where to change it.
522
+
523
+ - **A source build could stamp a pin to a version that was never published**,
524
+ silently, until an agent tried to run it. `install` and `update` now say so
525
+ when the running CLI is not an npm-installed package.
526
+
527
+ - **Concurrent runs lost each other's manifest entries.** Two runs against one
528
+ install — two agents, or a script running `jig init` across a monorepo against
529
+ a shared global install — both read the manifest and both wrote it, and the
530
+ last writer's copy dropped the other's records of "Jig owns this file".
531
+ Losing one makes a later `update` treat that file as the user's and stop
532
+ refreshing it: the safe direction, but silent.
533
+
534
+ Fixed without a lock. The `files` map is additive and per-file — a run only
535
+ records entries for files it actually wrote — so merging against whatever is
536
+ on disk at write time is correct, and needs none of the stale-lock recovery a
537
+ mutex would after a process is killed mid-run. Writes are atomic
538
+ (temp + rename), and verified-and-retried to close the window between the read
539
+ and the rename. Ten parallel processes writing fifty entries now keep all
540
+ fifty; without the merge, one survives.
541
+
542
+ - **An asset could be staged at prepack and left out of the tarball** — correct
543
+ code reading an asset that never shipped. Both lists must now agree, verified
544
+ against the real `npm pack` output.
545
+
546
+ - **A legacy `.jig/manifest.json` hijacked `update`** and resurrected the whole
547
+ vendored layout.
548
+
549
+ - **Agents invented token values when `init` had not been run.** A baseline run
550
+ with the skill installed but the project not initialised authored its own
551
+ `:root` block — "control height (32), row height (48), duration (120ms) and
552
+ the near-black brand default are my resolved values, not the system's" — and
553
+ wrote it into the project's stylesheet. Step 5 said "consume tokens by
554
+ semantic name only", and it complied to the letter by inventing the
555
+ definitions behind the names.
556
+
557
+ `SKILL.md` now opens with the precondition: no `jig.config.json` means no
558
+ token layer, so run `init` and stop, and do not author a `:root` block of your
559
+ own. Step 5 gained the counter — a token with no value is a finding to report,
560
+ not a number to supply. Re-run on the same fixture, the agent invented
561
+ nothing, and reported a real gap in the token contract instead.
562
+
563
+ ### Added
564
+
565
+ - **`/jig` slash commands.** `install` writes a command file for every harness
566
+ that has a slash-command mechanism, so `/jig init`, `/jig check --all` and
567
+ `/jig update` work without leaving the session. One file named `jig`
568
+ dispatching on its arguments, which is what gives `/jig init` with a space
569
+ rather than a separate `/jig-init` per subcommand.
570
+
571
+ The command is not a thin wrapper: `/jig check` runs the CLI for the seven
572
+ mechanical rules, then applies the 97 judgment rules to the same files and
573
+ merges both halves into one report keyed by rule id — the half a CLI cannot
574
+ do, which is the reason the command exists at all.
575
+
576
+ Claude Code, Cursor, opencode and Gemini CLI (which gets its own TOML shape).
577
+ Not Codex: its custom prompts are not expanded by `codex exec` — probed
578
+ directly — so a file there could sit and never fire. Only subcommands the CLI
579
+ actually registers are offered, since a slash command that errors out is worse
580
+ than one that does not exist.
581
+
582
+ - **`check` reads CSS wherever it lives.** It read `.css`/`.scss`/`.less` only,
583
+ which for most projects meant it examined almost nothing — and said nothing
584
+ about that. A Tailwind v4 fixture with `bg-[#6D28D9] p-[13px] rounded-[12px]
585
+ text-[22px] h-[32px]` reported "No findings · 0 errors · mechanical=pass:0". A
586
+ clean bill of health for a project where every value bypassed the token layer.
587
+
588
+ Now covered: `<style>` blocks (HTML, Astro, Vue, Svelte, PHP, ERB, Twig,
589
+ Handlebars, MDX, ASP and ASP.NET, Razor, JSP, Phoenix, EJS, Nunjucks, Liquid,
590
+ Jinja, Velocity, FreeMarker), the indentation-delimited templates (Pug's
591
+ `style.`, Haml's `:css`, Slim's `css:`, each with its own attribute syntax),
592
+ `style="…"` and `style={{ }}`, CSS-in-JS tagged templates
593
+ (`styled.button`, `styled(Link)`, `css`, `createGlobalStyle`, `keyframes`),
594
+ Tailwind arbitrary values, and Tailwind default-palette contrast pairs.
595
+ `@theme` counts as the token layer, so a v4 project that has adopted Jig is
596
+ recognised as having done so.
597
+
598
+ The mechanism is extraction rather than a detector per language: everything
599
+ that is not CSS is blanked, preserving character positions, and the existing
600
+ detectors run unchanged — so a finding in a `.vue` file still points at the
601
+ right line, and all seven rules gained host-language support at once.
602
+
603
+ Two deliberate limits. A bare `p-4` is not a finding: it resolves through a
604
+ scale, and flagging it would mean flagging correct Tailwind. A colour outside
605
+ the default palette is not resolved rather than guessed at.
606
+
607
+ - **Reference files ship beside the skill.** `references/**` in the package
608
+ installs into the bundle with its subdirectory shape preserved, refreshed by
609
+ `update` under the same rule as the rules: replace when untouched, skip when
610
+ you have edited it. Adding one is a file drop, no code change.
611
+
612
+ **No reference ships in 0.4.0.** Four were planned — a procedure for `init`,
613
+ for `check`, for building a component, and one for resolving apparent rule
614
+ conflicts. Each was tested first by running an agent on the task with no
615
+ guidance, and in every case the agent already did the right thing: it inferred
616
+ the mode with signals stated, kept a 32px control inside a 48px target rather
617
+ than reading the two as contradictory, surfaced the brand question instead of
618
+ guessing, and handled loading, error and never-checked states unprompted.
619
+ `04-principles.md`'s tiebreakers were doing the work the references were meant
620
+ to do. Writing them anyway would have added words the rules already carry.
621
+
622
+ ### Migration
623
+
624
+ `install` no longer writes rule files into `.jig/`, so a pre-0.4.0 project has
625
+ rules in two places. The old copies are yours to remove; nothing deletes them
626
+ for you, because you may have edited one and that edit is yours to keep. Run
627
+ `jig install --agent <name>` to place the new bundle, then delete `.jig/rules/`
628
+ and `.jig/rules.index.json` once you have checked them for your own changes.
629
+
630
+ ## 0.3.0
631
+
632
+ Two new commands. `install` and `update` put the system in place; these two make
633
+ it do something.
634
+
635
+ ### Added
636
+
637
+ - **`jig check`** — verifies a consumer's code against the rules. Seven
638
+ detectors, exactly the ones `rules.index.json` already named: `gradient-text`
639
+ (A-02), `backdrop-blur` (A-04), `pure-black-white` (C-18), `contrast-floor`
640
+ (C-19), `focus-removed` (E-29), `hardcoded-value` (H-47), and
641
+ `violet-band-hue` (A-01, hybrid — it asks rather than fails). With `--all`,
642
+ `--ci` (mechanical bucket only, no model, deterministic) and `--json`.
643
+
644
+ H-47 runs only on files that reference a Jig token or import a vendored token
645
+ file. "Hard-coded *past* the token layer" means nothing for a file that never
646
+ adopted it, and without that scope it produced 13 of 13 findings on a 20-line
647
+ stylesheet. A project where nothing participates is told how to start.
648
+
649
+ - **`jig init`** — sets a project up to use the system. Detects the CSS system,
650
+ derives a brand colour from existing code rather than interviewing cold,
651
+ validates it against the contract `brand.default.css` already states, writes
652
+ the brand file and `jig.config.json`, wires the imports, and runs `check` for
653
+ a baseline.
654
+
655
+ This is also what makes global installs coherent. The brand file and config
656
+ always live in the project, and for a global install the one selected mode
657
+ file is copied into the project's `.jig/tokens/` so the import is
658
+ project-relative. A `$HOME`-relative CSS import resolves only on the machine
659
+ that generated it; a stylesheet is committed and must build everywhere.
660
+
661
+ - `oklch()` is parsed. Tailwind v4 and shadcn emit it by default, so skipping it
662
+ meant `init` derived nothing on a large share of new projects and `C-19`
663
+ computed no contrast against Jig's own `--color-bg-base`.
664
+
665
+ ### Fixed
666
+
667
+ - **CSS nesting hid the parent's declarations.** A block containing braces was
668
+ discarded whole, so in `.card { color: red; .h { … } }` the `color: red`
669
+ belonged to no block and was invisible to every detector. On a Sass codebase
670
+ that was most declarations, reported as a clean result.
671
+
672
+ - Comments produced findings, and a commented-out `:focus-visible` silenced
673
+ E-29 for a whole file — a dead detector reporting success.
674
+
675
+ - `var(--x, fallback)` was resolved to the fallback and reported as fact.
676
+
677
+ - The large-text contrast exemption was inert because only `px` font-sizes were
678
+ recognised, so `2rem` at 3.54:1 was flagged as failing a 4.5:1 floor that did
679
+ not apply to it.
680
+
681
+
682
+ ## 0.2.1
683
+
684
+ ### Fixed
685
+
686
+ - **`--color-text-warning` and `--color-text-success` failed their contrast
687
+ floors.** Warning was 3.64:1 and success 4.48:1 against the surfaces they can
688
+ land on, where text requires 4.5:1 and strokes 3:1. The source is explicit:
689
+ system colours used for text need 4.5:1; used for interface elements and
690
+ icons, 3:1. `RECONCILE.md` also lists the contrast floors as the one category
691
+ not up for reconciliation, and `C-19` is a rule about this exact failure — so
692
+ the system was breaking its own hardest rule, in a shipped default that every
693
+ consumer inherits unless they override it.
694
+
695
+ Warning lightness 36% → 29%, success 26% → 23%. Hue and saturation unchanged,
696
+ so both are the same colour, darker. All four semantic colours now clear both
697
+ floors against `bg-base`, `bg-raised`, and `fill` on either.
698
+
699
+ - The system colours now state what a replacement must satisfy. The brand colour
700
+ already carried that contract; these did not, so a user bringing their own
701
+ error or warning colour — the intended workflow — had no floor to hit.
702
+
703
+ ### Added
704
+
705
+ - `scripts/check-tokens.mjs` gains two rules, both mutation-tested. Rule 5
706
+ computes contrast from the token values and fails the build; this is the only
707
+ defect class here that arithmetic can catch, and it shipped twice because
708
+ nobody was doing the arithmetic. Rule 6 fails the build when a token is
709
+ defined but never rendered by the preview.
710
+
711
+ - `packages/preview` — a rendering harness. Every prior check verified the
712
+ system by arithmetic or grep; nothing had looked at it. Plain HTML and CSS,
713
+ no build, not published. It found two gaps on its first run: there is no
714
+ border-width token and no focus-ring geometry tokens.
715
+
716
+ ## 0.2.0
717
+
718
+ The first release that actually works end to end. `0.1.0` shipped rules that
719
+ cited tokens it never installed, and a token name that did not exist.
720
+
721
+ ### Fixed
722
+
723
+ - **`--color-brand` was cited nine times and defined nowhere**, including
724
+ `03-patterns.md`'s primary-button spec. An agent following that rule wrote
725
+ `background: var(--color-brand)` and got an unset custom property.
726
+ - **The design tokens were never installed.** The rules cite tokens 22 times
727
+ and `02-tokens.md` instructs the reader to import them, but `install` only
728
+ wrote the rule markdown. The CSS was in the published tarball the whole time
729
+ and never copied out.
730
+ - **Rule `C-49` had no correction and `I-80` had no anti-pattern**, in a file
731
+ whose own contract states every rule pairs both. The totals hid it — the two
732
+ defects cancelled at 103/103.
733
+ - **Ten values in the mode profile tables contradicted the tokens**, including
734
+ editorial body size, section rhythm, card padding, and a heading step that
735
+ did not exist.
736
+ - **`A-07`'s correction had silently inverted from true to false.** A mechanical
737
+ rename of `--radius-base` to `--radius-sm` turned a correct statement about
738
+ radius derivation into an incorrect one, with nothing to catch it.
739
+ - **`A-07`'s prohibition used a `16px+` threshold** that flagged `--radius-md`,
740
+ the value the rule exists to sanction.
741
+ - **`C-68` depended on a radius token it never named.** "A badge is more rounded
742
+ than a button" now cites `--radius-full`.
743
+
744
+ ### Changed
745
+
746
+ - Tokens vendor to **`.jig/tokens/`** on install, in every scope. That is the
747
+ only location; `update` refreshes them there and nothing relocates them.
748
+ - Editing a vendored token file is expected: `install` and `update` both leave
749
+ files you have changed alone and report them skipped.
750
+ - The mode profile tables cite token names instead of literals; the comparison
751
+ table is ordinal. A number in prose is a call site.
752
+ - The semantic colours are defined once each as `--<name>-h/s/l`. Every variation
753
+ previously repeated the same literal four to five times, so changing a colour
754
+ meant editing every copy or the variations desynchronised.
755
+
756
+ ### Added
757
+
758
+ - `scripts/check-tokens.mjs`, run by `npm test`. Four rules, each mutation-tested
759
+ against the drift it guards: the type table must match its tokens by name, no
760
+ unanchored literal in a prose table, no chosen colour literal repeated in a
761
+ token file, and every token import must use the canonical path.
762
+