vipsgen
AGENTS.md269 starsChanged 23 days agoMITView on GitHub
Notes for agents and contributors, short on purpose. The README is for users; this is for anyone changing the generator. - vips/, vips817/ and vips816/ are produced by CI from the matrix in .github/workflows/ci.yml. Do not regenerate them locally: the libvips you have is not the one they were generated from, so a local run produces spurious diffs. Change internal/ or the templates, push, and CI commits the result. - vipsgen -debug writes into the repo, including regenerated packages. If…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
The file
We split this AGENTS.md into the rules it gives an agent
3 rules. About 0 are common, 2 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 14
Commit explicit paths. git add -A sweeps generated trees and scratch programs into the branch.
- Very specific: line 36
When a change alters generated output, the last CI job commits it.
- Distinctive: line 48
Call the operation twice — once with an explicit zero, once without — and compare outcomes.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Working on vipsgen
Notes for agents and contributors, short on purpose. The README is for users; this is for anyone changing the generator.
Regenerating
vips/,vips817/andvips816/are produced by CI from the matrix in.github/workflows/ci.yml. Do not regenerate them locally: the libvips you have is not the one they were generated from, so a local run produces spurious diffs. Changeinternal/or the templates, push, and CI commits the result.vipsgen -debugwrites into the repo, including regenerated packages. If you only wanted the JSON dump, restore the tree afterwards.- Commit explicit paths.
git add -Asweeps generated trees and scratch programs into the branch.
The options rule
- A field is a pointer when libvips accepts its zero value and that zero differs from the
option's default.
nilmeans "not supplied". The classification is derived from GParamSpec, so it tracks the libvips you build against — the three packages are not interchangeable. internal/generator/presence.goholds the rule and one measured exception list. The list cannot be derived:gifload.nandwebpload.nshare a range that contains zero, and both reject it. Probe with a fixture where the argument can have an effect —nlooks accepted on a single-page file and rejects zero once there is a second page.vips/zero_pins_test.gopins the classification;internal/generator/presence_test.gopins the policy, including that the policy reachesOptionalInputs— that slice holds copies, so flaggingArgumentsalone silently does nothing.
CI
- Three legs, one per libvips version, each regenerating its own package before it tests.
- Examples are built only on the
vips(8.18) leg, so a compile error an older libvips would hit does not show there — and one the newer one hits shows nowhere else. - When a change alters generated output, the last CI job commits it. That job's own run can
park at
action_required, which is neither a pass nor a failure: the parent run already built the content it committed. - The legs run in
ghcr.io/cshum/vipsgen-deps, built fromdocker/deps.Dockerfile: the packages the workflow used to install, plus libultrahdr. Its tags are append-only, so a dependency change bumps the number instead of repointing a tag a running job may be pulling. - Same libvips in, same bytes out — regeneration is expected to be byte-identical, and a change that alters the output should be explainable operation by operation.
Measuring a zero exception
The list in internal/generator/presence.go is measured, so re-measure before adding to it.
Call the operation twice — once with an explicit zero, once without — and compare outcomes;
an error in the first and not the second is attributable to the zero rather than to the
input. Two traps:
- The
vipsCLI swallows an empty string, so an empty--idproves nothing. Measure strings through the Go API, wherevips.Ptr("")reaches libvips. - A single-page input can give the wrong verdict for
n, which is about pages: a documented rejection only reproduces with a multi-page file.
The debug dump
vipsgen -debug writes debug_operations.json: operations with RequiredInputs,
OptionalInputs and their types. Note that its GoName is lowercase (gifload, n) while
the generated code is not (GifloadOptions, N), which is easy to get wrong in a script.
The templates also carry hand-written API beside the generated operations - the multi-page
helpers, Pages, Loop, the metadata accessors. That is v1 API: change it only for a stated
reason.
Tests
./internal/...runs on every leg, but each leg tests only its own output directory (go test ./${{ matrix.output_dir }}/...). So a test underinternal/that importsv2/vipsdrags the 8.18-generated package into the 8.16 and 8.17 legs and fails on headers the older libvips does not have - which is whyvips/zero_pins_test.gosits inside the package rather than beside the generator.- The consequence is that those pins run on one leg only, against the libvips the exception list was measured on. Behaviour on the other legs is not pinned.
- The generated tests count open images, so close what you open.
- The operation subtests are generated from the same introspection data as the operations: one
per operation, one per options variant, one per default constructor. They are regenerated,
so add a shape in
internal/generator/operation_*.gorather than editing them. - A hand-written test in a
*_test.go.tmplcompiles against all three trees, so it may only use symbols every version has. An operation that exists in some versions belongs in generation, where the tree without it simply has no subtest: writingNewMagickloadSourceby hand broke the 8.16 and 8.17 legs, which have no such symbol. - A wrapper calls the libvips function directly, so a library without that operation does not
link - the import path has to match.
HasOperationcovers the other direction, a loaded library older than the one the package was built against, where the missing symbol would be a fatal lookup rather than an error. testdata/holds inputs libvips reads but cannot write (pdf, svg, mat, analyze, openexr). Tests look for them at../testdata/and skip when absent, so a tree generated by the command passes without them: the fixtures ship with this repository, not the generator, and each one has a row intestdata/README.mdnaming where it came from.- A skip names the precondition and is the expected outcome for a tree or a build that lacks the thing. A subtest that asserts nothing on the way through is not a test.
- An error path is reachable through the public API: a required number at zero or an enum
outside its range makes libvips fail the call with parameter not set, which is what the
<op>_errorsubtests use. Out-of-range option values are accepted, so they do not. - A dependency CI is missing is a dependency to install, not a fixture to invent:
ubuntu-24.04splits libheif into plugin packages, and a shipped HEIC there failed with Unsupported codec instead of skipping. go test ./vips/ -covermeasures the package, and most of what is missing is not effort: the operations left out need a real document as their fixture - slide decks, camera raw - so the ceiling without test data is far below 100%.go testnever sees the C, and the flags cannot ride onCGO_CFLAGS:-Werrorthere fails on cgo's own prolog and onruntime/cgo, which is not warning-clean. So CI compiles the generated.cfiles directly. That is where the wrappers broke before.
Docs
- README and MIGRATION use plain words and state behaviour, not history. Generated field comments carry the range and the default, which is where a reader looks first.
- Numbers in docs come from the artefact — the classification table, the probe output — not from arithmetic in prose, and not from a previous draft.
Releasing
Tags are manual and they are the release: the module path carries the major version, so
v2.0.0 goes on this line and v1 keeps working at its existing paths.
Elsewhere
README for users, MIGRATION for v1 to v2, internal/introspection/README.md for the
checklist to run when bumping libvips.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

