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—shellcheckover every tracked*.sh, PSScriptAnalyzer over every tracked*.ps1, Pester over every trackedtest/*.Tests.ps1, andsh-test(install, connector, render, signing, CI-gate, and llm-smoke suites). Requirespython3, 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
-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),gofumptformatting,goimportswith local prefixgithub.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.
-
No
init()and no mutable package globals.//nolint:gochecknoglobalsis accepted only for immutable lookup tables, stateless singletons, and// test exportaliases inexport_test.go. -
Read the process environment only in the composition root and in
*_shell.gofiles.forbidigobansos.Getenv,os.LookupEnv,os.Environ, andt.Setenveverywhere else. Pass alookupEnvfunc into constructors and use that in tests.
- Errors wrap with
%w. - Sentinel errors are named
ErrX; error types are namedXError(errnameenforces both).
- 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 inexport_test.go, never by reassigning a package variable.
- Every
//nolintmust be//nolint:<linter> // reason. A bare marker or a missing reason is rejected bynolintlint(except forfunlen,gocognit, andgolines, 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.gofiles andinternal/auth/authtest). The gate is exact — add a test for every new branch. - Shell complexity gate: every function in a
*_shell.gofile must have cyclomatic and cognitive complexity ≤ 6. Move branchy logic into covered core files rather than raising the budget.//gocyclo:ignoreand//gocognit:ignoreare 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.gofunction — 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.
- Create one
.mdfile inagents/. - Run
make generateto update the embedded agent catalog (docs/agents-catalog.md). Thecatalog-checkinmake check-gocompares the staged catalog against a fresh render — stage the regenerated catalog alongside your agent file or the gate fails. - Never hand-edit
docs/agents-catalog.md.
Adding a connector
A new connector needs:docs/connectors/<name>.md— a guide covering credential discovery, hardening, limitations, and examples. TheconnectorDocsroster ininternal/auth/connector_docs_test.gomust include the new file; a missing entry fails that test.- Test roster entries — add a row to both
connectorDocs(connector_docs_test.go) andconnectorArgsKeys(connector_args_test.go). If the connector takes per-call arguments, its block intransport.RequestArgsis tagged<name>_authand that tag is the row’s value; connectors with no block (likegithubandgitlab) use an empty value.
Branch and PR conventions
Branches- Always branch from
mainusing the pattern<type>/<short-description>. - Examples:
feat/mcp-support,fix/exit-code-no-answer,docs/architecture-page,ci/release-dispatch-retrigger. - Direct pushes to
mainare blocked by a ruleset for everyone except org admins and one GitHub App.
- 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 andfix:only for product defects. - Mark breaking changes with
!and aBREAKING CHANGE:footer.
- PRs are squash-merged with linear history. Keep PRs focused.
- Required checks —
Lint & Test,Validate PR title, andBuild & 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
mainbefore requesting a merge; a stale branch cannot merge.
- Never hand-edit
CHANGELOG.mdor.release-please-manifest.json. release-please generates both from the Conventional Commit PR titles automatically.

