Skip to content

Pre-commit & CI

VibeXP gates every commit on the same checks CI runs. Installing and respecting the pre-commit hooks is mandatory — it is the fastest way to keep your PR green.

Do this once per clone:

Terminal window
pre-commit install

You need pre-commit on your machine (pipx install pre-commit or brew install pre-commit). If it is missing, install it before committing.

The hooks are configured in .pre-commit-config.yaml. They run only against the relevant files (backend hooks on backend/, frontend hooks on frontend/).

  • gofmt check — code must be gofmt -s clean.
  • golangci-lint — the full linter suite.
  • go vulnerability checkgovulncheck.
  • go security scangosec.
  • OpenAPI validation — validates the spec when openapi.yaml, paths/, or schemas/ change.
  • OpenAPI embedded bundle drift: the runtime-served bundle in internal/server/openapispec/ must be regenerated with the spec.
  • Block os.Getenv — config must go through the config package, not os.Getenv.
  • lint-staged (format & autofix), eslint, type-check (tsc -b), test (Vitest), and build.
  • security scan, dependency audit (on lockfile changes), and a complexity check.
  • gitleaks — secret detection.
  • trailing-whitespace, end-of-file-fixer, check-yaml, check-json, check-added-large-files, check-merge-conflict, check-case-conflict.
  • no-commit-to-branch — blocks direct commits to main.
  • Block nolint comments (backend) and block eslint-disable (frontend) — suppressions are not allowed outside the documented exceptions.
  • no-docs-directory: a top-level docs/ tree is rejected. Documentation lives in the vibexp/docs repo (published at docs.vibexp.io), not here. Package-level README.md files next to the code they describe are the sanctioned exception.

CI pins every analyzer, and the pre-commit hooks invoke whatever binary is on your PATH, so a version skew either way reports failures CI does not. Install the exact versions:

ToolPinned version
Go toolchaingo1.25.12 (GOTOOLCHAIN in the Makefile)
golangci-lintv2.12.2
gosecv2.28.0
govulncheckv1.6.0
mockeryv2.53.6
redocly2.5.0

CI runs the same make targets you run locally, so a clean local run is the best predictor of a green build.

Backend and frontend CI are consolidated into a single ci.yml (issue #390), fanned out into parallel jobs (#638). Its jobs:

JobWhat it does
changesPath filter (dorny/paths-filter) that decides which downstream jobs run.
migrationsPR-only duplicate-migration gate (merge mode against the branch the PR targets); skipped when the PR carries the migration-renumbering label.
unitBackend build plus the untagged test suite (whole module, no Postgres), with coverage. Also gates the config-schema, Wire, and mock drift checks.
integrationThe integration-tagged tests only (internal/repositories/postgres, internal/scheduler, and internal/services/projectmigration, pinned in the Makefile) against a pgvector Postgres service container, with coverage.
securitygovulncheck + gosec (split out of the test job in #638).
lintBackend golangci-lint.
openapiOpenAPI validation plus the embedded-bundle and strict-server drift checks.
frontend-staticFrontend install, lint, dependency audit, and type-check.
frontend-testFrontend Vitest coverage run plus the production build.
sonarSonarCloud scan fed by the unit, integration, and frontend coverage artifacts.

The Go test suite is sharded across unit (untagged) and integration (-tags=integration), and the two halves must stay exhaustive together; make backend-check-integration-shard guards that no integration-tagged file lands in a package outside the pinned list. CI restores a shared Go build cache on every Go job; the unit job is the sole saver (on pushes to main and on a total cache miss).

The go-version in this workflow must stay in sync with GO_VERSION (1.25.12) in the Makefile.

The SonarCloud quality gate is blocking (since #371/#397): a red gate fails CI. Coverage artifacts from both the backend and frontend test jobs feed the scanner.

The production-like end-to-end suite (Playwright) is not wired to PRs — it builds the combined image from source and boots a full stack (Postgres + fake-gcs + the backend serving the embedded SPA), which is too heavy to gate every PR. Run it manually via workflow_dispatch (Actions tab, or gh workflow run ci-e2e.yml -f branch=<ref>) against any branch. It delegates to make e2e, so a green run there means the same make e2e is green locally.

There is one combined artifact and one release workflow, release.yml. Creating a GitHub Release with a vX.Y.Z tag builds the combined image (frontend SPA embedded into the Go backend) and publishes it:

Release tagImage built
vX.Y.Zghcr.io/vibexp/vibexp:X.Y.Z (+ :latest for non-prereleases)

A workflow_dispatch input is available as a manual escape hatch to build from the current ref without a release. The old per-component backend-v* / frontend-v* tags (and their split images) are legacy and no longer released.

Once the image is published, the release workflow’s dispatch-cli-e2e job cross-repo dispatches the VibeXP CLI end-to-end suite against the latest CLI release with the new platform image, and links the run in the job summary. It is dispatch-and-link only: the CLI verdict is never awaited and cannot fail or roll back a release.

Every external GitHub Action referenced with uses: must be pinned to a full 40-character commit SHA, with the human-readable version in a trailing comment. Mutable tag references (@v4, @main) are rejected.

# correct
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# rejected
- uses: actions/checkout@v6

Internal reusable workflows referenced by path (uses: ./.github/...) are exempt.