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.

Quellinformationen

Repository
netdata/ai-viewer
Letzte Quellaktivität
28. Juni 2026 um 23:22
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-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/`.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen