Initial Commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# OpenSpec Change: enterprise-image-quantisation
|
||||
|
||||
- **Change ID**: enterprise-image-quantisation
|
||||
- **Package**: `@suaveplan/image-quantisation`
|
||||
- **Category**: media / image
|
||||
- **Target path**: `packages/media/image/image-quantisation`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-05-21
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/image-quantisation.md`
|
||||
- **Design**: ./design.md (not required for migration)
|
||||
- **Spec delta**: ./specs/image-quantisation/spec.md
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Migrate `@suaveplan/image-quantisation` from `../suaveplan/packages/universal/image-quantisation/` into genesis at enterprise quality. Preserve the full upstream public API while adding Zod validation, structured errors, OTel instrumentation, property-based tests, and 100% coverage.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package did not exist on genesis prior to this change. This is a fresh migration.
|
||||
|
||||
---
|
||||
|
||||
## Source mapping
|
||||
|
||||
| Requirement | Source lines | Notes |
|
||||
|---|---|---|
|
||||
| Zero workspace deps | N/A | upstream used `tslib` + `image-q` only |
|
||||
| OTel adapter | N/A | genesis-wide enhancement |
|
||||
| Zod schemas | N/A | genesis-wide enhancement |
|
||||
| `defineCodes` error registry | N/A | genesis-wide enhancement |
|
||||
| 100% coverage | N/A | genesis-wide policy |
|
||||
| Pool detection | upstream `src/analysis/pools.ts` | verbatim algorithm, restructured |
|
||||
| Edge analysis | upstream `src/analysis/edges.ts` | verbatim algorithm, added 1×1 test |
|
||||
| Complexity classification | upstream `src/analysis/classify.ts` | verbatim heuristics, clamped min colours |
|
||||
| Quantisation | upstream `src/quantise/quantise.ts` | preserved `image-q` wrapper, added Zod + OTel |
|
||||
| Adaptive pipeline | upstream `src/pipeline.ts` | preserved logic, added Zod + OTel |
|
||||
| Spatial cleanup | upstream `src/spatial/cleanup.ts` | preserved logic, removed unreachable guard |
|
||||
|
||||
---
|
||||
|
||||
## Parity Checklist
|
||||
|
||||
| Capability | Upstream | Migrated | Notes |
|
||||
|---|---|---|---|
|
||||
| `analyseComplexity` | ✅ | ✅ | |
|
||||
| `detectPools` | ✅ | ✅ | |
|
||||
| `poolDistribution` | ✅ | ✅ | |
|
||||
| `analyseEdges` | ✅ | ✅ | |
|
||||
| `colourKey` / `parseColourKey` | ✅ | ✅ | |
|
||||
| `rgbDistance` / `weightedDistance` | ✅ | ✅ | |
|
||||
| `quantise` | ✅ | ✅ | Added Zod validation + OTel |
|
||||
| `quantiseAdaptive` | ✅ | ✅ | |
|
||||
| `spatialCleanup` | ✅ | ✅ | |
|
||||
| `countUniqueColours` | ✅ | ✅ | |
|
||||
| `adaptivePipeline` | ✅ | ✅ | Added Zod validation + OTel |
|
||||
| Zod schemas | 🚫 | ✅ | Genesis enhancement |
|
||||
| OTel instrumentation | 🚫 | ✅ | Genesis enhancement |
|
||||
| Structured errors | 🚫 | ✅ | Genesis enhancement |
|
||||
| Property-based tests | 🚫 | ✅ | Genesis enhancement |
|
||||
| 100% coverage | 🚫 | ✅ | Genesis enhancement |
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
Run `bunx turbo ls --filter=...@suaveplan/image-quantisation` to enumerate.
|
||||
|
||||
- No dependents currently in genesis (verified by turbo ls).
|
||||
|
||||
---
|
||||
|
||||
## Deferred
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,228 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: image-quantisation
|
||||
|
||||
**Package**: `@suaveplan/image-quantisation`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Package provides image complexity analysis
|
||||
|
||||
The package SHALL analyse raw RGBA pixel buffers and classify image complexity into one of four tiers: pixel-art, vector-clean, vector-aa, or photographic.
|
||||
|
||||
#### Scenario: Solid-colour image classified as pixel-art
|
||||
|
||||
- **WHEN** a solid-colour 32×32 buffer is passed to `analyseComplexity`
|
||||
- **THEN** the returned `tier` is `"pixel-art"`, `uniqueColours` is 1, and `recommendedColours` ≤ 32
|
||||
|
||||
**Verified by:** `src/analysis/classify/classify.test.ts::"classifies solid colour as pixel-art"`
|
||||
|
||||
#### Scenario: Gradient classified as photographic
|
||||
|
||||
- **WHEN** a smooth gradient 64×64 buffer is passed to `analyseComplexity`
|
||||
- **THEN** the returned `tier` is `"photographic"`, `uniqueColours` > 100, and `recommendDithering` is `true`
|
||||
|
||||
**Verified by:** `src/analysis/classify/classify.test.ts::"classifies gradient as photographic"`
|
||||
|
||||
#### Scenario: Vector-clean tier detected
|
||||
|
||||
- **WHEN** a 32×32 image with 64 colours in 4×4 blocks is analysed
|
||||
- **THEN** the returned `tier` is `"vector-clean"`
|
||||
|
||||
**Verified by:** `src/analysis/classify/classify.test.ts::"classifies vector-clean for moderate colours with medium pools"`
|
||||
|
||||
#### Scenario: Vector-aa tier detected
|
||||
|
||||
- **WHEN** a 64×64 image with 256 colours in 4×4 blocks is analysed
|
||||
- **THEN** the returned `tier` is `"vector-aa"`
|
||||
|
||||
**Verified by:** `src/analysis/classify/classify.test.ts::"classifies vector-aa for high colour count with structure"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package provides pool detection and distribution
|
||||
|
||||
The package SHALL detect contiguous same-colour regions via 4-connected flood fill and compute pool size distribution statistics.
|
||||
|
||||
#### Scenario: Single pool for solid colour
|
||||
|
||||
- **WHEN** `detectPools` is called on an 8×8 solid red buffer
|
||||
- **THEN** it returns exactly one pool with `size` 64 and colour `{r:255,g:0,b:0,a:255}`
|
||||
|
||||
**Verified by:** `src/analysis/pools/pools.test.ts::"finds 1 pool for a solid colour"`
|
||||
|
||||
#### Scenario: Pool distribution computed correctly
|
||||
|
||||
- **WHEN** `poolDistribution` is called on pools from a checkerboard
|
||||
- **THEN** `singletonRatio` is 1 and `mean` is 1
|
||||
|
||||
**Verified by:** `src/analysis/pools/pools.test.ts::"detects high singleton ratio in checkerboard"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package provides edge analysis
|
||||
|
||||
The package SHALL analyse edges between adjacent pixels and return sharpness metrics.
|
||||
|
||||
#### Scenario: Solid image has zero hard edges
|
||||
|
||||
- **WHEN** `analyseEdges` is called on a solid-colour buffer
|
||||
- **THEN** `hardEdges` is 0 and `meanEdgeDistance` is 0
|
||||
|
||||
**Verified by:** `src/analysis/edges/edges.test.ts::"solid image has zero hard edges"`
|
||||
|
||||
#### Scenario: 1×1 image has zero edges
|
||||
|
||||
- **WHEN** `analyseEdges` is called on a 1×1 buffer
|
||||
- **THEN** `totalEdges` is 0, `sharpnessRatio` is 0, and `meanEdgeDistance` is 0
|
||||
|
||||
**Verified by:** `src/analysis/edges/edges.test.ts::"1x1 image has zero edges"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package provides colour quantisation
|
||||
|
||||
The package SHALL quantise RGBA pixel buffers to a target palette size using wrapped `image-q` algorithms.
|
||||
|
||||
#### Scenario: Gradient reduced to target colours
|
||||
|
||||
- **WHEN** `quantise` is called on a 32×32 gradient with `colours: 16`
|
||||
- **THEN** `paletteSize` ≤ 16
|
||||
|
||||
**Verified by:** `src/quantise/quantise.test.ts::"reduces a gradient to target colour count"`
|
||||
|
||||
#### Scenario: All three algorithms supported
|
||||
|
||||
- **WHEN** `quantise` is called with `algorithm: "wuquant"`, `"neuquant"`, and `"rgbquant"`
|
||||
- **THEN** each result has `paletteSize` ≤ 8
|
||||
|
||||
**Verified by:** `src/quantise/quantise.test.ts::"supports different algorithms"`
|
||||
|
||||
#### Scenario: Invalid dimensions throw structured error
|
||||
|
||||
- **WHEN** `quantise` is called with `width: 0`
|
||||
- **THEN** it throws `QuantisationError` with code `QUANTISATION.INVALID_DIMENSIONS`
|
||||
|
||||
**Verified by:** `src/quantise/quantise.test.ts::"throws on invalid dimensions"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package provides spatial AA cleanup
|
||||
|
||||
The package SHALL merge small pools into nearest spatial neighbours below a colour distance threshold.
|
||||
|
||||
#### Scenario: Near-colour AA pixels merged
|
||||
|
||||
- **WHEN** `spatialCleanup` is called on an AA edge buffer with default options
|
||||
- **THEN** the unique colour count drops from 3 to 2
|
||||
|
||||
**Verified by:** `src/spatial/cleanup/cleanup.test.ts::"merges near-colour AA pixels into spatial neighbours"`
|
||||
|
||||
#### Scenario: Pool size threshold respected
|
||||
|
||||
- **WHEN** `spatialCleanup` is called with `maxPoolSize: 0`
|
||||
- **THEN** no merge occurs and colour count stays 3
|
||||
|
||||
**Verified by:** `src/spatial/cleanup/cleanup.test.ts::"does not merge pools above maxPoolSize"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package provides adaptive pipeline
|
||||
|
||||
The package SHALL orchestrate analysis → spatial cleanup → quantisation in a single call, selecting strategy based on complexity tier.
|
||||
|
||||
#### Scenario: Pixel-art skips quantisation
|
||||
|
||||
- **WHEN** `adaptivePipeline` is called on a solid-colour buffer
|
||||
- **THEN** `quantisation` is `null` and `finalColours` is 1
|
||||
|
||||
**Verified by:** `src/pipeline/pipeline.test.ts::"skips quantisation for pixel-art"`
|
||||
|
||||
#### Scenario: Photographic images are quantised
|
||||
|
||||
- **WHEN** `adaptivePipeline` is called on a gradient
|
||||
- **THEN** `quantisation` is not `null` and `finalColours` ≤ 256
|
||||
|
||||
**Verified by:** `src/pipeline/pipeline.test.ts::"quantises photographic images"`
|
||||
|
||||
#### Scenario: Spatial cleanup applied for vector-aa
|
||||
|
||||
- **WHEN** `adaptivePipeline` is called on a vector-aa image
|
||||
- **THEN** `spatialCleanupApplied` is `true`
|
||||
|
||||
**Verified by:** `src/pipeline/pipeline.test.ts::"applies spatial cleanup for vector-aa images"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package validates options via Zod schemas
|
||||
|
||||
All public entry points that accept options SHALL validate them through exported Zod schemas before processing.
|
||||
|
||||
#### Scenario: Invalid options rejected by quantise
|
||||
|
||||
- **WHEN** `quantise` is called with `colours: 1`
|
||||
- **THEN** it throws `QuantisationError` with code `QUANTISATION.PROCESSING_FAILED`
|
||||
|
||||
**Verified by:** `src/quantise/quantise.test.ts::"throws on invalid options"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package emits OpenTelemetry instruments
|
||||
|
||||
The package SHALL emit a counter and histogram for `quantise` and `adaptivePipeline` invocations.
|
||||
|
||||
#### Scenario: Instruments exist and are lazily initialised
|
||||
|
||||
- **WHEN** `quantise` is called
|
||||
- **THEN** internal `getCounter()` and `getDuration()` functions return valid OTel instruments
|
||||
|
||||
**Verified by:** `src/quantise/quantise.test.ts::"reduces a gradient to target colour count"` (implicit via execution path)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package defines stable error codes
|
||||
|
||||
The package SHALL declare error codes via `defineCodes("QUANTISATION", [...])` and throw `QuantisationError` with those codes.
|
||||
|
||||
#### Scenario: Every code is reachable
|
||||
|
||||
- **WHEN** invalid inputs are provided to `quantise` and `adaptivePipeline`
|
||||
- **THEN** each of `INVALID_PIXEL_BUFFER`, `INVALID_DIMENSIONS`, and `PROCESSING_FAILED` is thrown at least once
|
||||
|
||||
**Verified by:** `src/quantise/quantise.test.ts::"throws on invalid dimensions"` AND `src/quantise/quantise.test.ts::"throws on mismatched buffer size"` AND `src/pipeline/pipeline.test.ts::"throws on invalid options"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package provides property-based tests
|
||||
|
||||
The package SHALL include fast-check property tests that verify invariants across random inputs.
|
||||
|
||||
#### Scenario: Palette size invariant
|
||||
|
||||
- **WHEN** random RGBA buffers and colour counts are quantised
|
||||
- **THEN** `result.paletteSize` ≤ requested colours
|
||||
|
||||
**Verified by:** `src/property.test.ts::"quantise palette size <= requested colours"`
|
||||
|
||||
#### Scenario: Output dimensions invariant
|
||||
|
||||
- **WHEN** random RGBA buffers are quantised
|
||||
- **THEN** output `width`, `height`, and `pixels.length` match input
|
||||
|
||||
**Verified by:** `src/property.test.ts::"quantise output has same dimensions as input"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package includes benchmarks
|
||||
|
||||
The package SHALL include mitata benchmarks for `quantise`, `analyseComplexity`, and `adaptivePipeline`.
|
||||
|
||||
#### Scenario: Benchmark file exists and compiles
|
||||
|
||||
- **WHEN** the benchmark file is inspected
|
||||
- **THEN** `src/__benchmarks__/quantisation.bench.ts` exists and imports the public API
|
||||
|
||||
**Verified by:** manual inspection of `src/__benchmarks__/quantisation.bench.ts`
|
||||
@@ -0,0 +1,86 @@
|
||||
# Tasks: enterprise-image-quantisation
|
||||
|
||||
**Change ID**: enterprise-image-quantisation
|
||||
**Package**: `@suaveplan/image-quantisation`
|
||||
**Target path**: `packages/media/image/image-quantisation`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] Create directory `packages/media/image/image-quantisation/` with `src/`, `src/__benchmarks__/`, and feature subdirectories.
|
||||
- [x] Write `package.json` at version `0.1.0` with `"type": "module"`, `"sideEffects": false`, full `exports` map.
|
||||
- [x] Write `tsconfig.lib.json` extending `tsconfig.base.json`.
|
||||
- [x] Write `vitest.config.ts` with istanbul thresholds at 100/100/100/100.
|
||||
- [x] Write `vite.config.ts` with multi-entry lib build.
|
||||
- [x] Add `image-q` to root `package.json` catalog.
|
||||
|
||||
## 2. Types + errors
|
||||
|
||||
- [x] Write `src/types/common.ts` — all domain types.
|
||||
- [x] Write `src/types/common.md`.
|
||||
- [x] Write `src/errors/image-quantisation-error.ts` — `QuantisationError`, `QUANTISATION_CODES`.
|
||||
- [x] Write `src/errors/image-quantisation-error.md` and `.test.ts`.
|
||||
|
||||
## 3. Schemas
|
||||
|
||||
- [x] Write `src/schemas/options.schema.ts` — Zod schemas for all public options.
|
||||
- [x] Write `src/schemas/options.schema.md`.
|
||||
|
||||
## 4. Analysis modules
|
||||
|
||||
- [x] Write `src/analysis/colour/colour.ts` + `.test.ts` + `.md`.
|
||||
- [x] Write `src/analysis/edges/edges.ts` + `.test.ts` + `.md`.
|
||||
- [x] Write `src/analysis/pools/pools.ts` + `.test.ts` + `.md`.
|
||||
- [x] Write `src/analysis/classify/classify.ts` + `.test.ts` + `.md`.
|
||||
|
||||
## 5. Quantisation
|
||||
|
||||
- [x] Write `src/quantise/quantise.ts` — wraps `image-q` with Zod + OTel.
|
||||
- [x] Write `src/quantise/quantise.test.ts` + `.md`.
|
||||
|
||||
## 6. Spatial cleanup
|
||||
|
||||
- [x] Write `src/spatial/cleanup/cleanup.ts` + `.test.ts` + `.md`.
|
||||
|
||||
## 7. Pipeline
|
||||
|
||||
- [x] Write `src/pipeline/pipeline.ts` — adaptive workflow with Zod + OTel.
|
||||
- [x] Write `src/pipeline/pipeline.test.ts` + `.md`.
|
||||
|
||||
## 8. Benchmarks
|
||||
|
||||
- [x] Write `src/__benchmarks__/quantisation.bench.ts`.
|
||||
|
||||
## 9. Property tests
|
||||
|
||||
- [x] Write `src/property.test.ts` with fast-check invariants.
|
||||
|
||||
## 10. Root barrel + README
|
||||
|
||||
- [x] Write `src/index.ts` with explicit named re-exports.
|
||||
- [x] Write `README.md`.
|
||||
|
||||
## 11. Gates
|
||||
|
||||
- [x] `bunx turbo lint --filter=@suaveplan/image-quantisation` — green.
|
||||
- [x] `bunx turbo typecheck --filter=@suaveplan/image-quantisation` — green.
|
||||
- [x] `bunx turbo test --filter=@suaveplan/image-quantisation` — green.
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/image-quantisation` — 100/100/100/100.
|
||||
- [x] `bunx turbo build --filter=@suaveplan/image-quantisation` — green.
|
||||
- [x] `bun run docs:check` — 100%.
|
||||
- [x] `bun scripts/openspec-conformance.ts packages/media/image/image-quantisation` — 0 errors.
|
||||
|
||||
## 12. Spec + archive
|
||||
|
||||
- [x] Write `openspec/changes/enterprise-image-quantisation/specs/image-quantisation/spec.md`.
|
||||
- [x] Update `openspec/changes/enterprise-image-quantisation/proposal.md` with parity checklist.
|
||||
- [x] Update `openspec/changes/enterprise-image-quantisation/tasks.md`.
|
||||
- [x] Fold spec delta into `openspec/specs/image-quantisation/spec.md`.
|
||||
- [x] Move `openspec/changes/enterprise-image-quantisation/` → `openspec/archive/2026-05-21-enterprise-image-quantisation/`.
|
||||
|
||||
## 13. Manifest updates
|
||||
|
||||
- [x] Run `bun scripts/folder-reorg/generate-manifest.ts`.
|
||||
- [x] Update `STATUS.md` — flip `@suaveplan/image-quantisation` from `–` to `✅`.
|
||||
- [x] Update `SUMMARY.md` with package description.
|
||||
Reference in New Issue
Block a user