Repository navigation
Releases: pawn002/klar
Release list
v3.1.0
klar's first eval (evals/skill-vs-playbook) measured how agents find klar's guidance. A CLAUDE.md pointer led them to the playbook in 0 of 69 trials; an Agent Skill loaded it in 69 of 69. This release ships that skill. The same eval found the wcag2 false pass fixed below.
Upgrading
npm install -g klar-cli@3.1.0 # or bump your devDependency
klar skill install # add the Agent Skill to this project (--user for all projects)If you use --type wcag2, read the Fixed note below: some displayed ratios drop by 0.1, and find may return a slightly different color.
Added
klar skill installwrites klar's Agent Skill to.claude/skills/klar/SKILL.md(or~/.claude/skills/klar/with--user). It won't overwrite a customizedSKILL.mdwithout--force(exits1when it leaves one alone).klar skill showprints the file for agents that keep skills elsewhere.klar playbookprintsAGENT_PLAYBOOK.mdfrom the installed package (--pathprints its location). The skill points here because the file's path differs between a local dependency,npm install -gandnpx.- The skill ships in the package at
skills/klar/SKILL.md. Its description is the one the eval tested; a test keeps the two in step. Its body namesklar playbookrather than thenode_modulespath the eval used, so it works for global andnpxinstalls too.
Fixed
-
wcag2no longer passes colors that fall short of the target (#17). Ratios were rounded to one decimal before being compared, so a true 4.458 displayed and passed as 4.5 incontrast, andfindreportedsuccess: truefor it. WCAG 2.x thresholds are unrounded:findnow compares at full precision, and wcag2 values are displayed rounded down to one decimal, so a shown value at or above a threshold always is.Behavior change for wcag2 users: some displayed ratios drop by 0.1 (for example 5.564 shows as 5.5, not 5.6), and
find -t wcag2can return a slightly different color. The old one may not have met the target. OKCA and deltaE output is unchanged.
Changed
AGENT_PLAYBOOK.md's setup section now recommends the skill, citing the eval, and keeps theCLAUDE.mdsnippets as a fallback for agents without skill support. The snippets point toklar playbook.
Full changelog: https://github.com/pawn002/klar/blob/main/CHANGELOG.md
v3.0.0
klar 3.0 measures out-of-gamut colors honestly, and says so instead of passing them through silently.
A color authored in OKLCH can sit outside sRGB. It has to be resolved to a displayable color before it can be measured — so the number describes that mapped equivalent, not the color in your token file. Previous versions reported it without a word.
⚠️ Read this before upgrading
1. contrast now exits 1 on out-of-gamut input
The value still goes to stdout. The exit code stops it being adopted as though it described what you asked about.
Expect this to fire. On a real OKLCH-authored design system, 29% of distinct color tokens and 21% of measured pairings were outside sRGB — including subtle ones like oklch(0.97 0.02 278.14). The gamut narrows to a point at both lightness extremes, so low chroma is no protection.
Under set -e, a bare assignment aborts your script at the first such token, leaving a partial result that reads like a complete short run:
# Guard it
for T in "${TOKENS[@]}"; do
if OKCA=$(klar contrast "$T" "$BG" -q); then
echo "$T -> $OKCA"
else
echo "$T -> $OKCA (outside sRGB — figure describes the mapped color)"
fi
doneNode's execSync throws the same way, stranding the value in err.stdout:
let out;
try { out = execSync(cmd, { encoding: 'utf8' }); }
catch (e) { out = e.stdout; outOfGamut = true; } // exit 1 still produced a valuePass --allow-out-of-gamut to waive it per call. There is deliberately no environment variable for the waiver — a global mute gets set once in CI and never removed.
Colors klar produces — variants cells, find results, match output — are in gamut by construction and never trigger this.
2. Recorded figures shift by 0.1
okca is now scored at full precision instead of through an 8-bit hex round-trip. Figures move by exactly 0.1, never more, and toward accuracy — the number now scores the color you wrote down rather than its rounding.
How often depends on how your tokens were authored, and the range is wide: random OKLCH coordinates moved 6.5%, but a real design system of hand-authored round values (oklch(0.27 0.06 245.34)) moved 47% of 216 pairings. Deliberately-chosen OKLCH values essentially never land on the 8-bit grid, so assume most of your recorded figures shift.
On that same system, the shift produced zero pass/fail verdict changes against recorded floors. Re-baseline; don't treat it as a regression.
3. find may return a color with reduced chroma
An out-of-gamut reference is now normalized into the gamut before searching — mandatory, since otherwise there is no renderable color to return. Read axesAdjusted and gamut to see what moved. Targets that previously failed may now succeed.
It will not trade brand saturation for contrast on its own. When lightness alone falls short it exits 1 and reports resolvableBy — the chroma, lightness, and ΔE cost of a fix — without applying it. --allow-desaturation records that a human made that call.
Fixed
Issue #9 reported one defect. It turned out to be three instances of one pattern — a plausible value, no signal, error running in the permissive direction:
contrastmeasured a color nobody chose. It routed through hex serialization, which silently applies CSS Color 4 mapping, sooklch(0.79 0.22 25)was scored as#ff938b— neither the authored color nor anything deliberate. The ANSI swatch on the same line rendered#ff746f. Two unrelated gamut conversions that disagreed.findreturned the reference unchanged and unrenderable when its chroma was out of gamut at every lightness. The fallback written to prevent exactly that stayed null.lightnessinvented a range.oklch(0.5 0.9 25)renders at no lightness, but it reported{lightMin: 0.5, lightMax: 0.5}at exit 0.
Also: find could report a target unreachable that the unmodified reference already met, and achromatic colors would have returned null on the new code path had it not been caught.
New
Three flags, and machine-readable outcomes.
--gamut-map <css|clip|none> |
Out-of-gamut resolution on contrast. Defaults to CSS Color 4 mapping |
--allow-out-of-gamut |
Waive the exit 1 |
--allow-desaturation |
Permit find to reduce chroma |
KLAR_GAMUT_MAP |
Sets the mapping process-wide |
find gains reason (ok, lightness-exhausted, unreachable, chroma-exhausted), resolvableBy, axesAdjusted and deltaE. Branch on reason, never on message — the prose wording is not stable.
klar takes no position on what any particular browser does. Engines vary and change; the spec is a stable reference and an implementation is not.
Full migration notes: CHANGELOG.md — now shipped in the npm package too.
Deferred and tracked: #10 (wide gamut), #11 (plugin gamut declaration), #12 (bulk audit), #14 (dogfood check on the find default), #15 (build-before-test).
What's Changed
- 3.0: gamut-aware measurement, and two more instances of the same defect by @pawn002 in #13
- build: ship CHANGELOG.md in the published package by @pawn002 in #16
Full Changelog: v2.0.0...v3.0.0
v2.0.0
⚠️ Breaking changes
1. Contrast scores changed — @pawn002/okca ^1.0.1 → ^2.0.2
okca 2.0 recalibrated its constants (CHROMA_K, POL_K, LOD_CAP, DOL_CAP), so the same colors score differently than they did under klar 1.x:
| v1.0.1 | v2.0.0 | |
|---|---|---|
| light-on-dark cap | 21 | 20.9 |
| dark-on-light cap | 20 | 20 |
white on #767676 |
~3.5 | ~3.9 |
A pair that cleared a threshold under 1.0.1 may not under 2.0.0, and vice versa. If you have design tokens validated against klar 1.x output, re-run them before upgrading. The FP=0 guarantee now holds by construction rather than by sampling — see FP0_PROOF.md.
2. klar pair --min-lightness / --max-lightness removed
These flags parsed but never constrained the output — passing them was already a no-op. They now exit 2 as unknown options instead of being silently ignored. Remove them from any scripts.
3. Usage errors exit 2, not 1
commander's parse errors (unknown option, unknown command, missing argument) previously exited 1 — the same code klar uses for soft failure, making klar find --targt 4.5 indistinguishable from "no color meets the target" to a script branching on $?. Usage errors now exit 2, matching the documented contract. --help and --version still exit 0.
Package versions
@pawn002/klar-plugin-interface and @pawn002/klar-plugin-registry are unchanged and stay at 1.0.1. The plugin API did not break — only klar-cli goes to 2.0.0.
What's Changed
Full Changelog: v1.0.1...v2.0.0