This document describes CI/CD best practices that significantly improve the quality and reliability of AI-driven development workflows. When properly configured, Hive Mind AI solvers are forced to iterate with CI/CD checks until all tests pass, ensuring code quality meets the highest standards.
Hive Mind's AI issue solver is instructed to pay attention to CI/CD checks in each pull request. This creates a powerful feedback loop:
- AI creates a solution - The solver generates code based on issue requirements
- CI/CD validates the solution - Automated checks verify code quality
- AI iterates until passing - The solver fixes issues until all checks pass
- Quality is guaranteed - No code merges without passing all gates
This approach ensures consistent quality regardless of whether the team consists of humans, AIs, or both.
We provide ready-to-use templates for multiple languages with all best practices pre-configured:
| Language | Template Repository |
|---|---|
| JavaScript/TypeScript | js-ai-driven-development-pipeline-template |
| Rust | rust-ai-driven-development-pipeline-template |
| Python | python-ai-driven-development-pipeline-template |
| Go | go-ai-driven-development-pipeline-template |
| C# | csharp-ai-driven-development-pipeline-template |
| Java | java-ai-driven-development-pipeline-template |
Only trigger checks when relevant files change. This dramatically reduces CI costs and run times.
Use a detect-changes job at the start of your workflow to determine which file categories changed:
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
code-changed: ${{ steps.changes.outputs.code }}
docs-changed: ${{ steps.changes.outputs.docs }}
docker-changed: ${{ steps.changes.outputs.docker }}
workflow-changed: ${{ steps.changes.outputs.workflow }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Detect changes
id: changes
run: node scripts/detect-code-changes.mjsThen gate each job on the relevant output:
test-suites:
needs: [detect-changes]
if: needs.detect-changes.outputs.code-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true'
# ...
validate-docs:
needs: [detect-changes]
if: needs.detect-changes.outputs.docs-changed == 'true'
# ...
docker-pr-check:
needs: [detect-changes]
if: needs.detect-changes.outputs.docker-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true'
# ...What to exclude from "code changes" detection:
- Markdown files (
*.md) — documentation-only changes don't need changeset files .changeset/folder — changeset metadata isn't codedata/andexperiments/folders — non-production content.gitkeepfiles — placeholder files with no functional impact
What always triggers checks when changed:
- Source code files (
.mjs,.ts,.py,.rs,.go, etc.) package.json/ dependency manifests- CI/CD workflow files (
.github/workflows/*.yml) Dockerfileand related infrastructure files
Enforce a maximum of 1000-1500 lines per code file.
This constraint benefits both AI and human developers:
- AI models can read and understand entire files within context windows
- Humans can navigate and comprehend files without cognitive overload
- Forces modular, well-organized code architecture
Example enforcement in CI (bash):
find src/ -name "*.mjs" -type f | while read -r file; do
line_count=$(wc -l < "$file")
if [ "$line_count" -gt 1500 ]; then
echo "ERROR: $file has $line_count lines (limit: 1500)"
echo "::error file=$file::File has $line_count lines (limit: 1500)"
exit 1
fi
doneSynchronize the file-size ESLint rule with the CI check to catch violations locally before CI:
// eslint.config.mjs
{
rules: {
'max-lines': ['error', { max: 1500 }]
}
}Consistent formatting eliminates style debates and reduces diff noise:
| Language | Tool |
|---|---|
| JavaScript/TypeScript | ESLint + Prettier |
| Rust | rustfmt |
| Python | Ruff |
| Go | gofmt |
| C# | dotnet format |
| Java | Spotless (Google Java Format) |
All templates include pre-commit hooks that run formatters automatically before each commit.
Catch bugs and enforce patterns before code reaches review:
| Language | Tools |
|---|---|
| JavaScript/TypeScript | ESLint with strict rules |
| Rust | Clippy (pedantic + nursery) |
| Python | Ruff + mypy |
| Go | go vet + staticcheck |
| C# | .NET analyzers (warnings as errors) |
| Java | SpotBugs (maximum effort) |
Run fast checks before slow checks to give the fastest possible feedback:
Fast checks (~7-30s each): Slow checks (~1-10 min each):
├── test-compilation ├── test-suites (unit tests)
├── lint (format + ESLint) ├── test-execution (integration)
└── check-file-line-limits ├── docker-pr-check
└── helm-pr-check
Gate slow checks on fast checks:
test-suites:
needs: [test-compilation, lint, check-file-line-limits]
if: |
always() &&
!cancelled() &&
!contains(needs.*.result, 'failure') &&
needs.test-compilation.result == 'success' &&
needs.lint.result == 'success' &&
needs.check-file-line-limits.result == 'success'All templates use a changeset system that:
- Eliminates merge conflicts - Each PR creates an independent changeset file
- Automates version bumps - Highest bump type wins when merging
- Generates changelogs - Release notes are compiled automatically
- Supports semantic versioning - patch/minor/major bumps are explicit
| Language | Tool |
|---|---|
| JavaScript/TypeScript | @changesets/cli |
| Rust | changelog.d + custom scripts |
| Python | Scriv |
| Go, C#, Java | Custom changeset workflows |
Exempt docs-only PRs from changeset requirements:
changeset-check:
needs: [detect-changes]
if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-code-changed == 'true'Documentation-only changes (updating .md files) should not require a version bump.
CI must test what will actually be merged, not a stale PR snapshot.
When a PR is opened against a base branch that later receives new commits, the GitHub merge preview can become stale. Simulate a fresh merge before running checks:
- name: Simulate fresh merge with base branch (PR only)
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
git fetch origin "$BASE_REF"
BEHIND_COUNT=$(git rev-list --count HEAD..origin/$BASE_REF)
if [ "$BEHIND_COUNT" -gt 0 ]; then
git merge origin/$BASE_REF --no-edit || \
(echo "::error::Merge conflict! PR must be rebased before merging." && exit 1)
fiThis ensures lint, file-size, and other checks validate the final merged state.
Local quality gates prevent broken commits from reaching CI:
- Format check and auto-fix
- Lint and static analysis
- Type checking (where applicable)
- File size validation
- Secrets detection
This "shift left" approach catches issues immediately rather than waiting for CI.
Automated release workflows ensure:
- No manual version management - Versions update automatically
- OIDC trusted publishing - No API tokens needed in CI (npm, PyPI, crates.io)
- Validated releases only - All checks must pass before publishing
- Dual trigger modes - Both automatic (on merge) and manual (workflow dispatch)
Prohibit manual version changes in PRs — all version bumps should be managed by the CI release workflow:
version-check:
if: github.event_name == 'pull_request'
steps:
- name: Check for version changes in package.json
run: node scripts/check-version.mjsPrevent multiple workflow runs from conflicting:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Cancel older runs on main to always release the latest version
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}Use !cancelled() instead of always() in job conditions so cancellation propagates correctly through the job graph.
Prevent accidental credential leaks in CI:
- Include a secrets scan step using tools like
secretlintortruffleHog - Fail CI immediately if secrets are detected
- Never log environment variables or token values
Validate documentation files in CI just like code:
- Check file size limits (e.g., max 2500 lines for docs)
- Verify required sections exist in key documents
- Check for broken links using tools like
lychee
validate-docs:
needs: [detect-changes]
if: needs.detect-changes.outputs.docs-changed == 'true'
steps:
- run: node tests/docs-validation.mjsThe templates implement a defense-in-depth approach:
Developer Machine → CI/CD Pipeline → Release
├── Pre-commit hooks ├── detect-changes ├── All checks pass
├── Local tests ├── version-check ├── Version bump
└── IDE integration ├── changeset-check ├── Changelog update
├── test-compilation └── Publish package
├── lint (format+ESLint)
├── check-file-line-limits
├── test-suites
├── test-execution
├── validate-docs
└── docker-pr-check
Each layer catches different issues, ensuring no problematic code reaches production.
- Choose a template from the table above matching your language
- Use it as a GitHub template to create your new repository
- Configure secrets if needed for publishing (OIDC preferred)
- Start developing with all best practices pre-configured
The AI solvers will automatically respect and iterate with all configured checks, producing higher quality output than repositories without CI/CD enforcement.