Guidance for AI coding agents (and humans) working in this repository. AGENTS.md
is a symlink to this file.
github.com/flashcatcloud/flashduty-sdk is the official typed Go client for the
Flashduty open API. It is a public, go get-able library
with external consumers; two first-party consumers are the Flashduty CLI and the
Flashduty MCP server. Treat every exported symbol as a public API surface.
- Single root package
flashduty. No subpackages. Flat, discoverable. - One file per API domain —
incidents.go,schedules.go,alerts.go,statuspage.go,audit.go,reports.go,insight.go, … Each holds that domain'sClientmethods and its request/response structs. client.go— transport:makeRequest, and the genericpostData[T]/getData[T]helpers that decode the API envelope into a typed value.types.go— shared response types used across domains.errors.go— typed API errors.logger.go,client_options.go— cross-cutting.
- All absolute time fields in RESPONSE structs use
flashduty.Timestamp(Unix seconds) orflashduty.TimestampMilli(Unix milliseconds), matching the wire unit. Both marshal to an RFC3339 string and unmarshal from epoch-or-RFC3339; pick the variant the API actually sends (most fields are seconds; feed/audit endpoints are milliseconds). This keeps machine-readable output human- and LLM-friendly without any downstream guessing. Do not add bareint64"...At/...Time" fields to responses. - Durations, cyclic-window offsets, and counts stay
int64(e.g. a rotation length, a notification lead-time). They are not instants. When a time-typed field must stayint64, state its unit and meaning in a comment — silence is a bug. - Request/input struct time fields stay
int64(callers pass epochs; inputs are never rendered for humans). Document the unit (// Unix seconds). - If you genuinely cannot tell whether an
int64is an instant or a duration, it staysint64until the API contract proves otherwise. Never guess a field into a timestamp — a wrong rendered date is worse than a raw integer.
- Every exported
Clientmethod returns a typed struct/slice, neverany/interface{}/map[string]any. If the API adds a new response, add the struct. - Signature shape:
func (c *Client) Verb(ctx context.Context, in *VerbInput) (*VerbResult, error). - Decode through
postData[T]/getData[T]; don't hand-rolljson.Unmarshalper method.
- First principles. Reason from the API contract and the caller's real need, not from analogy. Ask "what does this field/type have to be?"
- No over-engineering (YAGNI). Build for the requirement that exists. No speculative config knobs, no extension points without a concrete second caller.
- Root cause first. Fix the cause, not the symptom. If a change touches many call sites, route them through one shared helper rather than patching each.
- No transitional shims. Land at the end state in one change — no bridging feature flags, no dead-code paths, no "clean it up later."
- Gate (no Makefile/CI yet — run all four, all must be clean):
go test ./... && go vet ./... && go build ./... && gofmt -l .gofmt -l .must print nothing. - Unit tests are table-driven and hermetic — no live network. Use
httptestto stub the API; pin request shape and decode behavior. - New behavior ships with a test that fails before the change and passes after.
- "Verified" means I ran the gate and saw it pass. Never infer success from a build alone; never claim done on an unrun test.
- Pre-1.0 (
v0.x): av0.xbump carries no compat guarantee (Go module rules), so keep version churn minimal — each release bumps the patch (v0.x.(z+1)). Breaking changes (exported-type or signature changes) are permitted pre-1.0, but MUST be called out explicitly in the PR description so consumers upgrade deliberately. Reserve a minor bump for a deliberate milestone, not for every break. - Consumers pin via tag or pseudo-version (
go get github.com/flashcatcloud/flashduty-sdk@v0.x.y).
- Feature work lives on its own branch off
main; deliver via PR. Never commit tomain. - Never
git push --force. - Commit messages stay clean — no
Co-Authored-By/ "Generated with" trailers for any code agent. - One feature per branch/PR so review stays scoped.