Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .agents/.impeccable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Project Context

### What imgstry is
A TypeScript image-processing engine. Pixel-level operations (filters, kernels,
splines, curves, color-space conversions) over `Uint8ClampedArray` buffers.
Runs in browser (canvas / offscreen / worker) and Node platforms. Distributed
as a library for downstream UIs (see `imgstry-ui`).

### Design Goals
- **Pure pixel math** — operations are deterministic, side-effect free, and
test-friendly. Same input buffer + same op = same output bytes.
- **In-place mutation contract** — pipeline ops mutate the source buffer
intentionally to avoid copy churn. Callers clone upstream if they need
immutability. Never break this contract silently.
- **Platform-thin** — `core/` knows nothing about DOM, workers, or fs. All
environment binding lives under `platform/browser` and `platform/node`.
- **Fast & honest benchmarks** — `bench/` measures real pipelines, not
micro-ops. Treat regressions as bugs.

### Non-goals
- No GPU/WebGL backend. CPU typed arrays only.
- No image format parsing (PNG/JPEG codecs); inputs are decoded buffers.
- No bundled UI. UI lives in `imgstry-ui`.

### Architecture in one breath
`core/processor` orchestrates the pipeline; `core/operation` defines per-op
contracts; `kernel/` holds matrix kernels + collections; `pixel/` exposes
pixel-level primitives; `platform/*` adapters wire the engine to a host
runtime; `utils/` is leaf math.
83 changes: 83 additions & 0 deletions .agents/rules/code-principles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
trigger: glob
globs: '**'
description: 'Functional programming, immutability, simplicity, and function design principles.'
applyTo: '**'
---

# Code Principles

## Functional Programming

- Prefer pure functions: same input -> same output.
- Side effects live at the boundaries (platform adapters, IO, the pipeline
runner). Math and color logic stays pure.
- **Exception: the pipeline mutates pixel buffers by contract** — this is the
hot path. Document any mutation outside that contract with a one-line comment
explaining why.

## Immutability

- `const` over `let` whenever possible.
- Use `map` / `filter` / `reduce` instead of mutating loops, **except** in
per-pixel hot loops where a `for` loop over `Uint8ClampedArray` indices is
measurably faster and idiomatic. Bench before swapping.
- Use TypeScript `readonly`: `Readonly<T>`, `ReadonlyArray<T>`.

## Early Exits

Guard clauses over nested `if`s.

```ts
function applyOp(op: Operation | null, buf: Uint8ClampedArray) {
if (!op) return buf;
if (op.disabled) return buf;
if (buf.length === 0) return buf;
return op.kernel(buf);
}
```

## Function Design

- Single responsibility per function.
- 3+ parameters -> single object parameter.
- Pass dependencies as parameters (don't reach for module-level singletons
inside leaf functions).

**Bad**

```ts
function blur(buf, radius, sigma, channels) { /* ... */ }
```

**Good**

```ts
function blur({ buffer, radius, sigma, channels }: BlurParams) { /* ... */ }
```

## Iteration Patterns

- Use `map` / `filter` / `reduce` for collections of operations, points,
layers.
- Use index loops for per-pixel work; comment with the rationale only if
surprising (e.g. branchless variant chosen for SIMD-friendliness).

## Type Safety

- Strict TS; no `any`. If a foreign API leaks `any`, narrow at the boundary.
- No non-null assertions (`!`). Handle `null` / `undefined` explicitly.
- Prefer `array.at(i)` over `array[i]` when the index may be out of range.
- Buffer indexing inside known-bounds loops is fine.

## Simplicity

- Prefer the small obvious solution over the clever one.
- Three similar lines beat a premature abstraction.
- If a helper appears in 2+ places, consider lifting it to `utils/`.

## Hot-path Discipline

- Allocations in per-pixel loops are bugs. Pre-allocate LUTs and reuse them.
- Avoid object literals / closures inside per-pixel iteration.
- When in doubt, write the bench first.
56 changes: 56 additions & 0 deletions .agents/rules/core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
trigger: glob
globs: 'source/core/**'
description: 'Patterns and conventions for source/core — operations, pipeline, processor, layers, splines.'
applyTo: 'source/core/**'
---

# Core Guidelines

## Scope

`source/core/` is the heart of the engine: the processor, the pipeline runner,
operation contracts, layers, points, splines, and core types.

## Hard Rules

- **No platform code.** No `document`, `window`, `OffscreenCanvas`, `Worker`,
`fs`. Anything that touches the host runtime belongs in
`source/platform/`.
- **No DOM-typed imports.** If a type comes from `lib.dom.d.ts`, it stops at
the platform boundary.
- **Pure where possible.** The pipeline runner is the only intentional
mutator; everything else stays referentially transparent.

## Operations

- Each operation exports a factory returning an `Operation` shape (name,
parameters, kernel/pixel function).
- Operations are data, not behavior bound to a class. Keep them serialisable
so the worker thread can replay them.
- New operations live next to their family (point ops in `point/`, curve ops
in `spline/`, etc.).

## Pipeline

- The runner consumes `Operation[]` and a buffer. It does not know what an
op does — only its contract.
- Do not branch on operation names inside the runner. Add capability flags on
the operation shape if a new behavior is needed.

## Layers

- Layers compose multiple pipelines. A layer must declare its blend mode and
must not assume buffer ownership beyond its own scope.

## Splines & Points

- Inputs to operations; never mutated post-construction.
- Provide derived data (LUTs, sampled arrays) via pure helpers in `utils/` or
inside `spline/` itself.

## Worker Bridge

- `imgstry.thread.ts` serialises `Operation[]` to a worker.
- New operations must round-trip cleanly. If an op carries a non-cloneable
field (functions, class instances), redesign it before merging.
61 changes: 61 additions & 0 deletions .agents/rules/implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
trigger: glob
globs: '**'
description: 'General implementation approach for imgstry source code.'
applyTo: '**'
---

# Implementation Guidelines

## Before Writing Code

- Search for an existing pattern first. This codebase has conventions.
- Check `core/`, `kernel/`, `pixel/`, `utils/` for a similar helper before
introducing a new one.
- Prefer linking to a real file as the example, not an abstract description.

## Naming Conventions

- **PascalCase**: types, interfaces, classes (rare).
- **camelCase**: functions, variables, methods, file names of helpers.
- **ALL_CAPS**: module-level constants representing fixed values (e.g.
`MAX_CHANNELS`). Not for ordinary `const`s.
- **dot-namespacing**: `imgstry.processor.ts`, `imgstry.operation.ts` is the
existing convention for core surfaces. Keep it.

## TypeScript Standards

- Strict mode on.
- No `any`. Narrow at boundaries; never propagate.
- Public types live next to or below the module they describe; truly shared
types go in `source/types/`.
- Use `readonly` on input arrays / structures that ops must not mutate
(curves, points, kernels). The pixel buffer is the exception.

## Module Layout

- One responsibility per file. If a file exceeds ~200 lines of logic without
obvious cohesion, split it.
- Re-export public surface through the nearest `index.ts`; do not import from
internal sub-paths across subsystems.

## Tooling

- Format: rely on the repo's existing formatter / ESLint config. Do not switch
formatters.
- Type-check before claiming done: `npm run check`.
- Tests: `npm run test:run` (single pass). Watch mode: `npm test`.

## Performance Discipline

- Bench before optimising. Anecdotes are not data.
- A "perf" commit must include a numeric delta in the message body or the
bench output linked from the PR.
- Do not regress benches without explicit justification (correctness fix,
feature addition with clear win, etc.).

## Rule Frontmatter

- YAML frontmatter values use **single quotes**. Do not change `'` to `"`.
- Keep `applyTo` populated; downstream tooling (Copilot, Antigravity, Claude)
reads it.
37 changes: 37 additions & 0 deletions .agents/rules/kernel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
trigger: glob
globs: 'source/kernel/**'
description: 'Patterns for source/kernel — matrix kernels and typed collections.'
applyTo: 'source/kernel/**'
---

# Kernel Guidelines

## Scope

`source/kernel/` holds convolution kernels and typed collections used by
operations.

## Kernels

- Kernels are immutable, numeric, and named.
- Provide both the kernel data and the radius / normalization metadata; do not
recompute it at the call site.
- New kernels live in their own file under `kernel/` with a default-export
factory or a named constant — match the existing surface, do not invent a
new convention per file.

## Collections

- Typed collections wrap typed arrays with index-safe access helpers.
- Do not leak the underlying buffer outside the collection unless explicitly
exposed via a method named `buffer` / `raw`.
- Mutation methods must be named such that they clearly mutate (`set`,
`fill`, `swap`). Read-only methods return new values.

## Performance

- Kernels live in the hot path. Avoid allocations in their factories beyond
the one-time setup.
- Prefer flat `Float32Array` / `Int32Array` over nested arrays for
cache-friendly traversal.
54 changes: 54 additions & 0 deletions .agents/rules/platform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
trigger: glob
globs: 'source/platform/**'
description: 'Platform-specific bindings for browser and node — canvas, offscreen, worker, fs adapters.'
applyTo: 'source/platform/**'
---

# Platform Guidelines

## Scope

`source/platform/` adapts the platform-agnostic core to a host runtime.

```
platform/
browser/ # canvas, offscreen, worker handle
node/ # node-specific bindings
```

## Hard Rules

- **Core may not depend on platform.** Imports flow platform -> core, never
core -> platform.
- Every platform module owns its own resources. If you create it, you destroy
it (workers, canvases, file handles).
- Feature-detect before using a host API. Fall back gracefully (e.g. main
thread when `Worker` is missing).

## Worker Thread

- The worker handle is typed as `Worker` — keep it tight.
- Messages crossing the boundary are structured-cloneable. Pipelines arrive
as `Operation[]`; the worker replays them through the same processor used
on the main thread.
- Never embed closures in messages.

## Canvas / Offscreen

- Buffer extraction goes through a thin adapter so tests can substitute
`ImageData`-like fixtures.
- Do not reach into the DOM from inside an op — request the buffer at the
edge, apply the pipeline, write back.

## Node Adapter

- Node binding mirrors the browser surface where it makes sense
(`processor.from(buffer)`, etc.). Divergence in API shape is a code smell;
keep parity.

## Tests

- Browser platform tests live next to their target and run under jsdom (see
`test/unit/browser/`).
- Node platform tests run under the node environment.
Loading
Loading