Skip to main content

project-go-backend

Apply Go-specific patterns used in ai-viewer for fsnotify watching, SQLite access, SSE streaming, and structured logging. Use when editing internal/ingest, internal/store, internal/presenter, internal/adapters, internal/canonical, or internal/obs.

Quellinformationen

Repository
netdata/ai-viewer
Letzte Quellaktivität
30. Juni 2026 um 19:36
Erkannte Sprache von SKILL.md
Englisch
Sterne
2
Forks
3

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
project-go-backend
description
Apply Go-specific patterns used in ai-viewer for fsnotify watching, SQLite access, SSE streaming, and structured logging. Use when editing internal/ingest, internal/store, internal/presenter, internal/adapters, internal/canonical, or internal/obs.
# Go Backend Patterns ## Stack Versions Always pin to the latest stable Go release. Update Go toolchain (`go.mod` `go` directive + CI runner) when a new minor release is GA and has been out for 2+ weeks. Dependency policy: prefer stdlib. When a library is needed, prefer the canonical pure-Go option: | Need | Library | |---|---| | File watching | `github.com/fsnotify/fsnotify` | | SQLite | `modernc.org/sqlite` (pure Go, CGO-free, cross-compiles) | | Logging | stdlib `log/slog` | | HTTP routing | stdlib `net/http` + `http.ServeMux` (Go 1.22+ pattern routing) | | UUIDs | `github.com/google/uuid` | | Testing assertions | stdlib `testing`; resist adding testify | | YAML config | `gopkg.in/yaml.v3` | Adding any new dependency requires a one-line note in the SOW under `## Decisions`. ## Concurrency Patterns ### Goroutine ownership Every goroutine has a clear owner who is responsible for starting and stopping it. Document in the constructor godoc: ```go // NewIngester constructs an ingester. The caller MUST call Close to stop // the internal worker goroutines. func NewIngester(...) *Ingester { ... } ``` ### Context propagation - Every blocking function takes `ctx context.Context` as the first parameter. - Every goroutine respects `ctx.Done()` for shutdown. - HTTP handlers use `r.Context()` and pass it through to store/query calls. - Registered REST handlers (`mux.HandleFunc("/api/...", p.<handler>)`) keep their method gate directly visible in the handler body as `r.Method != http.Method...` comparisons. Do not extract the method guard itself into a helper: `scripts/spec-drift.sh` intentionally fails closed when it cannot prove the registered verb/path contract from the handler body. Extract the error writer or response/query work instead. ### Channels - Direction: prefer `<-chan T` / `chan<- T` in function signatures to make ownership explicit. - Buffer sizes: documented in code with a comment explaining the choice and the drop policy when full. - Close: only the sender closes. Document who the sender is. ## SQLite Patterns ### Schema migrations `internal/store/migrations/NNNN_description.sql`. Run in alphabetical order at startup; tracked in `schema_meta`. Migrations are idempotent (`CREATE TABLE IF NOT EXISTS ...`) where possible; non-idempotent migrations are guarded by version checks. ### Prepared statements Hold prepared statements in a package-level `*sql.Stmt` cache initialized once. Re-preparing on every call is forbidden. ### Transactions - Ingest pipeline batches writes in transactions of up to 100 rows or 500 ms, whichever first. - Keep source-scoped derived-table repair chunks at the same 100-row writer budget unless a later SOW proves a larger chunk still leaves Tail heartbeat/stale-tail writes within their 30 s liveness budget. - Server-side reads do not use transactions unless multiple queries must be consistent (rare). - Always `defer tx.Rollback()` immediately after `Begin`; the deferred call is a no-op if `Commit` succeeded. ### Connection pool - Ingester uses single-writer (max-open=1) on the write connection; readers can be a separate pool. - Server uses read-only pool (max-open=8). Tunable later if profiling shows contention. ## fsnotify Patterns ```go w, err := fsnotify.NewWatcher() if err != nil { return err } defer w.Close() if err := w.Add(rootDir); err != nil { return err } // For subdirs created at runtime: re-Add when CREATE event fires on a dir. for { select { case <-ctx.Done(): return ctx.Err() case ev, ok := <-w.Events: if !ok { return nil } handle(ev) case err, ok := <-w.Errors: if !ok { return nil } opts.OnError(err) } } ``` - Use `w.Add(dir)` not file paths. Filter events in `handle`. - Recurse manually: walk the tree once at startup, then `Add` each new dir on `CREATE`. - Atomic rename pattern (`x.tmp` → `x`): listen for `Rename`/`Create` on the target, not `Write`. - Inotify watcher limits: on Linux, default user limit is 8192 watches. If the source tree exceeds, log a clear error pointing to `sysctl fs.inotify.max_user_watches`. ## SSE Patterns stdlib only. Pattern: ```go func sseHandler(w http.ResponseWriter, r *http.Request) { flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "streaming unsupported", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") w.Header().Set("X-Accel-Buffering", "no") // disable nginx buffering sub, err := hub.Subscribe(...) if err != nil { http.Error(...); return } defer hub.Unsubscribe(sub) keepalive := time.NewTicker(15 * time.Second) defer keepalive.Stop() for { select { case <-r.Context().Done(): return case ev := <-sub.Events: fmt.Fprintf(w, "id: %d\nevent: %s\ndata: %s\n\n", ev.ID, ev.Type, ev.JSON) flusher.Flush() case <-keepalive.C: fmt.Fprint(w, ": keepalive\n\n") flusher.Flush() } } } ``` ## Logging Patterns ```go logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) logger = logger.With("subsystem", "ingest") logger.Info("source scan complete", "source_id", srcID, "events", n, "duration_us", dur.Microseconds(), ) logger.Warn("parse error", "source_id", srcID, "file", path, "offset", off, "err", err, ) ``` - Never log raw user content from snapshots (PII concerns). - Always include the subsystem. - Errors at WARN; system failures at ERROR. ## Error Patterns ```go // Wrap with context using %w return fmt.Errorf("scan source %s: %w", srcID, err) // Sentinel errors for control flow var ErrCursorBehind = errors.New("cursor behind oldest available record") // Adapter parse errors call opts.OnError, do not return opts.OnError(fmt.Errorf("parse record at %s:%d: %w", file, off, err)) continue ``` ## Testing Patterns ```go func TestScanHappyPath(t *testing.T) { t.Parallel() fixDir := filepath.Join("testdata", "happy_path") out := make(chan canonical.Event, 100) errs := []error{} a := New(fixDir, canonical.AdapterOptions{ OnError: func(e error) { errs = append(errs, e) }, }) if err := a.Scan(context.Background(), canonical.Cursor{}, out); err != nil { t.Fatalf("scan: %v", err) } close(out) got := drain(out) want := readGolden(t, filepath.Join(fixDir, "expected.jsonl")) if diff := cmp.Diff(want, got); diff != "" { t.Fatalf("events mismatch (-want +got):\n%s", diff) } if len(errs) > 0 { t.Fatalf("unexpected errors: %v", errs) } } ``` - `t.Parallel()` on every test that doesn't share state. - Use table-driven tests for input variations. - Use `cmp` for struct/slice comparisons (clearer diffs than `reflect.DeepEqual`). - Golden files in `testdata/<name>/expected.jsonl`; update with `-update-golden` flag. ## Linting Configuration `.golangci.yml` (v2) enables the strict set — errcheck, govet, ineffassign, staticcheck, unused, errorlint, gocritic, revive, gocyclo (min-complexity 25), misspell, nilerr, prealloc, unconvert, unparam, whitespace, bodyclose, noctx — plus the gofmt/goimports/gofumpt formatters. `gosimple` is folded into staticcheck (v2); `gosec` runs standalone (not as a golangci linter). Zero-warnings policy. The authoritative gate catalog is `.agents/sow/specs/quality-gates.md` "Go — Lint" and the `project-quality-gates` skill; run locally via `scripts/lint.sh`.
Auf GitHub ansehen