This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
just test # run all tests
just build # build to bin/sfai (with version/build timestamp)
just install # install to ~/go/bin/sfai
just verify # fmt + tidy + test + build
go test ./... # run all tests
go test ./cmd/... -run TestLabelCmd # run a single test
go test -count=1 -race ./... # tests with race detectorsfai is a Go CLI that exposes LLM-backed biodiversity informatics utilities via both a CLI and a REST API. The LLM backend is currently stubbed; real Ollama (and later ONNX) wiring comes later.
pkg/packages import only otherpkg/packagesinternal/packages import onlypkg/packages — never each othercmd/is the only layer that imports both and wires them together
cmd/ → internal/io*, pkg/*
internal/iohttp → pkg/sfai, pkg/config
internal/iolabel → pkg/model, pkg/sfai, pkg/label, pkg/config
internal/iomodel → pkg/config
pkg/sfai → pkg/label
pkg/model (no deps — pure interface)
pkg/label (no deps — domain types only)
pkg/config (no deps)
pkg/model — Model interface: Query(Request) (string, error). The LLM abstraction — send a prompt, get a raw string back. No domain knowledge here.
pkg/sfai — SFai struct (aggregator built in cmd/root.go) and processor interfaces (LabelProc). Adding a new utility means adding a new interface here and a field on SFai.
internal/iomodel — stub Model implementation. Replace with Ollama client here.
internal/iolabel — owns everything label-specific: system prompt, model options (temperature: 0.0), model.Query call, and response parsing into label.Record. One() processes a single label; Many() fans out using a sliding window of size cfg.BatchWindow; the dispatcher runs cfg.Jobs parallel model workers.
internal/iohttp — HTTP server built on huma v2 over stdlib http.ServeMux. The only API endpoint is GET /label?text=...; one label per request, clients manage their own concurrency. Label size is capped at 4096 bytes by the schema (maxLength struct tag) and may be tightened further at runtime by cfg.MaxLabelBytes; oversize requests get 422 (schema) or 400 (config). Takes *sfai.SFai and *config.Config; graceful shutdown on SIGINT/SIGTERM.
Auto-served pages and spec artifacts (huma defaults, no code needed): GET / is a hand-written HTML home page; GET /docs is an HTML page rendering Stoplight Elements against the spec; GET /openapi.{json,yaml} and /openapi-3.0.{json,yaml} are the OpenAPI 3.1 / 3.0 specs.
Adding an endpoint with huma: define Input and Output structs, then huma.Register(api, huma.Operation{...}, handlerFunc). Validation is automatic from struct tags. See internal/iohttp/label.go as the canonical example.
Deployment privacy note: GET puts label text in the request URL, which means it lands in reverse-proxy access logs (nginx, CDNs, etc.). If labels may contain sensitive locality or collector data, configure the proxy to redact the query string for
/labelroutes before logging.
- Add
label.Record-equivalent types to a newpkg/coords/package - Add
CoordsProcinterface topkg/sfai/sfai.goandCoords CoordsProcfield onSFai - Implement in
internal/iocoords/— owns its own system prompt, model options, and response parsing - Wire in
cmd/root.go:sfa.Coords = iocoords.New(m, cfg) - Add
cmd/coords.goandinternal/iohttp/coords.go
Persistent config lives in ~/.config/sfai/config.yaml. Precedence: CLI flags > env vars (SFAI_*) > config file > defaults. config.ToOptions() round-trips the persistent fields. HomeDir and runtime-only values are never persisted.
sfai label resolves input like gnparser: file path argument → read lines from file; non-path argument → treat as single label text; no argument → read from stdin pipe. One label per line; embed literal \n for in-label newlines. Output is JSONL.