Releases: chipi/chemigram
Release list
v1.11.0 â Camera-portable vocabulary + visual-proof trust closure
[1.11.0] — 2026-05-23
Vocabulary maturation, honest visual proofs, camera-aware apply.
v1.11.0's headline is RFC-039: raw-derived parameters in L2
composition. The vocabulary now ports across photographers AND across
camera bodies — an L2 look that says "warm by +1500K" produces three
different op_params on Sony, Canon, and Nikon raws, because the
substitution reads each body's EXIF WB at apply time. Same pattern
extends to denoise (per-ISO threshold scaling), filmic (per-histogram
tone points), and lens correction (per-body lensfun identifier).
Vocabulary entries are reauthored to compose parametric primitives
instead of inlining camera-specific bytes.
The visual-proofs gallery's trust gap (rendered the wrong fixture for
21 entries pre-v1.11.0) was closed in three steps: a chart-verifiable
discriminator (#129), real-raw landscape + portrait fixtures via Git
LFS (#130), and content-aware fixture routing for sky / skin / food /
foliage / landscape / water entries (#138). A formal audit script
(#141) wired to CI now catches direction-of-effect regressions in the
gallery before they reach a release.
Stats
- 114 vocabulary entries (3 starter + 111 expressive-baseline)
- 2200+ tests; lab-grade direction-of-change for 88 entries
- Cross-camera fixtures: Sony DSC-RX10M4 + Sony ZV-E10 + Canon EOS R6
- Nikon D70 (4 manufacturers, 3 raw formats: ARW / CR3 / NEF)
- Visual-proof audit: 114 ok / 0 warn / 0 fail
RFC-039 / ADR-091/092/093 — Raw-derived parameters in L2 composition
The architectural shift: a vocabulary entry's claim is portability
across photographers AND across raws. Before v1.11.0, WB-touching
entries carried hardcoded raw-domain coefficients from their authoring
camera — they LOOKED portable but produced foreign-WB casts on any
other body. RFC-039 makes the vocabulary architecturally honest by
computing the WB coefficients per-raw at apply time.
- ADR-091 — Engine raw-metadata-aware apply API.
apply_entry()
gains araw_pathparameter; parametric decoders that need raw
metadata declare it; a lazy rawpy reader caches the EXIF WB +
daylight matrix per workspace. Fallback semantics for non-raw
fixtures: identity-skip (the temperature op drops from the
rendered output entirely, so darktable applies its own camera
default). - ADR-092 — Manifest
composesfield for L2 composition by
reference. L2 looks gain an optionalcomposes: [{primitive, params}]
field; the synthesizer resolves references at apply time, splicing
the parametric primitive's output into the L2 dtstyle's history at
the correct iop_order. Depth-1 only (L2 composes L3); cross-pack
resolution works. - ADR-093 — Camera-aware parametric
temperaturesemantics.
Identity preserves the camera's EXIF WB (not 1.0/1.0/1.0); deltas
apply in coefficient space (red ∝ 1+0.0001·kelvin_delta, blue
inverse). Empirically verified against three manufacturers
(#142).
Same RFC-039 engine machinery extends to:
- denoise (#134) — log2 ISO scaling above reference (ISO 200 →
1×, ISO 400 → 2×, ISO 800 → 4×). New ADR-094. - filmic (#135) — black/white points auto-tuned per raw
histogram percentiles. New ADR-095. - lens correction (#136) — EXIF camera + lens identifiers
populate the lensfun fields. New ADR-096.
Visual-proof gallery — three trust-gap closures
- #129 — Chart-verifiable discriminator. Every entry routes to
one of four modes (chart/real_raw/not_yet_portable/
manual) by the modules it touches. 38 entries route to real-raw
fixtures; the rest stay on the synthetic colorchecker / grayscale
ramp where those are honest fixtures. - #130 — Real-raw fixtures via Git LFS. Sony DSC-RX10M4
landscape ARW + Sony ZV-E10 portrait ARW, both CC BY-SA 4.0 from
discuss.pixls.us. Per-entry routing landscape / portrait based on
touches + content. - #138 — Content-aware fixture routing.
requires_content
manifest tag (sky / water / foliage / landscape / food / skin)
picks the matching fixture for entries whose effect needs scene
content the chart doesn't have. - #139 — Clipped-gradient routing for
blacks_lifted/
blacks_crushed. The sigmoidtarget_blackeffect is below noise
on the colorchecker but visible on the synthetic clipped fixture.
#141 — Formalized visual-proof audit
scripts/audit_visual_proofs.py (wired to CI on every push) compares
each rendered after-image to its baseline via pixel-stat signatures
(mean R/G/B, luma, std-luma, mean chroma, clip counts) and emits a
per-entry verdict by subtype-specific direction-of-effect heuristics.
Current state: 114 ok / 0 warn / 0 fail across the 114-entry
vocabulary. 17 smoke tests verify the heuristics themselves can't
silently break. The audit knows about parametric identity-default
entries, requires_content-tagged content-aware masks, and chroma-
shift entries (different metric from R/B mean).
#142 — Cross-camera RFC-039 verification
Two new CC0 fixtures via Git LFS (Canon EOS R6 CR3, Nikon D70 NEF;
~10 MB total, from raw.pixls.us). 7-test integration suite at
tests/integration/core/test_cross_camera_wb.py asserts:
- Per-body daylight WB probe returns plausible values (Sony 2.39/1.0/
1.71, Canon 1.81/1.0/1.63, Nikon 2.17/1.0/1.52) - +1500K shift anchors on each camera's WB (R ≈ camera_R × 1.15;
B ≈ camera_B × 0.85) - Same logical operation produces 3 distinct op_params across 3
bodies — the load-bearing portability assertion
#143 — Strengthened parametric assertions
Three _check_render_completes entries upgraded to direction-of-
change:
grain_strength— new_check_pixel_std_increaseLabCheck;
PatchSample.std_luma_8bitfield reads pixel-level luma std.
grain_strength=50 lifts std by +1.57; grain_strength=8 by +0.12.highlights_clip_threshold— parametrized
test_clipped_fixture_validation.pyto cover clip_0.85 (≥5%
clipped reduction) AND clip_0.95 (≥1%).toneequalizer— stays at render-completes after empirical
finding that band centers don't align with linear grayscale ramp
indices; direction-of-bytes is exhaustively covered by
test_toneequal.py. Documented in the entry's inline comment.
Side-cleanup: wb_warm_subtle moved to SKIP_REASONS — the
RFC-039 composed entry can't substitute on a non-raw chart;
direction asserted by #142's cross-body suite and the byte-level
kelvin-delta test.
#144 — Inline CI-assertion annotations in the gallery
Every gallery entry now surfaces what CI mechanically verifies for
it alongside the photographic-intent description. Four cases:
EXPECTED_EFFECTSdirect coverage →check_name(threshold)+
fixture namePARAMETERIZED_EFFECTS→ one bullet per (label, params)SKIP_REASONS→ the documented skip reason inline- L2 composite (auto-skipped by structural rule) → "verified by
visual review" inline
53 entries get direct assertions; 14 get skip reasons; 47 get the
L2-composite line. Reader never wonders.
#145 — Cross-pack composition stress test
Three synthetic test packs at tests/fixtures/vocabulary/ exercise
N=3-pack scenarios that the v1.10.0 [starter, expressive-baseline]
default didn't cover:
- Cross-pack
composesreference (a 3rd pack composes starter's
primitive) - Cross-pack name collision (ManifestError naming both source
packs) - Modversion drift warning fires per
emit_drift_signals - 3-pack-together load (starter + compose + drift) in one
VocabularyIndex construction
RFC-028 (deferred to v1.12.0) will be the architectural answer to
N-pack semantics; #145 is the engineering safety net under the
v1.11.0 loader.
Other engineering changes
- #100 — Darkroom-session umbrella opened with structured
checklist (Sub-tasks A-D: tone-curve baselines #94, EXIF probes,
curves #96, colorzones #98). Estimated ~3-4 hours. - #119 — Hypothesis property-based fuzz tests for 18
parameterize decoders (round-trip, idempotence, size-invariance). - #129–#140 — Visual-proofs trust-gap closures, content-aware
routing, AGENTS.md inversion (now canonical; CLAUDE.md +
.cursorrules are thin pointers). - Post-push CI verification — codified in AGENTS.md as a
workflow rule (never walk away from a red CI).
Carryover from v1.10.0 release
The ADR-088/089/090 trio shipped Draft pending darkroom validation
in v1.10.0; they remain Draft. The new ADR-091/092/093 (RFC-039)
and ADR-094/095/096 (RFC-039 follow-ups) ship the same way.
What's next (v1.12.0)
- RFC-027 — Multi-photographer review phase plan
- RFC-028 — Pack management / multi-pack composition / vendor packs
- Possibly: the deferred darkroom-session work (tonecurve mv5
decoder #94, colorzones spline fallback #98, EXIF baselines)
promoted from #100 — depending on whether you sit down with the
GUI.
v1.10.0 — Photographer-survey vocabulary + workflow primitives
Photographer-survey vocabulary + workflow primitives + 4 stabilization rounds + new docs.
A 6-genre photographer-workflow survey (36 photographers) drove a vocabulary expansion across portrait / landscape / wedding / B&W / nature-wildlife / food-product. The vocabulary delta + three workflow primitives implement the gaps that survey identified. Post-tag, 4 rounds of audit work stabilized CLI/MCP/docs alignment, and a docs prominence pass added a cookbook (~60 recipes), an onboarding guide, and four Mermaid architecture diagrams.
Three doors in
| If you want to... | Start here | Time |
|---|---|---|
| Run chemigram on a real photo | docs/getting-started.md |
30–60 min |
| Know "I want X look — how?" | docs/guides/cookbook.md |
as needed |
| Understand chemigram deeply | docs/onboarding.md |
2.5–3 hours |
Visual companion: docs/diagrams/ — four Mermaid one-pagers (stack / mask trilogy / vocabulary layers / phase timeline).
Vocabulary
- 29 new L2 looks across 6 genres + bw_convert v2 (colorequal-based 8-axis Adams-school B&W primitive).
- Parametric L2 strength (RFC-035 / ADR-088 Draft) —
--strength 0.6interpolates each parameterized field from identity to authored. - Mixed-op
apply_per_region(RFC-036 / ADR-089 Draft) — composite moves like eye-detail land as one snapshot. propagate_stateMCP+CLI (RFC-037 / ADR-090 Draft) — Lightroom-Sync analog with framing-bound auto-exclusion.apply-spotCLI verb — closes the ADR-069 verb-for-verb gap from v1.9.0.
Stats
- Tests: 1868 unit+integration + ~190 e2e (2058 total)
- ADRs: 90 (ADR-088/089/090 all Draft pending darkroom validation)
- RFCs Decided: 30 + RFC-038 Mode B drafted (v1.11+ pick)
- Vocabulary: 114 entries (2 starter + 112 expressive-baseline) + 9 named maskdefs
- 40 parameterized entries across 18 modules; 19 total modules touched
- MCP: 27 tools / CLI: 28 verbs / 23 mirrored / 0 unmirrored gaps
Stabilization (4 audit rounds)
Round 1 — holistic drift audit. 6 drifts closed including the critical pyproject.toml claiming 1.9.0 after v1.10.0 was tagged, generator allowlist hard-coded at v1.3.0 (silently lost 18 verbs across two releases).
Round 2 — CLI ↔ MCP ↔ docs alignment. 3 new audit tests + 7 TA.md MCP signature fixes + the canonical CLI ↔ MCP name mapping locked.
Round 3 — coverage audit. 4 coverage gaps closed: visual-proof generator silently dropping 10 named-mask entries (rescued); vocab show-mask / list-masks CLI tests added; apply_spot MCP wrapper unit tests added.
Round 4 — visual-proof coverage lock. New test prevents the silent-drop bug class from recurring.
Docs prominence
guides/cookbook.md— ~60 intent-driven recipes by genreonboarding.md— 2.5–3h reading order for new contributorsdiagrams/— 4 Mermaid one-pagers- "Three doors in" table on README + docs/index landing pages
doc-tree.mdadds guides/ + diagrams/ tiers; mkdocs nav surfaces onboarding + diagrams
What's next
ADR-088/089/090 ship as Draft pending darkroom-session validation against real raws (docs/guides/darkroom-session-debt.md items 7-11). Phase 2 vocabulary maturation continues; open issues live on v1.11.0 milestone. RFC-038 Mode B is the v1.11+ pick.
See CHANGELOG.md. Built on the shoulders of darktable.
v1.9.0 — Mask + retouch architecture trilogy
Headline
The mask + retouch architecture is structurally complete. Photographers can now compose spatial masks (drawn shapes), parametric range filters (luminance + HSL color), LLM-vision-derived content masks (chat-client native), and spot heal/clone retouch — all through one unified mask_spec wire and one new MCP tool. Lightroom's range-mask + retouch + AI-subject workflows all map cleanly into the chemigram surface.
Stats
- Tests: 1451 → 1811 passing (+360)
- ADRs: 83 → 87 (ADR-084..087)
- RFCs Decided: 23 → 27 (RFC-024 / RFC-025 / RFC-026 / RFC-029)
- RFCs Drafted: +1 (RFC-030, deferred — deployed sibling-provider scaffolding)
- Vocabulary entries: 78 → 83 (+5 compositional-mask L2 looks)
- New MCP tool:
apply_spot(sister toapply_primitive) - New CLI verbs:
vocab validate,cache {list, size, clear}
The mask trilogy
| Layer | RFC / ADR | What it does |
|---|---|---|
| Spatial — drawn masks (build-by-words) | RFC-029 / ADR-084 | "Bottom third", "circle on subject" → dt_form + dt_params. Path geometry added (N-vertex polygons). |
| Pixel-level refinement | RFC-024 / ADR-085 | "Only the dark pixels", "only blue hues" → range_filter field. Composes with drawn via AND-intersection. |
| Content-derived (coarse) | RFC-026 / ADR-086 | The chat-client's vision-capable LLM looks at render_preview and constructs mask_spec. Zero deployment cost. |
| Spot removal / heal / clone | RFC-025 / ADR-087 | New MCP tool apply_spot(image_id, kind, x, y, radius, source_x?, source_y?). HEAL + CLONE on circle geometry. |
| Precision-tier (deferred) | RFC-030 (Draft) | Deployed sibling-provider scaffolding for SAM-class subjects, depth, AI auto-spot detection. |
Highlights
RFC-029 / ADR-084 — Compositional masks at apply time (#113).
The wire was already there (apply_primitive accepts inline mask_spec); v1.9.0 formalizes it as the canonical build-by-words workflow. New docs guide mask-shapes-from-words.md maps spatial English to spec dicts: "bottom third" → gradient with anchor_y=0.67, rotation=180, compression=0.5. 30+ phrase→spec examples and 12 visual proofs against the synthetic grayscale ramp. The deterministic-hash mask_id provides reuse semantics for free.
RFC-024 / ADR-085 — Parametric range-filter masks.
mask_spec gains optional range_filter field: {kind: "luminance"|"color_h"|"color_s"|"color_l", min, max, feather, invert}. Encoded directly into darktable's blendif byte fields. Three valid combinations: drawn only, parametric only, drawn + parametric (intersection — the canonical "dark pixels in this region" workflow). 13 unit tests + 3 e2e tests verifying the wire end-to-end.
RFC-026 / ADR-086 — LLM-vision-as-provider for AI masks.
Reframed from RFC-026 v0.1's deployed-provider-only architecture: the chat-client (Claude.ai / ChatGPT / Claude Code) already has vision in the conversation. When the agent calls render_preview, the LLM sees the JPEG and constructs mask_spec from spatial reasoning. Zero deployment cost; covers ~70% of content-derived masking workflows. New docs guide llm-vision-for-masks.md with 6 workflow patterns (subject region, polygon trace, sky/foreground split, color-range estimation, subject-vs-background routing, iterative refinement). The deployed-provider precision tier moved to RFC-030 (Draft, deferred).
RFC-025 / ADR-087 — Retouch byte encoding + apply_spot MCP tool (#108).
New MCP tool sister to apply_primitive for the structurally different primitive class of pixel replacement. Byte encoders verified against darktable 5.4.1 src/iop/retouch.c: 13260-byte op_params (300-form fixed array of 44 bytes each + 60-byte global tail), 16-byte circle mask form, 8-byte clone mask_src. v1.9.0 scope: HEAL + CLONE on CIRCLE geometry, single form per call. 12 unit tests + 4 e2e tests.
5 compositional-mask L2 looks demonstrating the trilogy:
look_subject_lift_dark_only— drawn ellipse + luminance shadowslook_sky_blue_deepen— gradient (top half) + color_h cyan-bluelook_horizon_warm_glow— horizontal gradient + warm color_hlook_subject_brighten_highlights— drawn ellipse + luminance highlightslook_dark_pixels_global_lift— pure parametric range_filter
CLI ergonomics polish.
chemigram vocab validate <name>— runs 6 consistency checks per entry.chemigram cache {list, size, clear}— preview cache management.
Drift cleanup. Comprehensive doc-alignment pass: CLAUDE.md PNG-mask references retired (ADR-021 historical), RFC-024 cross-references updated to RFC-026 / RFC-030 split, capability-survey § 10 + § 13 reflect closures, apply_with_drawn_mask renamed to apply_with_mask (backcompat alias preserved), new docs guides for spatial English and LLM-vision workflows.
Lightroom parity
51/52 daily-use controls (98%) — unchanged from v1.8.0. The mask trilogy adds workflow surfaces orthogonal to the panel-mapping count. Only #94 manual tone curve remains, blocked on the darktable-session empirical-baseline work under #100.
Backward compatibility
apply_with_drawn_maskretained as a module-level alias forapply_with_mask; existing callers continue to work.- All v1.8.0 mask-bound vocabulary entries continue to function; the
mask_specschema is purely additive (addedrange_filter,dt_form: "path"). - CLI verbs and MCP tools are additive only.
Deferred to v1.10.0+
- RFC-027 (multi-photographer review phase plan) — not yet drafted.
- RFC-028 (pack management / vendor packs) — not yet drafted.
- RFC-030 (deployed sibling-provider scaffolding) — Draft v0.1.
- Recipe book, onboarding guide, architecture diagrams, fuzz tests for parameterize decoders.
#94manual tone curve,#95lens EXIF auto-binding,#96denoise wavelet baseline,#98colorzones HSL precision — all under the#100darktable-session umbrella.
🤖 Generated with Claude Code
v1.8.0 — Lightroom daily-use parity (51/52, 98%)
Headline
Lightroom daily-use parity at 98% across 6 panels (Light / Color / Color Mixer / Color Grading / Effects / Transform). Only manual tone curve (#94) remains, blocked on darktable-session empirical-baseline work tracked under #100.
Stats
- Tests: 1127 → 1451 passing (+324)
- Path C decoders: 11 → 18
- Parameterized vocabulary entries: 18 → 38
- ADRs: 82 → 83 (ADR-083 closes RFC-023)
- Visual-proofs: 553 fresh chart renders + documented placeholders for HSL
Lightroom panels covered
| Panel | Coverage |
|---|---|
| Light | 6/7 (only #94 tone curve deferred) |
| Color | 4/4 (incl. #102 WB Kelvin UX wrapper) |
| Color Mixer | 24/24 (HSL via colorequal — RFC-023 / ADR-083) |
| Color Grading | 7/7 (per-zone H/S/L + blending + balance) |
| Effects | 5/5 (incl. #92 Texture, #90 A.2 Dehaze) |
| Transform | 5/5 NEW (#101 ashift) |
Highlights
RFC-023 / ADR-083 — HSL Color Mixer parity (#93).
Backed HSL with the modern colorequal mv4 (128-byte flat-scalar struct) instead of the older colorzones spline-curve module. 3 multi-axis vocabulary entries — hsl_hue, hsl_saturation, hsl_luminance — with 8 per-color axes each (24 axes total). First formal Tier 3 → Tier 2 promotion under ADR-081.
Bucket A.5 — Color Grading panel completion (#91).
9 new colorbalancergb axes: per-zone hue × 3, per-zone saturation × 3, blending × 2 (shadows_weight, highlights_weight), balance × 1 (white_fulcrum).
Tier 3 promotions to Tier 2.
- Lens correction (
lensmv10, #95) — 10 magnitude axes for manual TCA / vignette / per-correction-type strengths. EXIF auto-binding for camera/lens identifiers tracked under #100. - Denoise (
denoiseprofilemv12, #96) — 4 magnitude axes parameterized; pinned at NLMEANS mode for chart-pipeline compatibility (wavelet-mode baseline capture under #100 task C). - Filmic v6 (
filmicrgbmv6, #97) — 8 magnitude axes parallel to sigmoid for users wanting explicit log-encoding control.
Bucket A.7 — Transform panel (#101).
darktable's ashift module mv5 (892-byte struct). 5 magnitude axes covering Lightroom's Transform panel sliders.
WB Kelvin UX wrapper (#102).
Photographic-units axes (kelvin_delta, tint_delta) layered on the existing temperature decoder.
Visual proofs verified
The visual-proof sweep ran post-tag against all 38 parameterized + 24 discrete entries. All v1.8.0 entries verified visually correct, with one documented exception: HSL via colorequal needs real-raw input (the synthetic chart pipeline doesn't provide the working-profile chain colorequal expects). The 3 HSL entries get documented placeholder rows in the gallery; their byte-level apply path is verified by the 5-layer test coverage (1451 tests pass).
Deferred
Tracked under #100 darktable-session empirical verification umbrella:
- #94 manual tone curve — 520-byte spline-curve struct needs darktable-GUI baseline capture
- #95 lens EXIF auto-binding extension — populate lensfun camera/lens strings from raw EXIF at apply time
- #96 denoise wavelet-curve baseline verification — confirm or fix the constructed x[6][7]/y[6][7] baseline (NLMEANS mode shipped as fallback)
- HSL real-raw visual proofs — capture per-zone HSL effect renders against a real raw fixture (v1.9.0 work)
- #98 colorzones spline-curve HSL precision fallback — discrete-only presets for the 5% HSL workflow needing Lightroom Range slider precision
Full notes
See CHANGELOG.md and docs/capability-survey.md § 10 + § 13 for the complete picture.
v1.7.0 — Phase 4 close + RFC-022 Tier 2 + brilliance + B&W + L2 looks
Architecture
- RFC-022 closed → ADR-081 (parameterization tiering policy; explicitly amends ADR-008)
- RFC-007 closed → ADR-082 (modversion drift handling: warn-loud at load, hard-fail at apply)
- IMPLEMENTATION.md Phase 5 (conditional Path C extension) retired — the question it asked is answered by ADR-081's tiered policy
Vocabulary (expressive-baseline pack: 28 → 39 entries)
Phase 4 closure — 6 magnitude-ladder modules collapsed (closes the v1.6.0 plan):
saturation_global,sigmoid_contrast,bilat_clarity_strength,grain_strength,highlights_clip_threshold,temperature(multi-axis — first 2-parameter ship)
RFC-022 Tier 2 expansion — 4 brand-new modules / 6 entries:
crop(4-axis workflow primitive),sharpen,vibrance/chroma_global/hue_angle(colorbalancergb extras),toneequalizer(9-axis)
#86 Brilliance axes — 4 colorbalancergb per-zone luminance entries
#63 B&W trio — bw_convert (Rec. 709), bw_sky_drama (red-emphasis), bw_foliage (green-emphasis); channelmixerrgb mv3 reverse-engineered programmatically
#87 L2 looks — look_portrait, look_landscape, look_vintage_film — multi-plugin composites
Final pack: 18 parameterized + 4 mask-bound + 3 L2 composites + 14 plain discrete = 39 entries.
Test infrastructure
- 5-layer parameterized coverage CI gate (ADR-080) — every parameterized entry must have unit + integration + lab-grade global + lab-grade masked + visual-proof sweep coverage
- Manifest-modversion consistency CI gate (#85) — manifest's declared modversions checked against decoder's
_STRUCT_SIZEat unit-test time - Runtime modversion drift detection (ADR-082) — warns / raises at vocab load when manifest disagrees with decoder. Caught 6 real pre-existing fixture bugs as a side-effect.
CLI / MCP discoverability (#89)
chemigram vocab show/vocab listsurfaceparameterizedflag +parametersblock (name, type, range, default, module, modversion, offset) per axis- MCP
list_vocabularytool serializes parameters identically — agents can introspect without runtime trial-and-error
Visual-proofs gallery (#84)
- Multi-axis sweep grid: every multi-parameter entry now shows one sweep row per axis (toneequalizer 9 / crop 4 / temperature 2). Total sweep rows: 12 → 26.
Test counts
- 1127 unit + integration tests passing (was ~767 at v1.6.0)
- 13/13 lab-grade masked tests pass against real darktable 5.4.1
- ruff / mypy / mkdocs --strict all clean
ADRs added in this milestone
ADR-077, ADR-078, ADR-079, ADR-080 (RFC-021 closure), ADR-081 (RFC-022 closure), ADR-082 (RFC-007 closure).
Issues closed
#63, #80, #81, #82, #83, #84, #85, #86, #87, #88, #89 (11 issues).
v1.6.0 — parameterized vocabulary magnitudes
The discrete-magnitude-ladder vocabulary trap is escaped. Parameterized vocabulary entries (RFC-021 / ADR-077..080) replace the per-strength enumeration with continuous values supplied at apply time.
Highlights
exposure --value V— continuous EV in[-3.0, +3.0]. Replaces the v1.5.x discreteexpo_+0.5 / expo_-0.5 / expo_+0.3 / expo_-0.3 / shadows_global_+/-ladder. Any value the photographer wants.vignette --value V— continuous brightness in[-1.0, +1.0]. Replacesvignette_subtle / vignette_medium / vignette_heavy.- CLI:
chemigram apply-primitive --entry exposure --value 0.7(single-param shorthand) or--param NAME=V(repeatable, for multi-parameter modules). - MCP:
apply_primitivetool gains optionalvaluearg (number for single-param entries, dict for multi-parameter). - Composition:
--valueand--mask-specare orthogonal axes —apply-primitive --entry exposure --value 0.5 --mask-spec '<json>'brightens the masked region by exactly 0.5 EV.
Architecture
- Path C as default for parameterized modules (ADR-077; partially supersedes ADR-008 for explicitly-declared parameterizable modules).
- Manifest schema gains
parametersarray (ADR-078; multi-parameter from day one). Each parameter declaresname / type / range / defaultplus byte-levelfieldlocation in the module'sop_paramsblob. apply_primitivevalueargument (ADR-079; hard-reject range validation).- 5-layer test-coverage CI gate (ADR-080): every parameterized entry must have unit + integration + lab-grade global + lab-grade masked + visual-proof-sweep coverage. Manifest-reading linter enforces it.
What's in scope for v1.6.0
exposure(mv7) andvignette(mv4) — two single-axis modules to validate the architecture isn't tied to one module's quirks.- 9 magnitude-ladder entries removed:
expo_+0.5/-0.5/+0.3/-0.3,shadows_global_+/-,vignette_subtle/medium/heavy. No deprecation aliases per ADR-078 (RFC-021 §Q4 — no users yet, no migration burden). - Vocabulary count: 39 → 31 (-9 ladder + 2 parameterized).
Phase 4 (deferred)
Parameterizing additional modules — temperature (multi-axis), colorbalancergb saturation, sigmoid contrast, bilat clarity, grain, highlights. Each ~half a day per ADR-077 now that the architecture is in place.
Other v1.6.0 work
- #77 — Lab-grade primitive isolation tests (chart-based per-patch assertion)
- #78 —
--mask-specflag for ad-hoc masked primitive application - #79 — Synthetic clipped-gradient fixture for
highlights_recoveryandgrainvalidation - Visual-proofs gallery extended with parameter-sweep rows for parameterized entries
- Mask-applicable controls compatibility matrix doc (
docs/guides/mask-applicable-controls.md) - Capability survey doc (
docs/capability-survey.md) — the honest catalog of what chemigram supports today vs what darktable offers
Tests
734 unit + integration tests passing. Lab-grade e2e suite runs against real darktable. Lint, mypy, mkdocs strict all clean.
Closed
v1.5.0 — drawn-mask only
Mask architecture cleanup — drawn-mask only
While shipping path 4a (drawn-mask XMP serialization) in v1.4.0 we discovered the previous PNG-mask path was a silent no-op: darktable never reads external PNG files for raster masks. Verified against darktable 5.4.1 source — src/develop/blend.c resolves raster masks from in-pipeline pointers (self->raster_mask.sink.source), not the filesystem. The bundled MaskingProvider Protocol, CoarseAgentProvider, geometric providers, mask registry, materialize_mask_for_dt helper, and the generate_mask/regenerate_mask/list_masks/tag_mask/invalidate_mask MCP tools were all infrastructure for a path that never connected to pixels.
This release rips that dead infrastructure entirely. Breaking changes.
ADR-076 (supersedes ADR-021/022/055/057/058/074): drawn-mask only. Mask-bound vocabulary entries declare mask_spec and route through apply_with_drawn_mask automatically — no providers, no registry, no PNG.
Removed
- Production code:
chemigram.core.masking.MaskingProviderProtocol + result/error types,CoarseAgentProvider,Gradient/Radial/RectangleMaskProvider,chemigram.core.versioning.masks(entire mask registry),chemigram.core.helpers.materialize_mask_for_dt/serialize_mask_entry/ensure_preview_render. - MCP tools:
generate_mask,regenerate_mask,list_masks,tag_mask,invalidate_mask. - CLI sub-app:
chemigram masks ...(list / generate / regenerate / tag / invalidate). - Vocabulary schema:
mask_kind,mask_reffields.mask_specis now the only mask declaration. - Tool surface:
apply_primitive'smask_overrideargument (CLI flag and MCP arg). - Server wiring:
build_server(masker=...),ToolContext.masker. - Workspace layout:
Workspace.masks_dirproperty andmasks/subdir. - Runtime dep:
numpy>=1.26(was for the now-removed geometric providers).
Vocabulary changes
tone_lifted_shadows_subject(#62) retired from the starter pack — was doubly broken (wrong dtstyle content awaiting a real darktable session AND the raster path it depended on never worked). Starter drops 5 → 4 entries.- The four expressive-baseline mask-bound entries —
gradient_top_dampen_highlights,gradient_bottom_lift_shadows,radial_subject_lift,rectangle_subject_band_dim— survive unchanged. Theirmask_specwas already the load-bearing field; the redundantmask_kind: "drawn"marker was stripped.
Prompt template
mode_a/system_v4.j2 is the new active version per ADR-043 (append-only). Drops references to the removed tools and the mask_kind: "raster" schema; rewrites the "Local adjustments" section around the built-in geometric mask types.
Architecture seam
The Phase 4 sibling project (working name chemigram-masker-sam) for content-aware masking will produce darktable drawn-form geometry — not PNG bytes. The wire format is the same one v1.5.0 uses today; the apply path stays unchanged when it lands. ADR-076 documents the rationale.
Audit follow-up (commit 5937d7a)
After the initial v1.5.0 ship, a four-angle deep audit (code, tests, docs, architecture-seam) surfaced a handful of stale references and one coverage gap. All closed in commit 5937d7a and included in this tag:
- Live docs corrected:
CONTRIBUTING.md(vocabulary schema),vocabulary/starter/README.md(entry count + dropped row),docs/adr/TA.md(MCP component description: 27 → 22 tools with category breakdown),docs/testing.md(tool count + example primitive + dead-category drop),docs/concept/02-project-concept.md(masking framing rewritten),docs/IMPLEMENTATION.md(Slice 4 supersession status note). - Append-only ADRs marked partial-superseded: ADR-056 (5 mask tools + mask_override gone) and ADR-070 (masks sub-app gone), mirroring what we already did for ADR-021/022/055/057/058/074.
- Coverage gap closed: added
test_apply_primitive_with_mask_spec_routes_through_drawn_maskandtest_apply_primitive_with_invalid_mask_spec_returns_masking_error(MCP unit) plus the matching CLI integration test. Without these, only the e2e suite (real darktable) exercised the drawn-mask dispatch branch.
Stats
- 66 production / test files in commit
155784b; 11 doc files in commit76b0cb7; 10 audit-fix files in commit5937d7a. - Net –4135 lines code/tests (cleanup) + ~50 net lines docs (compression) + 122 lines audit fixes.
- CI: 440 unit + 206 integration + 46 e2e all pass against real darktable 5.4.1. 692 tests total.
- Pre-existing
tests/integration/cli/test_stdin_batch.pyimport bug fixed in passing.
Carry to v1.5.x
#63— threechannelmixerrgbB&W vocabulary entries still want a hand-authored darktable seed.
No PyPI (deferred per the v1.4.0 ship direction; will revisit after MVP review).
🤖 Generated with Claude Code
v1.4.0 — vocabulary + masks + CLI ergonomics + infra cleanup
v1.4.0
The "ambitious 1.4" milestone: closes the v1.4.0 extension plan with 2 new ADRs, 1 amended ADR, 3 RFC closures, and 11 GH issues. Total 23 new tests (700 → 723) across 14 commits in 7 batches.
New capabilities
- Built-in geometric mask providers (ADR-074).
GradientMaskProvider,RadialMaskProvider,RectangleMaskProviderinchemigram.core.masking.geometric— deterministic, parameter-driven, BYOA-compatible (no agent required). Addsnumpy>=1.26to runtime deps. - CLI mask integration (#73).
chemigram masks generate <id> --target <name> --provider gradient --config '{...}'works with all 3 built-in providers. Without--provider, the v1.3.0 MASKING_ERROR surface is preserved. - Vocabulary
mask_specfield. Optional declaration of which provider + config should generate a raster mask if it isn't yet registered. v1.4.0 stores; v1.5.x will auto-generate at apply time.
Vocabulary completion
- 31 expressive-baseline entries authored programmatically across 9 darktable iop modules (exposure, temperature, sigmoid, localcontrast, colorbalancergb, grain, vignette, highlights, channelmixerrgb deferred). 22 e2e direction-of-change tests passing against real darktable 5.4.1.
- ADR-073 formalizes Path C (programmatic vocabulary authoring via reverse-engineered iop struct layouts) as an accepted complement to hand-authoring. Closes RFC-012.
CLI ergonomics
- Shell completion (#67).
chemigram --install-completion/--show-completion(bash/zsh/fish/PowerShell). Closes RFC-020 §Q1. - Workspace auto-discovery (#69).
cd ~/Pictures/Chemigram/iguana && chemigram get-state .works without flags. Closes RFC-020 §Q3.
Infrastructure
- Linux CI matrix (#70).
ubuntu-latestjoinsmacos-latest. ADR-075 amends ADR-040. - Manifest↔dtstyle audit (#71). Catches the
tone_lifted_shadows_subject-style content drift at CI time. - Helpers refactor (#66). 6 MCP-private helpers lifted to
chemigram.core.helpers. Closes the ADR-071 follow-up.
RFC closures
- RFC-012 (programmatic vocabulary generation / Path C) → Decided via ADR-073.
- RFC-018 (vocabulary expansion for expressive taste) → Decided via ADR-063, ADR-064, ADR-073.
- RFC-019 (reference-image validation baseline) → Decided via ADR-066, ADR-067, ADR-068.
Known deferrals
- #62 (starter
tone_lifted_shadows_subjectcontent bug) and #63 (channelmixerrgb B&W ×3) carry to v1.4.x — both blocked on hand-authored darktable sessions. - #68 (CLI stdin support) deferred — needs verb-signature changes across 8+ files.
Distribution
No PyPI per project decision. Install via pip install git+https://github.com/chipi/chemigram@v1.4.0.
Full changelog
https://github.com/chipi/chemigram/blob/main/CHANGELOG.md#140--2026-05-02
v1.3.0 — chemigram CLI
chemigram CLI shipped. 22 verbs in chemigram.cli mirroring the MCP tool surface verb-for-verb, alongside the existing chemigram-mcp server. Same engine, same workspace state on disk; the choice between MCP and CLI is about workflow shape (conversational vs programmatic), not capability.
PRD-005 frames the user-value case; RFC-020 (now Decided) locks the design via four closing ADRs (069/070/071/072).
What ships
chemigrambinary on$PATHalongsidechemigram-mcpafterpip install- 22 verbs total:
- Diagnostic:
status - Lifecycle:
ingest - Edit/state:
apply-primitive,remove-module,reset,get-state - Versioning:
snapshot,branch,tag,checkout,log,diff - Layer binding:
bind-layers - Render/export:
render-preview,compare,export-final - Masks:
masks list / generate / regenerate / tag / invalidate - Context:
read-context,log-vocabulary-gap,apply-taste-update,apply-notes-update - Vocab:
vocab list / show
- Diagnostic:
- Output: human-readable text by default; NDJSON via
--json. Output schema versioned independently of package SemVer (locked at1.0; surfaced viachemigram status). - Stable exit codes: 11-value
ExitCodeIntEnum mapped fromchemigram.mcp.errors.ErrorCode. Mypy-exhaustive mapping; runtime audit test as a safety net. - Auto-generated CLI reference:
docs/guides/cli-reference.mdregenerated fromchemigram --helpviamake docs-cli; CI checks for drift.
Why two adapters
Both MCP and CLI are thin wrappers over chemigram.core (per ADR-071, lint-enforced). Same engine, two invocation surfaces:
- MCP (conversational): Long-lived session with an agent in an MCP-capable client (Claude Code, Cursor, Continue, Cline, Zed, Claude Desktop). Best for one-image-deep collaborative editing.
- CLI (programmatic): Subprocess calls from shell scripts, custom Python loops, batch jobs, watch-folder daemons, CI pipelines. No session lifecycle; each invocation is one operation.
See docs/index.md § Two planes of control and docs/getting-started.md § Driving Chemigram from a script or agent loop.
Notable design decisions
propose-taste-update/confirm-taste-updateare MCP-only by design. The conversational propose/confirm protocol requires per-process state across two tool calls. The CLI ships directapply-taste-update/apply-notes-updateverbs instead because parallel batch invocations would race on a shared proposal store.masks generate/regenerateexitMASKING_ERROR (7)because the subprocess CLI has no provider-injection path equivalent to MCP'sbuild_server(masker=...). List/tag/invalidate work fully without a provider. Future RFC may add config-driven provider selection.chemigram statusalways exits 0 (diagnostic; reports missing components in fields/warnings rather than failing). Scripts that need a hard check for darktable can branch onpayload["darktable_cli_path"] is Nonein the JSON output.
Architectural anchors
- ADR-069 — CLI alongside MCP, won't replace it
- ADR-070 — CLI framework: Typer
- ADR-071 — Thin-wrapper discipline (lint-enforced)
- ADR-072 — Output format (NDJSON) + exit codes
- RFC-020 — Decided, closes here
Verification
- 697 unit + integration tests pass; ruff + mypy + audit + cli-reference sync clean
- 4 e2e session tests against real darktable: ingest → apply → render → export → compare round-trip
- Verb-parity audit
_KNOWN_PENDING_VERBSallowlist down to 4 (the conversational propose/confirm tools, MCP-only by design)
Full changelog: CHANGELOG.md
MCP server unchanged. All existing MCP integrations (Claude Code, Cursor, Claude Desktop, etc.) continue to work without modification.
v1.2.0 — Engine unblock + reference-image validation
Engine unblock + reference-image validation infrastructure. Closes the synthesizer's Path B gap (RFC-018 v0.2) and ships a CI-safe colorimetric assertion library (RFC-019 v0.2) — the two pieces that gate the future 35-entry expressive-baseline vocabulary authoring sprint (now v1.4.0). The CLI work (RFC-020 / PRD-005) ships next as v1.3.0.
Engine — Path B unblocked
synthesize_xmpnow appends new-instance history entries for vocabulary primitives whose(operation, multi_priority)tuple isn't in the baseline (closes RFC-001's open Path B question; ADR-063). Empirical evidence intests/fixtures/preflight-evidence/showed darktable 5.4.1 resolves pipeline order fromiop_order_version+ the internaliop_listregardless of per-entryiop_order, so appended entries ship withiop_order=None— no probe script, no manifest schema fields. Supersedes ADR-051's "Path B deferred" stance.HistoryEntry.iop_ordertype widened fromint | Nonetofloat | Noneso rendered XMP sidecars (which can carry float values) round-trip cleanly.VocabularyIndexnow acceptsPath | list[Path]and shipsload_packs(["starter", "expressive-baseline"])for multi-pack loading. Cross-pack name collisions raise a clear error naming both pack roots.
Reference-image validation (RFC-019 v0.2)
chemigram.core.assertions— hand-rolled CIE DE2000, sRGB ↔ Lab D50 (Lindbloom Bradford adaptation), patch extraction, and high-level helpers (assert_color_accuracy,assert_tonal_response,assert_exposure_shift,assert_wb_shift). Validated against the Sharma/Wu/Dalal 2005 reference test pair (DE2000 = 2.0425).tests/fixtures/reference-targets/— synthetic CC24 + grayscale ramp PNGs paired with X-Rite published L*a*b* D50 ground truth. CI-safe (no darktable, no real RAW). Tier B (real-RAW reference shooting) is deferred per ADR-068.
Prompts
- Mode A
system_v3.j2is now active. Filter-first vocabulary navigation, multi-pack enumeration, explicitend_sessionorchestration. Addsvocabulary_packsto required context. v1 + v2 stay loadable for eval reproducibility (ADR-045).
Vocabulary scaffolding
vocabulary/packs/expressive-baseline/skeleton (empty manifest, README, L3 module subdirectories). Authoring of the 35 entries is hands-on darktable work in v1.4.0.
Closing ADRs
063, 064, 066, 067, 068. ADR-065 deliberately gapped — see docs/adr/index.md Conventions.
Tests
556 unit + integration green. New: 34 unit tests for the assertion library, 9 integration tests for the synthetic CC24 round-trip, 16 e2e scaffolds for expressive-baseline (auto-skip until entries are authored).
Phase plan reshaped (2026-05-02)
Phase 1.2 originally bundled engine + 35-entry authoring under v1.2.0. Authoring is hands-on darktable work; it was decoupled from the engine slice. Engine + assertion library ship now as v1.2.0; CLI ships as v1.3.0; the 35-entry authoring sprint ships as v1.4.0. Phase 2 begins post-v1.4.0.
Full changelog: CHANGELOG.md