From 3b0eb069911ae368711151129b04309aa02ac493 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 19 Mar 2026 20:49:06 +0100 Subject: [PATCH] fix(agents): better context --- .agents/DEVELOPER.md | 134 ------------------ .agents/REVIEWER.md | 51 ------- .agents/TESTER.md | 87 ------------ AGENTS.md | 318 ++++++++++++++++++++++++++++++++++++++++--- CLAUDE.md | 2 +- CONTRIBUTING.md | 8 +- GEMINI.md | 2 +- 7 files changed, 304 insertions(+), 298 deletions(-) delete mode 100644 .agents/DEVELOPER.md delete mode 100644 .agents/REVIEWER.md delete mode 100644 .agents/TESTER.md diff --git a/.agents/DEVELOPER.md b/.agents/DEVELOPER.md deleted file mode 100644 index 5be2c7ae..00000000 --- a/.agents/DEVELOPER.md +++ /dev/null @@ -1,134 +0,0 @@ -# Developer Persona - -You are implementing features, fixing bugs, or refactoring code in Gotenberg. - -## Makefile — the Only Build Interface - -All build and verification tasks go through the Makefile. Do not run `go` commands directly unless debugging a specific package. - -| Command | Purpose | When to use | -| ----------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------- | -| `make build` | Build the Docker image | Before integration tests, or to verify compilation | -| `make run` | Run a Gotenberg container locally | Manual testing. Flags are configured via `.env` and Makefile variables | -| `make fmt` | Format Go code (`go fix`, `golangci-lint fmt`, `go mod tidy`) | Before every commit | -| `make lint` | Lint Go code (strict `.golangci.yml` config) | Before every commit. Zero errors permitted | -| `make lint-prettier` | Lint non-Go files (Markdown, YAML, etc.) with Prettier | Before every commit | -| `make prettify` | Format non-Go files (Markdown, YAML, etc.) with Prettier | Before every commit | -| `make test-unit` | Run unit tests (`go test -race ./...`) | After code changes to `pkg/` | -| `make test-integration` | Run integration tests (Gherkin/Godog, 40min timeout) | After any feature or route change | -| `make godoc` | Serve GoDoc at `localhost:6060` | To verify documentation | - -## Module System - -Gotenberg uses a self-registering module architecture inspired by CaddyServer. Each module: - -- Lives in `pkg/modules//` -- Implements the `gotenberg.Module` interface (at minimum `Descriptor()`) -- May also implement `gotenberg.Provisioner`, `gotenberg.Validator`, or `gotenberg.Debuggable` -- Self-registers via `init()` and is wired through `pkg/standard/` - -When adding a feature, first determine if it belongs in an existing module. Only create a new module if the feature represents a genuinely separate concern. - -## Commit Convention - -Commits must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: - -``` -(): -``` - -Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`, `build`. The scope should match the module or area of the change (e.g., `chromium`, `pdfengines`, `api`). - -## Adding PDF Engine Features - -When adding a new PDF engine capability (e.g., bookmarks, watermark, stamp, embed), you must update the Makefile to include the corresponding engine list variable and flag. Every `--pdfengines-*-engines` flag registered in `pkg/modules/pdfengines/pdfengines.go` must have a matching entry in the Makefile: - -1. **Add a variable** in the Makefile's variable block (around line 60-70): - ```makefile - PDFENGINES__ENGINES= - ``` -2. **Add the flag** in the Makefile's command args block (around line 140-155): - ```makefile - --pdfengines--engines=$(PDFENGINES__ENGINES) \ - ``` - -The default value should match what is defined in `pdfengines.go`'s `fs.StringSlice(...)` call for that flag. - -## Coding Patterns - -- **Error handling:** Always wrap errors with context using `fmt.Errorf("description: %w", err)`. Never swallow errors silently. -- **Import ordering:** Enforced by `gci` — standard library, then third-party, then `github.com/gotenberg/gotenberg/v8`. Three groups separated by blank lines. -- **Mocks:** Comprehensive mock implementations for all major interfaces live in `pkg/gotenberg/mocks.go`. Use these for unit tests. -- **No business logic in `cmd/`:** The `cmd/gotenberg/` package is strictly for wiring and startup. - -## Bruno API Collection - -A [Bruno](https://www.usebruno.com/) collection lives in `.bruno/` and mirrors every Gotenberg route. When adding or updating a route, update the collection to match. - -### Structure - -``` -.bruno/ -├── bruno.json # Collection config -├── collection.bru # Collection-level defaults (Gotenberg-Trace header) -├── environments/ -│ ├── Local.bru # baseUrl: http://localhost:3000 -│ └── Demo.bru # baseUrl: https://demo.gotenberg.dev -├── Health & Info/ # GET routes -├── Chromium/Convert/ # POST routes grouped by module -├── Chromium/Screenshot/ -├── LibreOffice/ -└── PDF Engines// # One folder per feature (Merge, Split, Rotate, …) -``` - -### `.bru` file format - -```bru -meta { - name: - type: http - seq: -} - -post { - url: {{baseUrl}}/forms/ - body: multipartForm - auth: none -} - -body:multipart-form { - files: @file(../../test/integration/testdata/) - : - ~: -} - -headers { - ~Gotenberg-Output-Filename: - ~Gotenberg-Webhook-Url: http://localhost:8080/webhook - ~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error - ~Gotenberg-Webhook-Method: POST - ~Gotenberg-Webhook-Error-Method: POST - ~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"} -} -``` - -### Conventions - -- **Mandatory fields** are listed without prefix; **optional fields** are prefixed with `~` (disabled by default in Bruno). -- **File references** use relative paths to `test/integration/testdata/`. -- **Webhook and output filename headers** are included on every POST route as optional (`~`). -- **One `.bru` file per request**. For routes with read/write variants (e.g., bookmarks, metadata), create separate files in the same folder. - -### Checklist when adding/updating a route - -1. Create or update the `.bru` file in the matching folder under `.bruno/`. -2. Include all form fields from the route handler — check `FormData*` calls in the route function. -3. For file upload fields (`files`, `watermark`, `stamp`, `embeds`), use `@file(...)` with a suitable test file. -4. Verify the URL path matches the route's `Path` field exactly. -5. If you add a new module folder, keep the naming consistent (e.g., `PDF Engines/Rotate/`). - -## Documentation - -- Do not modify `README.md` unless explicitly asked. -- Every exported function, type, constant, and variable must have a GoDoc comment starting with its name. -- New packages must include a `doc.go` file with package-level documentation. diff --git a/.agents/REVIEWER.md b/.agents/REVIEWER.md deleted file mode 100644 index de6b5c76..00000000 --- a/.agents/REVIEWER.md +++ /dev/null @@ -1,51 +0,0 @@ -# Reviewer Persona - -You are reviewing code changes to Gotenberg. Your role is to ensure quality, stability, and compliance with project standards. - -## Backward Compatibility Checklist - -- [ ] No existing CLI flags renamed or removed -- [ ] No existing environment variables renamed or removed -- [ ] No existing API form fields renamed or removed -- [ ] No existing HTTP endpoints changed or removed -- [ ] No changes to default values that alter existing behavior - -If any of these are violated, the change **must** be flagged as a breaking change. - -## Linting Standards - -The `.golangci.yml` enforces strict rules including: `gosec`, `govet`, `errcheck`, `staticcheck`, `dupl`, `bodyclose`, `exhaustive`, `errname`, and more. Zero linting errors are permitted. - -Formatters enforce `gci`, `gofmt`, `gofumpt`, `goimports` with import ordering: - -1. Standard library -2. Third-party packages -3. `github.com/gotenberg/gotenberg/v8` - -Three groups separated by blank lines. - -## Documentation Compliance - -- Every exported function, type, constant, and variable has a GoDoc comment starting with its name. -- New packages include a `doc.go` file. -- Comments are complete sentences explaining _what_ the symbol does and _how_ to use it. -- `README.md` is not modified unless explicitly requested. - -## Code Quality - -- Errors are wrapped with context: `fmt.Errorf("description: %w", err)`. No swallowed errors. -- No business logic in `cmd/`. -- No panics in production code paths. -- Input is validated defensively. -- New features belong in the correct module (or justify a new one). - -## Definition of Done - -A change is ready to merge only when: - -1. Code compiles: `make build` -2. Code is formatted: `make fmt` -3. All linters pass: `make lint` and `make lint-prettier` -4. Integration tests pass: `make test-integration` (at minimum, the relevant `TAGS`) -5. Unit tests pass: `make test-unit` -6. All exported symbols and new packages have compliant GoDoc diff --git a/.agents/TESTER.md b/.agents/TESTER.md deleted file mode 100644 index 5558fbc3..00000000 --- a/.agents/TESTER.md +++ /dev/null @@ -1,87 +0,0 @@ -# Tester Persona - -You are writing or updating tests for Gotenberg. Integration tests are the primary and preferred method. - -## Integration Tests - -- **Framework:** Gherkin (BDD) via [Godog](https://github.com/cucumber/godog), with `testcontainers-go` for Docker orchestration. -- **Feature files:** `test/integration/features/*.feature` — one file per endpoint or capability. -- **Test infrastructure:** `test/integration/scenario/` — Go step definitions, container management, HTTP helpers, PDF validation. -- **Entry point:** `test/integration/main_test.go` (build tag: `integration`). -- **Test data:** `test/integration/testdata/` - -### How It Works - -Each scenario spins up a fresh Gotenberg Docker container via testcontainers. The step definitions in `scenario/scenario.go` map Gherkin steps to Go functions. An additional `gotenberg/integration-tools` container provides PDF validation tools (`verapdf`, `pdfinfo`, `pdftotext`). - -**Important:** Integration tests require a Docker image. Run `make build` before `make test-integration`. - -### Selective Test Runs - -Use the `TAGS` variable to run only relevant scenarios: - -```bash -make test-integration TAGS=health -make test-integration TAGS=chromium-convert-html -make test-integration TAGS="merge,split" -``` - -Available tags: `chromium`, `chromium-concurrent`, `chromium-convert-html`, `chromium-convert-markdown`, `chromium-convert-url`, `debug`, `health`, `libreoffice`, `libreoffice-convert`, `output-filename`, `pdfengines`, `pdfengines-convert`, `pdfengines-embed`, `embed`, `pdfengines-encrypt`, `encrypt`, `pdfengines-flatten`, `flatten`, `pdfengines-merge`, `merge`, `pdfengines-metadata`, `metadata`, `pdfengines-split`, `split`, `pdfengines-watermark`, `watermark`, `pdfengines-stamp`, `stamp`, `pdfengines-bookmarks`, `bookmarks`, `prometheus-metrics`, `root`, `version`, `webhook`, `download-from`. - -Other useful flags: - -```bash -make test-integration NO_CONCURRENCY=true # Disable parallel scenarios -make test-integration PLATFORM=linux/arm64 # Force a specific platform -``` - -### Writing a New Integration Test - -1. Create or update a `.feature` file in `test/integration/features/`. -2. Tag it appropriately (e.g., `@chromium @chromium-convert-html`). -3. If the feature requires new tag(s), add them to both the `TAGS` comment block in the `Makefile` and the "Available tags" list in this file. -4. If you create a new step definition, add it to `scenario/scenario.go`, register it in `InitializeScenario`, and update the "Available Gherkin Steps" list below. -5. Test data goes in `test/integration/testdata/`. - -### Available Gherkin Steps - -**Given (setup):** - -- `I have a default Gotenberg container` -- `I have a Gotenberg container with the following environment variable(s):` (table: key | value) -- `I have a (webhook|static) server` - -**When (action):** - -- `I make a "(GET|HEAD)" request to Gotenberg at the "" endpoint` -- `I make a "(GET|HEAD)" request to Gotenberg at the "" endpoint with the following header(s):` (table: name | value) -- `I make a "(POST)" request to Gotenberg at the "" endpoint with the following form data and header(s):` (table: name | value | kind — where kind is `file`, `field`, or `header`) -- `I make concurrent "(POST)" requests to Gotenberg at the "" endpoint with the following form data and header(s):` (same table format) -- `I wait for the asynchronous request to the webhook` - -**Then (assertions):** - -- `the response status code should be ` -- `the (response|webhook request) header "" should be ""` -- `the (response|webhook request) cookie "" should be ""` -- `the (response|webhook request) body should match string:` (docstring) -- `the (response|webhook request) body should contain string:` (docstring) -- `the (response|webhook request) body should match JSON:` (docstring — use `"ignore"` for dynamic values like timestamps) -- `there should be PDF(s) in the (response|webhook request)` -- `there should be the following file(s) in the (response|webhook request):` (table of filenames) -- `the "" PDF should have page(s)` -- `the "" PDF (should|should NOT) be set to landscape orientation` -- `the "" PDF (should|should NOT) have the following content at page :` (docstring) -- `the (response|webhook request) PDF(s) should be valid "" with a tolerance of failed rule(s)` (standards: `PDF/A-1b`, `PDF/A-2b`, `PDF/A-3b`, `PDF/UA-1`, `PDF/UA-2`) -- `the (response|webhook request) PDF(s) (should|should NOT) be flatten` -- `the (response|webhook request) PDF(s) (should|should NOT) be encrypted` -- `the (response|webhook request) PDF(s) (should|should NOT) have the "" file embedded` -- `the Gotenberg container (should|should NOT) log the following entries:` (table of log substrings) -- `all concurrent response status codes should be ` -- `all concurrent responses should have PDF(s)` - -## Unit Tests - -- Use **table-driven tests** for pure logic in `pkg/`. -- Mock external dependencies using the comprehensive mocks in `pkg/gotenberg/mocks.go`. -- Run with `make test-unit`. diff --git a/AGENTS.md b/AGENTS.md index 678a1f1f..de023ca3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,49 @@ You are working on **Gotenberg**, a Docker-based API for converting documents to PDF. It is a widely used production dependency. Stability and backward compatibility are paramount. When in doubt about whether a change is breaking, flag it rather than assuming it's safe. +## Mandatory Workflow + +Every task MUST follow these five steps in order. Do not skip any step. + +### Step 1 — Plan + +Before writing any code, produce a plan that covers: + +- **Problem statement**: What needs to change and why. +- **Proposed solution**: The recommended approach with enough detail to implement (files to modify, interface changes, pipeline positioning, form fields, etc.). +- **Alternatives considered**: At least one alternative approach when pertinent, with a brief explanation of why the proposed solution is preferred. +- **Scope**: List every file that will be created or modified. +- **Testing strategy**: Which integration test tags will be affected, what new scenarios are needed, and whether unit tests are required. + +Present the plan to the user and wait for approval before proceeding to Step 2. If the user provides a plan, validate it against the codebase and flag any issues before implementing. + +### Step 2 — Implement + +Implement the approved plan following the coding standards and patterns described in this document. After implementation, verify the build compiles (`go build ./...`). + +### Step 3 — Test + +Write or update tests based on the plan's testing strategy: + +- **Integration tests** (primary): Gherkin scenarios in `test/integration/features/`. See the [Integration Tests](#integration-tests) section. +- **Unit tests** (when applicable): Table-driven tests in `*_test.go` files using mocks from `pkg/gotenberg/mocks.go`. + +### Step 4 — Review + +Self-review the implementation against the [Review Checklist](#review-checklist). Fix any issues found before presenting the result to the user. + +### Step 5 — Commit + +Present the review to the user and **wait for explicit approval**. Do NOT commit until the user confirms. Once approved, create a commit following the [Conventional Commits](https://www.conventionalcommits.org/) specification: + +``` +(): +``` + +Stage only the files related to the change. Do not use `git add -A` or `git add .`. + +--- + ## Core Principles - **Backward compatibility is law.** Never modify existing CLI flags, environment variables, or API form fields unless explicitly instructed to perform a breaking change. Flag any breaking change immediately. @@ -18,19 +61,11 @@ pkg/modules/ → Feature modules (api, chromium, libreoffice, pdfengines pkg/standard/ → Wires all standard modules together via imports. test/integration/ → Gherkin feature files + Go test infrastructure. build/ → Dockerfile, fonts, Chromium config. +.bruno/ → Bruno API collection (mirrors every route). ``` Key interfaces live in `pkg/gotenberg/` — `Module`, `Provisioner`, `Validator`, `Debuggable`. Every module implements `Descriptor()` and self-registers. When adding features, determine if they belong in an existing module or require a new one. -## Quick Reference - -- Format before committing: `make fmt` (Go) and `make prettify` (non-Go) -- Lint before committing: `make lint && make lint-prettier` -- Commits must follow [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat(chromium): add screenshot endpoint`) -- Run unit tests: `make test-unit` -- Run integration tests: `make build && make test-integration TAGS=` -- Never run `go` commands directly — use the Makefile. - ## Codebase Navigation - Start with `pkg/gotenberg/` for core interfaces and `pkg/modules/` for feature implementations. @@ -39,14 +74,263 @@ Key interfaces live in `pkg/gotenberg/` — `Module`, `Provisioner`, `Validator` - Import ordering is enforced: standard library, third-party, then `github.com/gotenberg/gotenberg/v8` — separated by blank lines. - When making changes, run only the relevant integration test tag rather than the full suite (40min timeout). -## Persona Selection (MANDATORY) +--- -Before starting any task, you MUST read the appropriate persona file from `.agents/` based on what is being asked. This is not optional — the persona contains critical context you need. +## Makefile — the Only Build Interface -| Task type | Persona to load | Trigger keywords / signals | -| ------------------------------------------------------------ | ---------------------------------------------- | --------------------------------------------------------------------------------- | -| Writing or modifying code (features, bug fixes, refactoring) | [`.agents/DEVELOPER.md`](.agents/DEVELOPER.md) | "add", "fix", "implement", "refactor", "change", "update", writing any `.go` file | -| Writing or updating tests | [`.agents/TESTER.md`](.agents/TESTER.md) | "test", "scenario", "coverage", `.feature` files, `_test.go` files | -| Reviewing code or PRs | [`.agents/REVIEWER.md`](.agents/REVIEWER.md) | "review", "check", "audit", PR URLs, reviewing diffs | +All build and verification tasks go through the Makefile. Do not run `go` commands directly unless debugging a specific package. -If a task spans multiple concerns (e.g., implementing a feature AND writing tests), load ALL relevant personas. +| Command | Purpose | When to use | +| ----------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------- | +| `make build` | Build the Docker image | Before integration tests, or to verify compilation | +| `make run` | Run a Gotenberg container locally | Manual testing. Flags are configured via `.env` and Makefile variables | +| `make fmt` | Format Go code (`go fix`, `golangci-lint fmt`, `go mod tidy`) | Before every commit | +| `make lint` | Lint Go code (strict `.golangci.yml` config) | Before every commit. Zero errors permitted | +| `make lint-prettier` | Lint non-Go files (Markdown, YAML, etc.) with Prettier | Before every commit | +| `make prettify` | Format non-Go files (Markdown, YAML, etc.) with Prettier | Before every commit | +| `make test-unit` | Run unit tests (`go test -race ./...`) | After code changes to `pkg/` | +| `make test-integration` | Run integration tests (Gherkin/Godog, 40min timeout) | After any feature or route change | +| `make godoc` | Serve GoDoc at `localhost:6060` | To verify documentation | + +## Module System + +Gotenberg uses a self-registering module architecture inspired by CaddyServer. Each module: + +- Lives in `pkg/modules//` +- Implements the `gotenberg.Module` interface (at minimum `Descriptor()`) +- May also implement `gotenberg.Provisioner`, `gotenberg.Validator`, or `gotenberg.Debuggable` +- Self-registers via `init()` and is wired through `pkg/standard/` + +When adding a feature, first determine if it belongs in an existing module. Only create a new module if the feature represents a genuinely separate concern. + +## Commit Convention + +Commits must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: + +``` +(): +``` + +Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`, `build`. The scope should match the module or area of the change (e.g., `chromium`, `pdfengines`, `api`). + +## Coding Patterns + +- **Error handling:** Always wrap errors with context using `fmt.Errorf("description: %w", err)`. Never swallow errors silently. +- **Import ordering:** Enforced by `gci` — standard library, then third-party, then `github.com/gotenberg/gotenberg/v8`. Three groups separated by blank lines. +- **Mocks:** Comprehensive mock implementations for all major interfaces live in `pkg/gotenberg/mocks.go`. Use these for unit tests. +- **No business logic in `cmd/`:** The `cmd/gotenberg/` package is strictly for wiring and startup. + +## Adding PDF Engine Features + +When adding a new PDF engine capability (e.g., bookmarks, watermark, stamp, embed), you must update the Makefile to include the corresponding engine list variable and flag. Every `--pdfengines-*-engines` flag registered in `pkg/modules/pdfengines/pdfengines.go` must have a matching entry in the Makefile: + +1. **Add a variable** in the Makefile's variable block (around line 60-70): + ```makefile + PDFENGINES__ENGINES= + ``` +2. **Add the flag** in the Makefile's command args block (around line 140-155): + ```makefile + --pdfengines--engines=$(PDFENGINES__ENGINES) \ + ``` + +The default value should match what is defined in `pdfengines.go`'s `fs.StringSlice(...)` call for that flag. + +--- + +## Integration Tests + +- **Framework:** Gherkin (BDD) via [Godog](https://github.com/cucumber/godog), with `testcontainers-go` for Docker orchestration. +- **Feature files:** `test/integration/features/*.feature` — one file per endpoint or capability. +- **Test infrastructure:** `test/integration/scenario/` — Go step definitions, container management, HTTP helpers, PDF validation. +- **Entry point:** `test/integration/main_test.go` (build tag: `integration`). +- **Test data:** `test/integration/testdata/` + +### How It Works + +Each scenario spins up a fresh Gotenberg Docker container via testcontainers. The step definitions in `scenario/scenario.go` map Gherkin steps to Go functions. An additional `gotenberg/integration-tools` container provides PDF validation tools (`verapdf`, `pdfinfo`, `pdftotext`). + +**Important:** Integration tests require a Docker image. Run `make build` before `make test-integration`. + +### Selective Test Runs + +Use the `TAGS` variable to run only relevant scenarios: + +```bash +make test-integration TAGS=health +make test-integration TAGS=chromium-convert-html +make test-integration TAGS="merge,split" +``` + +Available tags: `chromium`, `chromium-concurrent`, `chromium-convert-html`, `chromium-convert-markdown`, `chromium-convert-url`, `debug`, `health`, `libreoffice`, `libreoffice-convert`, `output-filename`, `pdfengines`, `pdfengines-convert`, `pdfengines-embed`, `embed`, `pdfengines-encrypt`, `encrypt`, `pdfengines-flatten`, `flatten`, `pdfengines-merge`, `merge`, `pdfengines-metadata`, `metadata`, `pdfengines-split`, `split`, `pdfengines-watermark`, `watermark`, `pdfengines-stamp`, `stamp`, `pdfengines-bookmarks`, `bookmarks`, `pdfengines-rotate`, `rotate`, `prometheus-metrics`, `root`, `version`, `webhook`, `download-from`. + +Other useful flags: + +```bash +make test-integration NO_CONCURRENCY=true # Disable parallel scenarios +make test-integration PLATFORM=linux/arm64 # Force a specific platform +``` + +### Writing a New Integration Test + +1. Create or update a `.feature` file in `test/integration/features/`. +2. Tag it appropriately (e.g., `@chromium @chromium-convert-html`). +3. If the feature requires new tag(s), add them to both the `TAGS` comment block in the `Makefile` and the "Available tags" list in this file. +4. If you create a new step definition, add it to `scenario/scenario.go`, register it in `InitializeScenario`, and update the "Available Gherkin Steps" list below. +5. Test data goes in `test/integration/testdata/`. + +### Available Gherkin Steps + +**Given (setup):** + +- `I have a default Gotenberg container` +- `I have a Gotenberg container with the following environment variable(s):` (table: key | value) +- `I have a (webhook|static) server` + +**When (action):** + +- `I make a "(GET|HEAD)" request to Gotenberg at the "" endpoint` +- `I make a "(GET|HEAD)" request to Gotenberg at the "" endpoint with the following header(s):` (table: name | value) +- `I make a "(POST)" request to Gotenberg at the "" endpoint with the following form data and header(s):` (table: name | value | kind — where kind is `file`, `field`, or `header`) +- `I make concurrent "(POST)" requests to Gotenberg at the "" endpoint with the following form data and header(s):` (same table format) +- `I wait for the asynchronous request to the webhook` + +**Then (assertions):** + +- `the response status code should be ` +- `the (response|webhook request) header "" should be ""` +- `the (response|webhook request) cookie "" should be ""` +- `the (response|webhook request) body should match string:` (docstring) +- `the (response|webhook request) body should contain string:` (docstring) +- `the (response|webhook request) body should match JSON:` (docstring — use `"ignore"` for dynamic values like timestamps) +- `there should be PDF(s) in the (response|webhook request)` +- `there should be the following file(s) in the (response|webhook request):` (table of filenames) +- `the "" PDF should have page(s)` +- `the "" PDF (should|should NOT) be set to landscape orientation` +- `the "" PDF (should|should NOT) have the following content at page :` (docstring) +- `the (response|webhook request) PDF(s) should be valid "" with a tolerance of failed rule(s)` (standards: `PDF/A-1b`, `PDF/A-2b`, `PDF/A-3b`, `PDF/UA-1`, `PDF/UA-2`) +- `the (response|webhook request) PDF(s) (should|should NOT) be flatten` +- `the (response|webhook request) PDF(s) (should|should NOT) be encrypted` +- `the (response|webhook request) PDF(s) (should|should NOT) have the "" file embedded` +- `the Gotenberg container (should|should NOT) log the following entries:` (table of log substrings) +- `all concurrent response status codes should be ` +- `all concurrent responses should have PDF(s)` + +--- + +## Review Checklist + +### Backward Compatibility + +- [ ] No existing CLI flags renamed or removed +- [ ] No existing environment variables renamed or removed +- [ ] No existing API form fields renamed or removed +- [ ] No existing HTTP endpoints changed or removed +- [ ] No changes to default values that alter existing behavior + +If any of these are violated, the change **must** be flagged as a breaking change. + +### Linting Standards + +The `.golangci.yml` enforces strict rules including: `gosec`, `govet`, `errcheck`, `staticcheck`, `dupl`, `bodyclose`, `exhaustive`, `errname`, and more. Zero linting errors are permitted. + +Formatters enforce `gci`, `gofmt`, `gofumpt`, `goimports` with import ordering: + +1. Standard library +2. Third-party packages +3. `github.com/gotenberg/gotenberg/v8` + +Three groups separated by blank lines. + +### Code Quality + +- Errors are wrapped with context: `fmt.Errorf("description: %w", err)`. No swallowed errors. +- No business logic in `cmd/`. +- No panics in production code paths. +- Input is validated defensively. +- New features belong in the correct module (or justify a new one). + +### Documentation + +- Every exported function, type, constant, and variable has a GoDoc comment starting with its name. +- New packages include a `doc.go` file. +- `README.md` is not modified unless explicitly requested. + +### Definition of Done + +A change is ready to merge only when: + +1. Code compiles: `go build ./...` +2. Code is formatted: `make fmt` +3. All linters pass: `make lint` and `make lint-prettier` +4. Integration tests pass: `make test-integration` (at minimum, the relevant `TAGS`) +5. Unit tests pass: `make test-unit` +6. All exported symbols and new packages have compliant GoDoc +7. Bruno collection is updated (if routes were added or modified) + +--- + +## Bruno API Collection + +A [Bruno](https://www.usebruno.com/) collection lives in `.bruno/` and mirrors every Gotenberg route. When adding or updating a route, update the collection to match. + +### Structure + +``` +.bruno/ +├── bruno.json # Collection config +├── collection.bru # Collection-level defaults (Gotenberg-Trace header) +├── environments/ +│ ├── Local.bru # baseUrl: http://localhost:3000 +│ └── Demo.bru # baseUrl: https://demo.gotenberg.dev +├── Health & Info/ # GET routes +├── Chromium/Convert/ # POST routes grouped by module +├── Chromium/Screenshot/ +├── LibreOffice/ +└── PDF Engines// # One folder per feature (Merge, Split, Rotate, …) +``` + +### `.bru` file format + +```bru +meta { + name: + type: http + seq: +} + +post { + url: {{baseUrl}}/forms/ + body: multipartForm + auth: none +} + +body:multipart-form { + files: @file(../../test/integration/testdata/) + : + ~: +} + +headers { + ~Gotenberg-Output-Filename: + ~Gotenberg-Webhook-Url: http://localhost:8080/webhook + ~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error + ~Gotenberg-Webhook-Method: POST + ~Gotenberg-Webhook-Error-Method: POST + ~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"} +} +``` + +### Conventions + +- **Mandatory fields** are listed without prefix; **optional fields** are prefixed with `~` (disabled by default in Bruno). +- **File references** use relative paths to `test/integration/testdata/`. +- **Webhook and output filename headers** are included on every POST route as optional (`~`). +- **One `.bru` file per request**. For routes with read/write variants (e.g., bookmarks, metadata), create separate files in the same folder. + +### Checklist when adding/updating a route + +1. Create or update the `.bru` file in the matching folder under `.bruno/`. +2. Include all form fields from the route handler — check `FormData*` calls in the route function. +3. For file upload fields (`files`, `watermark`, `stamp`, `embeds`), use `@file(...)` with a suitable test file. +4. Verify the URL path matches the route's `Path` field exactly. +5. If you add a new module folder, keep the naming consistent (e.g., `PDF Engines/Rotate/`). diff --git a/CLAUDE.md b/CLAUDE.md index 6057d762..23d03fa6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,3 +1,3 @@ # Claude Code — Gotenberg -Read [AGENTS.md](AGENTS.md) first. It is the root context: core principles, project layout, quick reference, codebase navigation, and persona selection. +Read [AGENTS.md](AGENTS.md) first. It contains everything: core principles, project layout, coding standards, the mandatory 4-step workflow (Plan → Implement → Test → Review), integration test reference, review checklist, and Bruno collection guidelines. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86361b76..645721ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,13 +4,7 @@ Thank you for your interest in contributing to Gotenberg! This guide will help y ## Before You Start -Please read the [AGENTS.md](AGENTS.md) file — it describes the core principles, project layout, and development standards that all contributions must follow. Even though it is written for AI agents, the same rules apply to human contributors. - -For deeper context on specific areas, see the personas in `.agents/`: - -- **[DEVELOPER](.agents/DEVELOPER.md)** — Makefile workflow, module system, coding patterns. -- **[TESTER](.agents/TESTER.md)** — How to write integration and unit tests. -- **[REVIEWER](.agents/REVIEWER.md)** — What reviewers look for (useful to check before submitting). +Please read the [AGENTS.md](AGENTS.md) file — it describes the core principles, project layout, development standards, integration test reference, review checklist, and Bruno collection guidelines that all contributions must follow. ## Getting Started diff --git a/GEMINI.md b/GEMINI.md index d36266d4..58b9277d 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,3 +1,3 @@ # Gemini — Gotenberg -Read [AGENTS.md](AGENTS.md) first. It is the root context: core principles, project layout, quick reference, codebase navigation, and persona selection. +Read [AGENTS.md](AGENTS.md) first. It contains everything: core principles, project layout, coding standards, the mandatory 4-step workflow (Plan → Implement → Test → Review), integration test reference, review checklist, and Bruno collection guidelines.