Skip to main content
Cynative is released under the Apache-2.0 license and contributions are welcome. The most impactful areas are new agents, new connectors, evaluation datasets that improve research quality, and fixes across the codebase. Every contribution — code, docs, or data — goes through the same gate.

Prerequisites

Go tooling (golangci-lint, moq, gocyclo, gocognit, goreleaser) is declared in the tool block of go.mod and invoked through make as go tool <name> — do not install them separately. Dependabot keeps them up to date. On a fresh checkout, generate the gitignored mocks before running any package tests:

Development workflow

make check-go is what the pre-commit hook runs. make check is the full gate every PR must pass. The two halves are:
  • check-go — mod-tidy-check + generate + lint + shell-complexity + format-diff + the full race-enabled test suite (-race -shuffle=on) + windows-build (GOOS=windows, amd64 and arm64). 100 % go.mod-pinned.
  • check-scripts — shellcheck over every tracked *.sh, PSScriptAnalyzer over every tracked *.ps1, Pester over every tracked test/*.Tests.ps1, and sh-test (install, connector, render, signing, CI-gate, and llm-smoke suites). Requires python3, PyYAML, jq, shellcheck, Pester, and PSScriptAnalyzer.
Run make check-go before every commit and make check before opening a PR. The pre-commit hook wires make check-go automatically, but only where pre-commit install has been run — a successful git commit proves nothing about the gate unless that hook is installed.

Running a single test

Add -race to match CI. CGO must be available. Run make generate first on a fresh checkout — the moq mock files (*_mock_test.go) are gitignored and packages will not compile without them.

Code conventions

These conventions are enforced by .golangci.yaml (golangci-lint v2) and by the gates: Formatting and style
  • 120-column line limit (golines), gofumpt formatting, goimports with local prefix github.com/cynative/cynative.
  • Every comment ends in a period (godot).
  • No magic numbers (mnd).
  • Function length limit: 100 lines or 50 statements. Cyclomatic complexity ≤ 30, cognitive complexity ≤ 20.
Package structure
  • No init() and no mutable package globals. //nolint:gochecknoglobals is accepted only for immutable lookup tables, stateless singletons, and // test export aliases in export_test.go.
  • Read the process environment only in the composition root and in *_shell.go files. forbidigo bans os.Getenv, os.LookupEnv, os.Environ, and t.Setenv everywhere else. Pass a lookupEnv func into constructors and use that in tests.
Errors
  • Errors wrap with %w.
  • Sentinel errors are named ErrX; error types are named XError (errname enforces both).
Ports and adapters
  • Every external dependency (cloud SDKs, OS, filesystem, network, stdio, terminal, the Bifrost client, the sobek runtime) is a constructor-injected interface or function field, defaulted to the real implementation. Tests swap implementations through With* options in export_test.go, never by reassigning a package variable.
nolint markers
  • Every //nolint must be //nolint:<linter> // reason. A bare marker or a missing reason is rejected by nolintlint (except for funlen, gocognit, and golines, where the reason may be omitted — write it anyway).

Coverage and complexity gates

  • Coverage gate: 100 % statement coverage of all core code (everything except *_shell.go files and internal/auth/authtest). The gate is exact — add a test for every new branch.
  • Shell complexity gate: every function in a *_shell.go file must have cyclomatic and cognitive complexity ≤ 6. Move branchy logic into covered core files rather than raising the budget. //gocyclo:ignore and //gocognit:ignore are rejected.
  • A core error path that no test can reach (a constructor whose real implementation cannot fail) is handled with a factory field defaulted, branch-free, to a *_shell.go function — not with a //nolint.

Adding a new agent

An agent is a markdown file with strict YAML frontmatter (only key: description) followed by the prompt body. The filename is the agent name.
  1. Create one .md file in agents/.
  2. Run make generate to update the embedded agent catalog (docs/agents-catalog.md). The catalog-check in make check-go compares the staged catalog against a fresh render — stage the regenerated catalog alongside your agent file or the gate fails.
  3. Never hand-edit docs/agents-catalog.md.

Adding a connector

A new connector needs:
  1. docs/connectors/<name>.md — a guide covering credential discovery, hardening, limitations, and examples. The connectorDocs roster in internal/auth/connector_docs_test.go must include the new file; a missing entry fails that test.
  2. Test roster entries — add a row to both connectorDocs (connector_docs_test.go) and connectorArgsKeys (connector_args_test.go). If the connector takes per-call arguments, its block in transport.RequestArgs is tagged <name>_auth and that tag is the row’s value; connectors with no block (like github and gitlab) use an empty value.

Branch and PR conventions

Branches
  • Always branch from main using the pattern <type>/<short-description>.
  • Examples: feat/mcp-support, fix/exit-code-no-answer, docs/architecture-page, ci/release-dispatch-retrigger.
  • Direct pushes to main are blocked by a ruleset for everyone except org admins and one GitHub App.
PR titles
  • PR titles must follow Conventional Commits — enforced by CI (Validate PR title).
  • Allowed types: build, chore, ci, deps, docs, feat, fix, perf, refactor, revert, style, test.
  • Use deps: for dependency-only bumps and fix: only for product defects.
  • Mark breaking changes with ! and a BREAKING CHANGE: footer.
Merging
  • PRs are squash-merged with linear history. Keep PRs focused.
  • Required checks — Lint & Test, Validate PR title, and Build & smoke-test macOS packaging toolchain — must pass on an up-to-date branch, every review thread must be resolved, and there must be no open CodeQL alerts before merge.
  • Rebase onto main before requesting a merge; a stale branch cannot merge.
Changelog
  • Never hand-edit CHANGELOG.md or .release-please-manifest.json. release-please generates both from the Conventional Commit PR titles automatically.