Skip to content

Releases: pawn002/klar

v3.1.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 00:51

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 install writes klar's Agent Skill to .claude/skills/klar/SKILL.md (or ~/.claude/skills/klar/ with --user). It won't overwrite a customized SKILL.md without --force (exits 1 when it leaves one alone). klar skill show prints the file for agents that keep skills elsewhere.
  • klar playbook prints AGENT_PLAYBOOK.md from the installed package (--path prints its location). The skill points here because the file's path differs between a local dependency, npm install -g and npx.
  • 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 names klar playbook rather than the node_modules path the eval used, so it works for global and npx installs too.

Fixed

  • wcag2 no 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 in contrast, and find reported success: true for it. WCAG 2.x thresholds are unrounded: find now 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 wcag2 can 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 the CLAUDE.md snippets as a fallback for agents without skill support. The snippets point to klar playbook.

Full changelog: https://github.com/pawn002/klar/blob/main/CHANGELOG.md

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 02 Aug 17:50
ee15251

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
done

Node'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 value

Pass --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:

  • contrast measured a color nobody chose. It routed through hex serialization, which silently applies CSS Color 4 mapping, so oklch(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.
  • find returned the reference unchanged and unrenderable when its chroma was out of gamut at every lightness. The fallback written to prevent exactly that stayed null.
  • lightness invented 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

Choose a tag to compare

@github-actions github-actions released this 27 Jul 22:30

⚠️ 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

  • Upgrade to okca 2.x and re-baseline everything that assumed the old ratios by @pawn002 in #7

Full Changelog: v1.0.1...v2.0.0

v1.0.1

Choose a tag to compare

@github-actions github-actions released this 11 Jun 00:22

What's Changed

  • Harden plugin discovery against supply-chain exposure by @pawn002 in #2
  • ci: add CI + OIDC trusted-publishing workflows by @pawn002 in #3

New Contributors

Full Changelog: https://github.com/pawn002/klar/commits/v1.0.1