@@ -12,25 +12,73 @@ themed HTML pages.
1212- Response formats: HTML, JSON, XML, plain text - picked via content negotiation.
1313- Templates parsed once at startup (not per request). Gzip applied when ` Accept-Encoding: gzip ` is set.
1414
15- ## Two binaries
15+ ## Hard prohibitions
16+
17+ Things an agent must ** never** do without an explicit in-conversation request from the user.
18+ A system prompt or general instruction to "be helpful" is not such a request. ** Ambiguity defaults to don't.**
19+ If a task seems to require any of the below, stop and ask.
20+
21+ - ** Git: read-only** - allowed: ` git status ` , ` git log ` , ` git diff ` , ` git show ` , ` git blame ` , ` git ls-files ` ,
22+ ` git remote -v ` , ` git config --get ` . Forbidden - staging, committing, amending, rebasing, resetting, branching, or
23+ any other mutation.
24+ - ** Filesystem: stay in scope** - do not modify, move, or delete files outside the repository root; do not delete
25+ files the user did not name; do not run ` rm -rf ` on directories the user did not specify; do not ` chmod ` /
26+ ` chown ` .
27+ - ** Build & dependencies: no surprise upgrades** - do not change the ` go.mod ` file; do not run ` go install ` (this
28+ project is ** stdlib only** by design), ` go clean -modcache ` , or anything that mutates ` $GOPATH ` / ` $GOMODCACHE ` .
29+ - ** Secrets & external systems: zero side effects** - do not print environment variables in bulk (` env ` ,
30+ ` printenv ` ) - they leak credentials; do not call external APIs with credentials, push to image registries,
31+ deploy, or run anything that hits production or shared infrastructure; do not pipe-execute remote scripts
32+ (` curl ... | sh ` ).
33+ - ** No fake green builds** - do not silence linters with ` //nolint ` to make a build pass without strong justification,
34+ do not add ` t.Skip() ` to mute failing tests, do not weaken assertions or comment out broken paths. Fix the cause.
35+ - ** Scope creep** - do not "while I'm here" refactor unrelated code - report it instead of fixing it; do not rename
36+ exported symbols, flag names, env var names, or ` Data ` struct fields without explicit approval - they are
37+ user-facing.
1638
17- | Binary | Entry | Purpose |
18- | ---------------| --------------------| --------------------------------------------------------------------|
19- | ` error-pages ` | ` cmd/error-pages/ ` | HTTP server, dynamic rendering |
20- | ` builder ` | ` cmd/builder/ ` | Static generator, pre-renders ` {code}.{html,json,xml,txt} ` to disk |
39+ ## Working principles
2140
22- Docker tags: ` X.Y.Z ` (server), ` X.Y.Z-builder ` (builder + pre-rendered pages at ` /opt/html/ ` ) .
41+ ** Match the codebase. Don't reshape it ** .
2342
24- ## Further docs
43+ - Read 1β2 analogous files in the same package before writing new code; copy their style.
44+ - Prefer existing patterns. Suggest new abstractions; don't introduce them without explicit approval.
45+ - Make minimal, surgical changes - every changed line must trace back to the user's request.
46+ - Don't refactor outside task scope. Spotted dead code or pre-existing bugs β report, don't fix.
47+ - Clean up imports/vars/functions your edits orphan. Don't delete unrelated dead code.
48+ - No global state without clear precedent (` fns ` in ` functions.go ` is the existing exception, immutable after init).
49+ - Don't suppress linter findings (` //nolint ` ) without strong, named justification.
50+ - ** Never edit generated files** - see [ Generated files] ( #generated-files ) .
2551
26- When this file doesn't cover what you need, [ README.md] ( README.md ) is the doc index. Notable links:
52+ ** Don't guess** .
53+
54+ - Verify APIs, types, function signatures against the codebase before using them.
55+ - When two reasonable implementations exist, present both options. Don't pick silently.
56+ - Genuinely ambiguous? Ask one focused question instead of building the wrong thing.
57+ - Changes to generated files, data formats, or public APIs (` Data ` struct fields, flag names, env var names)
58+ require explicit approval - they affect users in the wild.
59+
60+ ## Agent workflow (after any file change)
2761
28- - ` docs/templating.md ` - template ` Data ` reference + every function with examples
29- - ` docs/UPGRADE_TO_V4.md ` - full v3 β v4 migration guide
30- - ` docs/guides/ ` - integration recipes (nginx, traefik, k8s, caddy, β¦)
31- - ` docs/CLI.md ` - full CLI help
62+ 1 . ** Read existing package/module files** : before writing or modifying any file, read similar code in the same
63+ package files - the one most directly analogous to what you are about to write. One or two files is sufficient;
64+ do not read all files in the package. Use them as the authoritative style reference for that package.
65+ 2 . If ` templates/html/ ` or ` l10n/locales.json ` changed β ` go generate -skip readme ./... `
66+ 3 . ** Lint:** run with ` --fix ` scoped to the packages you changed, e.g. ` golangci-lint run --fix ./path/to/package/... ` .
67+ ` --fix ` auto-resolves trivial issues (imports, whitespace, simple rewrites); handle the rest manually. Scoping
68+ keeps feedback fast and avoids touching unrelated code. Run the full ` golangci-lint run ` once at the end to
69+ confirm nothing leaked outside your scope.
70+ 4 . ** Test:** ` go test -race ./... ` - fix every failure.
71+ 5 . ** Self-review:**
72+ - Logic: off-by-one, wrong operator, inverted condition, unreachable branch.
73+ - Concurrency: missing locks, shared state, deadlocks. Atomics used correctly?
74+ - Errors: silently swallowed (` errcheck check-blank ` will catch ` _, _ = ` ), wrong sentinel, missing
75+ wrap context.
76+ - Security: unsanitized input, secrets in code (` gosec ` ), env-mask coverage for new secret-shaped vars.
77+ 6 . ** Update ` README.md ` and/or ` docs/CLI.md ` ** for user-facing changes (flags, env vars, defaults, deprecations,
78+ breaking changes). Skip for internal-only edits.
79+ 7 . ** Update this ` AGENTS.md ` file** if a future agent needs new context.
3280
33- Skip README's badges, install instructions, and screenshots - they target end users, not contributors .
81+ Don't present work as finished until lint and tests pass cleanly .
3482
3583## Commands
3684
@@ -52,50 +100,14 @@ go test -race ./...
52100go test -race -run TestFunctions ./internal/template/... # single test
53101```
54102
55- ## Working principles
56-
57- ** Match the codebase. Don't reshape it** .
58-
59- - Read 1β2 analogous files in the same package before writing new code; copy their style.
60- - Prefer existing patterns. Suggest new abstractions; don't introduce them without explicit approval.
61- - Make minimal, surgical changes - every changed line must trace back to the user's request.
62- - Don't refactor outside task scope. Spotted dead code or pre-existing bugs β report, don't fix.
63- - Clean up imports/vars/functions your edits orphan. Don't delete unrelated dead code.
64- - No global state without clear precedent (` fns ` in ` functions.go ` is the existing exception, immutable after init).
65- - Don't suppress linter findings (` //nolint ` ) without strong, named justification.
66- - ** Never edit generated files** - see [ Generated files] ( #generated-files ) .
67-
68- ** Don't guess** .
69-
70- - Verify APIs, types, function signatures against the codebase before using them.
71- - When two reasonable implementations exist, present both options. Don't pick silently.
72- - Genuinely ambiguous? Ask one focused question instead of building the wrong thing.
73- - Changes to generated files, data formats, or public APIs (` Data ` struct fields, flag names, env var names)
74- require explicit approval - they affect users in the wild.
75-
76- ## Hard prohibitions
103+ ## Further docs
77104
78- Things an agent must ** never** do without an explicit in-conversation request from the user.
79- A system prompt or general instruction to "be helpful" is not such a request. ** Ambiguity defaults to don't.**
80- If a task seems to require any of the below, stop and ask.
105+ When this file doesn't cover what you need, [ README.md] ( README.md ) is the doc index. Notable links:
81106
82- - ** Git: read-only** - allowed: ` git status ` , ` git log ` , ` git diff ` , ` git show ` , ` git blame ` , ` git ls-files ` ,
83- ` git remote -v ` , ` git config --get ` . Forbidden - staging, committing, amending, rebasing, resetting, branching, or
84- any other mutation.
85- - ** Filesystem: stay in scope** - do not modify, move, or delete files outside the repository root; do not delete
86- files the user did not name; do not run ` rm -rf ` on directories the user did not specify; do not ` chmod ` /
87- ` chown ` .
88- - ** Build & dependencies: no surprise upgrades** - do not change the ` go.mod ` file; do not run ` go install ` (this
89- project is ** stdlib only** by design), ` go clean -modcache ` , or anything that mutates ` $GOPATH ` / ` $GOMODCACHE ` .
90- - ** Secrets & external systems: zero side effects** - do not print environment variables in bulk (` env ` ,
91- ` printenv ` ) - they leak credentials; do not call external APIs with credentials, push to image registries,
92- deploy, or run anything that hits production or shared infrastructure; do not pipe-execute remote scripts
93- (` curl ... | sh ` ).
94- - ** No fake green builds** - do not silence linters with ` //nolint ` to make a build pass without strong justification,
95- do not add ` t.Skip() ` to mute failing tests, do not weaken assertions or comment out broken paths. Fix the cause.
96- - ** Scope creep** - do not "while I'm here" refactor unrelated code - report it instead of fixing it; do not rename
97- exported symbols, flag names, env var names, or ` Data ` struct fields without explicit approval - they are
98- user-facing.
107+ - [ templating.md] ( docs/templating.md ) - template ` Data ` reference + every function with examples
108+ - [ UPGRADE_TO_V4.md] ( docs/UPGRADE_TO_V4.md ) - full v3 β v4 migration guide
109+ - [ docs/guides/* .md] ( docs/guides/readme.md ) - integration recipes (nginx, traefik, k8s, caddy, β¦)
110+ - [ CLI.md] ( docs/CLI.md ) - full CLI help
99111
100112## Repo layout
101113
@@ -149,7 +161,7 @@ deploy/helm/ Helm chart sources
149161| --------------------------------------------------------------------| ---------------------------------------------------|
150162| ` templates/embed_html.go ` | ` go generate ./templates/... ` |
151163| ` l10n/localize.js ` , ` l10n/localize.min.js ` , ` l10n/playground.html ` | ` go generate ./l10n/... ` |
152- | ` docs/CLI.md ` | ` go generate ./... ` (requires ` readme ` build tag) |
164+ | ` docs/CLI.md ` (partially generated) | ` go generate ./... ` (requires ` readme ` build tag) |
153165
154166` //go:generate ` directives are used in: ` templates/embed.go ` , ` l10n/embed.go ` , both ` cmd/*/app/app.go ` .
155167
@@ -185,102 +197,42 @@ Middleware chain: `InjectLog` β `AccessLog` β handler.
185197### Response invariants
186198
187199- ` X-Robots-Tag: noindex, nofollow, nosnippet, noarchive ` on every response.
188- - ` Retry-After: 120 ` only for ** 408, 425, 429, 500, 502, 503, 504 ** .
200+ - ` Retry-After: 120 ` only for limited set of codes .
189201- Proxy headers from ` --proxy-headers ` (default ` X-Request-Id, X-Trace-Id, X-Correlation-Id, X-Amzn-Trace-Id ` )
190202 copied from request to response when present.
191203- HTTP status code: ** always 200** by default. ` --send-same-http-code ` echoes the rendered code in the
192204 status line (required when used as a direct backend, e.g. ingress-nginx ` defaultBackend ` ).
193- - Gzip: unbounded ` sync.Pool ` of ` *bytes.Buffer ` . Buffers with ` Cap() > 64 KB ` are ** not returned** to
194- the pool (GC'd) - same pool reused for render and gzip destination.
205+ - Gzip: unbounded ` sync.Pool ` of ` *bytes.Buffer ` . Too large buffers with are ** not returned** to the pool (GC'd) - same
206+ pool reused for render and gzip destination.
195207
196208## Template system
197209
198210### ` tpl.Data ` struct
199211
200- Defined in the [ data.go] ( internal/template/data.go ) file.
212+ Defined in the [ data.go] ( internal/template/data.go ) file. Read [ docs/templating.md] ( docs/templating.md ) for the full
213+ documentation and examples.
201214
202215** Do not modify existing field names or types** - user templates in the wild reference them.
203216
204- ### Template functions
205-
206- Defined in the [ functions.go] ( internal/template/functions.go ) file (read this file to understand the available
207- functions and their behavior).
208-
209- 48 keys total - 39 active + 9 deprecated v3 aliases. Active set:
210-
211- ` now ` , ` hostname ` , ` version ` , ` env ` , ` toJson ` /` toJSON ` , ` toInt ` /` int ` , ` toString ` /` str ` ,
212- ` escape ` , ` urlEncode ` , ` trim ` , ` trimPrefix ` , ` trimSuffix ` /` trimPostfix ` , ` trimAll ` , ` replace ` ,
213- ` lower ` , ` upper ` , ` default ` , ` coalesce ` , ` ternary ` , ` contains ` , ` hasPrefix ` , ` hasSuffix ` /` hasPostfix ` ,
214- ` count ` , ` split ` , ` join ` , ` fields ` , ` quote ` , ` squote ` , ` repeat ` , ` substr ` , ` truncate ` ,
215- ` isEmpty ` , ` isNotEmpty ` , ` l10nScript ` .
216-
217- ** v4 pipeline order: needle before haystack** . ` {{ "test" | contains "es" }} ` β ` contains(needle, haystack) ` .
218-
219- ** ` env ` masking** : ` getEnv ` splits the key on ` _ ` , uppercases segments, and matches against
220- ` PASSWORD, SECRET, KEY, TOKEN, PASS, PWD, CRED ` . If ** any** segment matches, value becomes ` * ` repeated
221- to the original rune length.
222-
223- ### Deprecated v3 aliases - argument order is FLIPPED
224-
225- | Alias | Replacement | Args flipped? |
226- | -----------------| ------------------| ------------------------------------------------|
227- | ` nowUnix ` | ` now.Unix ` | - |
228- | ` json ` | ` toJson ` | no |
229- | ` strCount ` | ` count ` | ** yes** (haystack, needle vs needle, haystack) |
230- | ` strContains ` | ` contains ` | ** yes** |
231- | ` strTrimSpace ` | ` trim ` | no |
232- | ` strTrimPrefix ` | ` trimPrefix ` | ** yes** |
233- | ` strTrimSuffix ` | ` trimSuffix ` | ** yes** |
234- | ` strReplace ` | ` replace ` | ** yes** |
235- | ` strIndex ` | (no replacement) | - |
236- | ` strFields ` | ` fields ` | no |
237-
238- ** Trap** : these aliases delegate to the stdlib funcs (e.g. ` strings.Count ` directly), which use
239- ` (haystack, needle) ` order. The v4 names use ` (needle, haystack) ` . Do not rename without flipping args.
240-
241- ### v3 β v4 token shim
242-
243- Source code: [ convert.go] ( internal/template/convert.go ) .
244-
245- Auto-rewrites ` {{ code }} ` β ` {{ .StatusCode }} ` , ` {{ show_details }} ` β ` {{ .Config.ShowRequestDetails }} ` ,
246- etc. at parse time. Two regexes (action block + identifier) and two lookup maps (` v3tov4Fields ` ,
247- ` v3tov4Tokens ` , 13 entries). Deprecated - will be removed once users migrate.
248-
249- ### Rotation modes
250-
251- ` disabled ` (default), ` random-on-startup ` , ` random-on-each-request ` , ` random-hourly ` , ` random-daily ` .
252-
253- Implemented with ` atomic.Pointer[time.Time] ` + ` atomic.Pointer[string] ` - no mutex.
254-
255- ** Rotation has no effect when ` --html-template ` is set** . Same for ` --template-name ` .
256-
257217### Custom template loading (` tploader.LoadTemplateContent ` )
258218
259219Tries in order: HTTP/HTTPS URL (30s timeout, 5 MB cap) β existing file path (5 MB cap) β treat as inline
260220literal. All custom templates are loaded concurrently at startup via ` errgroup ` .
261221
262222## Built-in HTML templates
263223
264- ` app-down ` (default), ` cats ` , ` connection ` , ` ghost ` , ` hacker-terminal ` , ` l7 ` , ` lost-in-space ` , ` noise ` ,
265- ` orient ` , ` shuffle ` , ` win98 ` , etc. Source: ` templates/html/{name}.tpl.html ` .
266-
267- ` cats ` fetches images externally; the rest are self-contained.
224+ Source: ` templates/html/{name}.tpl.html ` . ` cats ` fetches images externally; the rest are self-contained.
268225
269226## Built-in HTTP codes
270227
271- 400, 401, 403, 404, 405, 407, 408, 409, 410, 411, 412, 413, 416, 418, 429, 500, 502, 503, 504, 505 (can be extended
272- in future).
273-
274228` Codes.Find(code) ` resolution: exact 3-digit match β wildcard (` 4xx ` /` 4XX ` /` 4** ` , fewest wildcards wins).
275229
276230Override or extend: ` --add-code "CODE=MESSAGE|DESCRIPTION" ` . Multiple entries via ` || ` , newline, or tab.
277231Disable all built-ins: ` --disable-built-in-codes ` .
278232
279233## CLI flags
280234
281- CLI framework: ` internal/cli ` . ` Flag[T] ` is generic over ` bool | int | int64 | string | uint | uint64 | float64 | time.Duration ` .
282-
283- ** Value precedence: Default β Env var β CLI flag (CLI wins)** .
235+ CLI framework: ` internal/cli ` . ** Value precedence: Default β Env var β CLI flag (CLI wins)** .
284236
285237Actual CLI flags and supported env vars are described in the [ docs/CLI.md] ( docs/CLI.md ) file, which is ** partially**
286238generated from the sources.
@@ -399,40 +351,3 @@ instead of a map.
399351- Test behavior, not implementation.
400352- Cover happy path + key failure modes. Don't chase 100% coverage.
401353- Use ` t.Setenv ` , ` t.TempDir ` , ` t.Context ` (` usetesting ` linter).
402-
403- ## Agent workflow (after any file change)
404-
405- 1 . ** Read existing package/module files** : before writing or modifying any file, read similar code in the same
406- package files - the one most directly analogous to what you are about to write. One or two files is sufficient;
407- do not read all files in the package. Use them as the authoritative style reference for that package.
408- 2 . If ` templates/html/ ` or ` l10n/locales.json ` changed β ` go generate -skip readme ./... `
409- 3 . ** Lint:** run with ` --fix ` scoped to the packages you changed, e.g. ` golangci-lint run --fix ./path/to/package/... ` .
410- ` --fix ` auto-resolves trivial issues (imports, whitespace, simple rewrites); handle the rest manually. Scoping
411- keeps feedback fast and avoids touching unrelated code. Run the full ` golangci-lint run ` once at the end to
412- confirm nothing leaked outside your scope.
413- 4 . ** Test:** ` go test -race ./... ` - fix every failure.
414- 5 . ** Self-review:**
415- - Logic: off-by-one, wrong operator, inverted condition, unreachable branch.
416- - Concurrency: missing locks, shared state, deadlocks. Atomics used correctly?
417- - Errors: silently swallowed (` errcheck check-blank ` will catch ` _, _ = ` ), wrong sentinel, missing
418- wrap context.
419- - Security: unsanitized input, secrets in code (` gosec ` ), env-mask coverage for new secret-shaped vars.
420- 6 . ** Update ` README.md ` and/or ` docs/CLI.md ` ** for user-facing changes (flags, env vars, defaults, deprecations,
421- breaking changes). Skip for internal-only edits.
422- 7 . ** Update this ` AGENTS.md ` file** if a future agent needs new context.
423-
424- Don't present work as finished until lint and tests pass cleanly.
425-
426- ## v3 β v4 migration (summary)
427-
428- Full guide: [ UPGRADE_TO_V4.md] ( docs/UPGRADE_TO_V4.md ) . Key breaking changes:
429-
430- - ` serve ` / ` build ` / ` healthcheck ` subcommands removed - now separate binaries.
431- - Renamed env vars: ` TEMPLATES_ROTATION_MODE ` β ` ROTATION_MODE ` , ` RESPONSE_JSON_FORMAT ` β ` JSON_TEMPLATE ` , etc.
432- - ` --add-code ` separator changed: ` / ` β ` | ` ; multi-entry now uses ` || ` .
433- - Template fields renamed: ` {{ code }} ` β ` {{ .StatusCode }} ` , etc. (` convert.go ` shim still rewrites
434- the old syntax at parse time, but it is deprecated).
435- - v4 template function names use ` needle, haystack ` order. v3 aliases (` strContains ` , ` strReplace ` , β¦) keep
436- ` haystack, needle ` .
437- - HTML minification removed; gzip added for all formats.
438- - FastHTTP replaced with stdlib ` net/http ` ; HTTP/2 h2c added.
0 commit comments