This guide covers building, testing, and developing clicky itself. For using clicky as a library in your own application, see CLAUDE.md and README.md.
- Go 1.26+.
go.workstitches this repo together with local sibling checkouts offlanksource/arch-unit,flanksource/commons, andflanksource/gavel. Clone those next to clicky so the workspace resolves — edits to commons are then picked up directly, without ago get/version bump.- The tree contains 6 separate Go modules, each with its own
go.mod:.(root),valkey/,aichat/,examples/,examples/uber_demo/,examples/enitity/. Theexamples/*modules are//go:build ignoredemos that are tidied and tested separately — keep theirgo.modtidy or CI's Test job fails.
make build # build ./clicky from ./cmd/clicky (NEVER run `go build` directly)
make test # go test -v ./... (ROOT module only — example modules are separate)
make lint # golangci-lint v2.8.0 (pinned into .bin/) + go vet ./...
make lint-clicky-ui # run clicky-ui's custom oxlint rules against the entity demo webapp
make fmt # gofmt -s -w . and `go mod tidy` across every module in GO_MODULES
make check # fmt + lint + test
make task-ui # rebuild the embedded Preact task-UI bundle (task/ui/dist/taskui.js)
make docs # serve pkgsite Go docs locally on :8089Run a single test (root module):
go test ./formatters/ -run TestReflect # plain go test
ginkgo run --focus "detects bad api.Text" ./lint # Ginkgo suites (preferred)Integration tests are behind the integration build tag:
make test:openapi # builds binary, starts server, runs ./rpc TestOpenAPIServe_E2E
go test -tags integration ./rpc/... -run TestOpenAPIServe_E2ECI (.github/workflows/gavel.yml) runs tests and lint through the flanksource/gavel action, not
bare go test/golangci-lint. The flanksource/dist.yml workflow rebuilds and commits the
embedded task/ui/dist/taskui.js bundle on pushes to main.
- Commits use Conventional Commits with sentence-case subjects, lower-case scopes, no
trailing period, and a ≤100-char header (enforced by
.commitlintrc.json). E.g.feat(exec): detect compiler activity during process startup. - Tests use Ginkgo/Gomega (
*_suite_test.goregister the suites). Colocate unit tests next to source; keep DB/integration tests behind theintegrationbuild tag. - Custom API linter —
lint/is ago/analysisanalyzer (run asclicky lint ./...) that enforces clicky's render-code and entity-registration rules against consumers of thegithub.com/flanksource/clickymodule (the clicky module itself is skipped). Diagnostics carry a severity: errors fail CI, warnings are advisory. The rules are documented under "Writing render code" in CLAUDE.md. - Generated artifacts at the repo root (
out.*,*.pdf,*.html,react.html,*-demobinaries) are gitignored scratch output — not source.task/ui/dist/taskui.jsis committed build output auto-rebuilt by CI; regenerate locally withmake task-ui.
gavel-action-lint-show-passed— the gavel GitHub action can crashgavel lint; run lint as a raw step when that happens.openapi-ansi-leak-bug—rpc/serve.gomust serve theExecutionResponseenvelope for json/yaml and not conflate render format with the HTTP wire format.datafunc-wire-envelope— the envelope substitution above is correct only for stdout-capture commands (payload inmetadata.Output). Entity list/get go throughop.DataFuncwhose payload is indata, so gate the substitution on!ExecutionResponse.DataIsStructured(set true in the executor's DataFunc branch) — otherwise everyGET /api/v1/<entity>returns an empty{success,exit_code,cli}envelope with no rows. Regression:rpc/datafunc_wire_test.go.valkey-nested-module-pin—valkey/is a separate Go module (owngo.mod) taggedvalkey/vX.Y.Zmatching the parent version; the parent proxy zip intentionally contains novalkey/. Consumers requireclickyandclicky/valkeyas two lines; avalkeypseudo-version pointing at a commit predating an API breaksGOWORK=off(Docker/CI) builds withundefined: clickyvalkey.Xeven though a localgo.workcheckout compiles. Cut a realvalkey/vXtag at the commit that has the API, thenGOWORK=off go get github.com/flanksource/clicky/valkey@vX.tree-multiline-label-gutter— lipgloss prefixes every physical line of a multi-lineTreeNodelabel with the│gutter, so blank/ANSI-only separator lines render as empty│-gutter rows. Normalize labels inapi/meta.go normalizeTreeLabel(drop blank lines, ANSI-aware strip) — not informatters/tree_formatter.go, which is a separate renderer.