Skip to content

Latest commit

 

History

History
75 lines (48 loc) · 4.3 KB

File metadata and controls

75 lines (48 loc) · 4.3 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

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 detector

Architecture

sfai 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.

Dependency rules — strictly enforced

  1. pkg/ packages import only other pkg/ packages
  2. internal/ packages import only pkg/ packages — never each other
  3. cmd/ is the only layer that imports both and wires them together

Dependency graph

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)

Key packages

pkg/modelModel interface: Query(Request) (string, error). The LLM abstraction — send a prompt, get a raw string back. No domain knowledge here.

pkg/sfaiSFai 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 /label routes before logging.

Adding a new utility (e.g. coords)

  1. Add label.Record-equivalent types to a new pkg/coords/ package
  2. Add CoordsProc interface to pkg/sfai/sfai.go and Coords CoordsProc field on SFai
  3. Implement in internal/iocoords/ — owns its own system prompt, model options, and response parsing
  4. Wire in cmd/root.go: sfa.Coords = iocoords.New(m, cfg)
  5. Add cmd/coords.go and internal/iohttp/coords.go

Config

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.

CLI input convention

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.