Skip to main content

project-quality-gates

Catalog of every automated quality gate ai-viewer enforces — commands, thresholds, and what to do when a gate fails. Use before claiming any work done, before any commit, when adding a new gate, or when investigating a CI failure. The runtime companion to .agents/sow/specs/quality-gates.md.

Source facts

Repository
netdata/ai-viewer
Last source activity
June 28, 2026 at 23:22
Detected SKILL.md language
English
Stars
2
Forks
3

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
project-quality-gates
description
Catalog of every automated quality gate ai-viewer enforces — commands, thresholds, and what to do when a gate fails. Use before claiming any work done, before any commit, when adding a new gate, or when investigating a CI failure. The runtime companion to .agents/sow/specs/quality-gates.md.
# Quality Gates ## Operating Rule Every gate listed here runs in CI on every push — except the benchmark regression gate (`scripts/check-bench.sh`), which is a local/workstation gate (its baseline is not comparable to CI-runner hardware; CI runs the bench compile-smoke + the gate's hardware-independent self-test). The assistant runs them all locally before reporting work done. If a gate fails: fix the root cause. **Never weaken a gate to make it pass.** Lowering a threshold or marking a test skipped to land a PR is a contract breach. **nolint policy** (two cases, both require a reason — never a bare `//nolint`): - **Deferred-fix suppression** (the finding is real but fixing it now is out of scope): forbidden unless the active SOW justifies it AND the directive links the tracking issue/SOW — `//nolint:rule // <reason>; see SOW-XXXX`. - **Permanent-architectural suppression** (the finding is a verified false-positive for a deliberate, durable pattern — e.g. `nilerr` on a `return nil` that intentionally lets a stricter downstream check win, or on poll-loop rotation-tolerance): allowed with a `//nolint:rule // <reason>` stating *why the pattern is correct*. No tracking link is required because there is nothing to fix later. The reason must be specific; "false positive" alone is not enough. Prefer a config-level exclusion (with a YAML rationale comment) when a whole file or rule class is involved. Bare/unexplained `//nolint` is always a contract breach. When a new pattern emerges that warrants enforcement, add it here AND to `.agents/sow/specs/quality-gates.md` AND to CI in the same commit. ## Gate Catalog ### Go — Module Tidiness ```bash go mod tidy git diff --exit-code go.mod go.sum ``` Threshold: zero diffs. `scripts/lint.sh` mirrors the CI `lint` job and runs this before formatter/linter/security checks. A stale module graph is a local gate failure, not a CI-only surprise. ### Go — Formatting ```bash gofmt -l <tracked Go files> # zero unformatted tracked files expected goimports -l <tracked Go files> # zero unformatted tracked files expected ``` Threshold: zero diffs. `scripts/lint.sh` runs standalone `gofmt` and `goimports@latest` exactly like CI before `golangci-lint`; both surfaces derive the input set from `git ls-files -z -- '*.go'`, so ignored/untracked local files such as `frontend/node_modules/**` cannot cause local-only formatter failures. An empty tracked Go file list is fail-closed. Golangci's formatters are defense in depth. Auto-fix locally with `gofmt -w` and `goimports -w` before commit. `scripts/test/lint-test.sh` is the hermetic self-test for the tracked-file formatter contract; it plants ignored/untracked malformed Go and must fail if the formatter gate regresses to walking `.`. ### Go — Vet ```bash go vet ./... ``` Threshold: zero warnings. ### Go — Lint (golangci-lint) ```bash golangci-lint run --timeout=5m # umbrella: also enforces fmt + vet ``` golangci-lint is **v2** (`.golangci.yml` has `version: "2"`). `golangci-lint run` is the umbrella gate — with the formatters enabled it enforces Go — Format, and the `govet` linter covers Go — Vet, so a clean `run` means fmt+vet+lint all pass. (In golangci-lint v2, `golangci-lint run` REPORTS formatter violations — gofmt/goimports/gofumpt — as failures; `golangci-lint fmt` is the separate auto-fix path. `run` enforces formatting; it is not merely a linter-only pass. Note: `--enable-only <formatter>` is rejected because formatters are configured under `formatters:`, not enabled as linters — this does not mean `run` skips them.) Run it locally via `./scripts/lint.sh` after the module-tidiness, standalone `gofmt`, standalone `goimports@latest`, and `go vet` checks that mirror the CI `lint` job. The version is pinned in `.golangci-lint-version` (single source for CI + `scripts/lint.sh`); CI runs it via `golangci/golangci-lint-action` at that pinned version, and local `scripts/lint.sh` fails when the installed binary cannot be parsed or differs from the pin. `.golangci.yml` enables linters: `errcheck`, `govet`, `ineffassign`, `staticcheck`, `unused`, `errorlint`, `gocritic`, `revive`, `gocyclo`, `misspell`, `nilerr`, `prealloc`, `unconvert`, `unparam`, `whitespace`, `bodyclose`, `noctx`; formatters: `gofmt`, `goimports`, `gofumpt`. NOT enabled: `gosimple` (v2 merged it into `staticcheck`) and `gosec` (runs standalone — see Go — Security — to avoid duplicate analysis). Tuning (rationale in the `.golangci.yml` inline comments): `gocyclo` uses `min-complexity: 25` (the stream parsers/scanners/tailers and the SOW-0007 ingest event loop legitimately sit at 16–24; 15 is false-positive churn on reviewed hot-path code). `_test.go` is excluded from the style/complexity linters (`gocyclo`, `noctx`, `unparam`, `prealloc`, `revive`, `gocritic`) but NOT from the bug-finders. `frontend/node_modules` is path-excluded (a transitive npm dep ships a non-project Go file). Threshold: zero warnings. ### Go — Security ```bash gosec -severity medium -confidence medium ./... govulncheck ./... ``` Threshold: zero high/critical findings from gosec; zero known vulnerabilities from govulncheck. Govulncheck runs in CI on a schedule plus every push so newly-disclosed CVEs surface fast. **GOTCHA — standalone pinned `gosec` ≠ golangci's bundled gosec.** CI's "Go — Security" step and `scripts/lint.sh` both install and run a **pinned standalone** `gosec@v2.26.1`; that version ships newer analyzers (e.g. **G705** XSS-taint) that golangci-lint's older *bundled* gosec does not have (which is why `gosec` is NOT enabled as a golangci linter — it runs standalone). So `golangci-lint run` returning 0 does NOT mean `gosec` passes — they are different gates. Always run the standalone `gosec -severity medium -confidence medium ./...` locally (install the pin: `go install github.com/securego/gosec/v2/cmd/gosec@v2.26.1`, or just run `./scripts/lint.sh`, which installs/verifies the pinned gosec + runs golangci + govulncheck) AND `govulncheck ./...` before pushing — not just `gofmt`/`vet`/`golangci-lint`/`test`/`build`. (govulncheck exits 0 when a required-but-uncalled module has a CVE — "your code doesn't appear to call" — that is a pass.) **Suppressions:** gosec honors the **hash** form `// #nosec G705 -- justification` (NOT `//nosec`). Per the Operating Rule, any `#nosec`/`//nolint` MUST be a verified false positive AND justified in the active SOW. Prefer restructuring to the gosec-clean pattern of a sibling handler over suppressing; suppress only when the finding is provably impossible (e.g. body is trusted embedded build output served on an exact-match route). ## Verifying CI Before PR Merge (do not trust `--watch` exit code) When GA or explicit-PR flow is active, branch protection may still have no required status checks before SOW-0013's post-merge setup runs. After setup, the required contexts are recorded in `.github/workflows-checks.yaml` and `docs/setup.md`. In both states, never gate a merge on `gh pr checks <pr> --watch` alone: it fails only on required checks, so a non-required red row can still leave `--watch` green. Before `gh pr merge`, run `gh pr checks <pr>` and confirm EVERY row reads `pass` (no `fail`/`pending`). ### Go — Tests ```bash go test -race -count=1 ./... ``` Threshold: all pass with race detector enabled. `-count=1` defeats test cache (CI must run fresh). ### Go — Coverage ```bash go test -race -coverprofile=coverage.out -covermode=atomic ./... go tool cover -func=coverage.out | tail -1 ``` Thresholds: - **Statement** coverage (Go has no native branch coverage; branch is deferred). Gated set = every `internal/*` package (gated **iff** the import path contains `/internal/` and not `/cmd/`): each ≥ 80% AND their aggregate ≥ 80%. Excluded (reported, not gated): `/cmd/` (binaries + nested dev tools) and any non-`internal/` path, e.g. vendored Go a frontend npm dep ships under `frontend/node_modules/`. - New code in the PR: ≥ 90% lines — **deferred (SOW-0036)**, not yet enforced (see Enforcement note below). Enforcement: `scripts/check-coverage.sh coverage.out` fails if any gated (`internal/*`) package or the gated aggregate is < 80% statements; it runs as a build-failing CI step (the `test` job) and as the local pre-commit gate, with synthetic-fixture self-tests (`scripts/test/check-coverage-test.sh`). New-code-in-PR ≥ 90% is deferred to a follow-up SOW (diff↔coverage intersector). ### Go — Fuzzing ```bash go test -run='^Fuzz' ./internal/adapters/... # per-push: seed corpus, deterministic go test -run='^$' -fuzz='^FuzzParseSnapshot$' -fuzztime=5m ./internal/adapters/aiagent_v2/ # nightly: explore (one target per pkg) ``` Every adapter parser exposes at least one `FuzzXxx` target (10 across the 5 adapter packages). `internal/canonical` has **no** fuzz target — it owns no parsers/decoders. CI per-push runs the seed corpus deterministically (`-run='^Fuzz'`, PR-blocking); `fuzz-nightly.yml` runs `-fuzz -fuzztime=5m` per target (non-blocking, uploads any crash reproducer). Commit a found reproducer under `testdata/fuzz/<Target>/` to make it a deterministic per-push regression. Auto-filing a GitHub issue on crash is a deferred follow-up. Threshold: zero crashes per run. A seed-corpus crash blocks merge. ### Go — Ingestion Parity ```bash scripts/test/check-ingestion-parity-test.sh # hermetic wrapper self-test scripts/check-ingestion-parity.sh --fixtures # source-vs-canonical parity fixture gate go test -count=1 ./internal/parity ./internal/ingest ./cmd/ai-viewer-ingest -run 'Parity|Source|Manifest|Diff|Canonical|Matrix|CheckParity' go test -run='^Fuzz' ./internal/parity # deterministic parity fuzz seed corpus ``` SOW-0097 gate. The wrapper is the named local/CI fixture gate over the parity source extractors, canonical extractor, diff engine, ingest parity tests, matrix drift tests, `ai-viewer-ingest check-parity` CLI tests, and `internal/parity` fuzz seeds. It exists so future agents run one durable gate instead of remembering scattered package-level commands. Threshold: exit zero only when every fixture parity check is clean. Missing wrapper/script, invalid wrapper mode, missing required parity package, malformed fixture, source extractor error, canonical extractor error, source/canonical mismatch, unverifiable artifact, matrix drift or open matrix gap, parity fuzz seed crash, or failing CLI mismatch test is a gate failure. Live local diagnostic entry point: ```bash ai-viewer-ingest check-parity --db /opt/ai-viewer/data/index.db --source <adapter:path> ... ``` The live full gate is workstation-only because it reads private local sources. Do not publish live manifests or raw payload bodies. Until SOW-0097 closes, the advanced live controls (streaming manifests, snapshot mutation detection, resume, sample mode, timeout, memory cap) remain open requirements, not a reason to claim full live parity done. ### Go — Benchmarks ```bash scripts/check-bench.sh # count=6, -cpu=1, -p=1, benchstat vs bench/baseline.txt, > 20% sec/op gate scripts/test/check-bench-test.sh # hardware-independent self-test of the gate's benchstat parser ``` Marked benchmarks (`func BenchmarkXxx`) exist for the 11 performance-critical paths: ai-agent v2 adapter `Scan`, ai-agent v2 adapter `Tail`, claude-code adapter `Scan`, claude-code adapter `Tail`, Codex adapter `Scan`, Codex adapter `Tail`, Opencode adapter `Scan`, Opencode adapter `Tail`, SQLite batch insert, REST query path, SSE fanout. (No canonical encode/decode benchmark — canonical events are constructed directly, never serialized.) Threshold: a statistically-significant **> 20% sec/op** regression for any individual benchmark fails `scripts/check-bench.sh` (the `geomean` aggregate + custom `ReportMetric` values are not gated; benchstat's `~` neutralizes noisy benchmarks). In real local/workstation mode, the same benchmark name must regress on the script's second benchmark attempt before the gate exits red; a first-run-only regression, or disjoint first/second-attempt regression sets, is reported as local measurement noise and exits green. The retry is not a threshold change: both attempts compare against the same checked-in `bench/baseline.txt` with the same `-count=6`, `-cpu=1`, `-p=1`, and parser, while compare-file mode remains single-pass so the self-test still proves a real >20% regression exits non-zero. The script runs the serial hot-path suite with `go test -p=1 -run=^$ -bench=. -benchmem -count=6 -cpu=1`; do not remove `-cpu=1` or `-p=1` unless a later explicit SOW proves the suite should measure multi-P or cross-package scheduler behavior. `-cpu=1` pins the benchmark binary's Go benchmark CPU list; `-p=1` serializes package benchmark binaries so the gate does not create cross-package contention. Real benchmark attempts fail closed with exit 2 before sampling when the 1-minute host load is at or above `0.50 * effective_GOMAXPROCS`, when `/proc/loadavg` is unavailable or malformed, or when the effective CPU divisor cannot be resolved. Passing and failing runs emit compact diagnostics (Go version, effective `GOMAXPROCS`, benchmark CPU setting, package parallelism, busy-host threshold, package list, baseline/current paths, and load averages) without process command lines. It is a **local/workstation** gate — `bench/baseline.txt` is workstation-measured (carries benchmark-code provenance: an implementing commit SHA when available, or a same-commit `git blame` note when benchmark code and baseline land together, plus the exact command and `goos/goarch/pkg/cpu` config lines) and is not comparable to GitHub-runner hardware, so CI runs only the bench compile-smoke + the gate self-test, not the regression gate itself. Baseline refresh requires an explicit SOW (no auto-update); SOW-0058 owns the original `-cpu=1` baseline refresh. Fail-closed benchmark-gate behavior is part of the contract: missing, empty, or benchmarkless baseline, missing/empty current output, failed `go test`, failed `benchstat`, dropped/renamed baseline benchmark, disjoint benchmark config groups, busy-host preflight failure, unavailable/malformed loadavg source, and unavailable effective CPU divisor all exit non-zero. The self-test must dynamically exercise both compare-file parser behavior and real-mode retry/error behavior with hermetic fakes, including disjoint first/second-attempt regression sets and busy-host preflight cases; static string assertions alone are not enough for the retry path. CI's `Require benchmarks` compile-smoke presence check must normalize both suffixed Go benchmark rows (`BenchmarkName-N`) and unsuffixed `-cpu=1` rows (`BenchmarkName`) from `bench/baseline.txt`, then compare those logical names against the implemented `func BenchmarkXxx` set. The benchmark self-test must assert this parser contract, because a workstation baseline refresh can otherwise pass local gates and fail only in CI. Gated benchmarks must isolate the intended hot path from helper-goroutine scheduler noise. For serial production paths (for example SSE `Hub.Deliver` fanout), use deterministic buffering/pre-seeding in the fixture instead of background helper goroutines inside the timed environment; otherwise the local workstation gate can fail on unchanged code under ordinary desktop/VM load. For very small serial hot paths, a benchmark operation may run a deterministic fixed batch to amortize timer/scheduler noise, but it must keep reporting the per-hot-path-unit metric (for example deliveries/sec) for human interpretation. ### Go — Race + Stress ```bash go test -race -count=10 ./... # local pre-commit on concurrency-touching changes ``` For changes to ingest pipeline, SSE hub, or anything with channels/goroutines: run `scripts/test.sh --stress 10` locally; CI runs `-count=1` per push (the `test` job) and `-count=10` race stress nightly (`race-stress-nightly.yml`). ### Frontend — Lint ```bash cd frontend && npm run lint # the `lint` script bakes in --max-warnings=0 ``` ESLint flat config with `@typescript-eslint`, `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-jsx-a11y`, `eslint-plugin-import`. Threshold: zero warnings (the `lint` npm script owns `--max-warnings=0`; `scripts/lint.sh` and CI run plain `npm run lint`). ### Frontend — Type Check ```bash cd frontend && npm run typecheck # invokes `tsc --noEmit` ``` `tsconfig.json` enforces: `strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noImplicitOverride`, `noFallthroughCasesInSwitch`, `noUnusedLocals`, `noUnusedParameters`. Threshold: zero errors. ### Frontend — Unit/Component Tests ```bash cd frontend && npm run test -- --run --coverage # REAL gate (run by scripts/test.sh after the Go suite, and in CI) cd frontend && npm run check:coverage-config # verify the REAL per-dir floors (RAW-canonical shape + non-vacuity + BIDIRECTIONAL lockstep + disk-completeness + whole-dir include shape) cd frontend && npm run check:coverage-thresholds:selftest # hermetic gate-MECHANISM self-test (throwaway fixture) ``` Vitest + React Testing Library. Threshold: all pass; global aggregate floor (≥ 80% lines/stmts/funcs, ≥ 75% branches) **plus a per-directory ≥ 80% lines floor** for every measured dir under `src/components/` and `src/pages/`.
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub