ZeroStarter

Code Quality

Git hooks, linting, formatting, and commit conventions.

Overview

ZeroStarter enforces code quality through automated tools that run on every commit. This ensures consistent code style, catches errors early, and maintains a clean commit history.

Tools

ToolPurpose
LefthookGit hooks manager
lint-stagedRun linters on staged files
OxlintFast JavaScript/TypeScript linter
OxfmtFast code formatter
CommitlintCommit message linter

Git Hooks

Git hooks are configured in lefthook.yml:

pre-commit:
  piped: true
  commands:
    lint-staged:
      run: bunx lint-staged --verbose
      stage_fixed: true
    build:
      run: bun run build
      use_stdin: true

commit-msg:
  commands:
    commitlint:
      run: bunx commitlint --edit {1}

pre-push:
  commands:
    audit:
      run: bun audit --audit-level high
      only:
        - ref: canary
    ensure-main:
      run: bun .github/scripts/ensure-remote-main.ts {1}

Pre-commit Hook

Runs before each commit in sequence (piped):

  1. lint-staged: Format and lint only staged files. The command runs with stage_fixed: true, so any files the formatter rewrites are re-staged automatically and the fixes are included in the same commit.
  2. Build: Verify the project builds successfully

Pre-push Hook

Runs before git push. The security audit (bun audit --audit-level high) runs here, scoped to canary (only: ref: canary), so it checks for vulnerable dependencies before the branch leaves your machine without blocking offline commits. It also runs ensure-main (.github/scripts/ensure-remote-main.ts), on GitHub remotes only. Your first git push origin canary creates canary, which GitHub makes the default branch; on your next push the hook seeds main (a separate ref, so it never collides with the canary push) so auto-canary-into-main opens the release PR. It needs no gh or repo-admin (the default branch falls out of push order); the only manual step it prints is enabling read-write Actions permissions. A local per-remote git config zerostarter.mainSeeded.<remote> marker makes it a no-op once done.

Audit overrides (AUDIT.md)

bun audit --audit-level high is the gate that decides whether a vulnerable dependency blocks a push. When an advisory can only be resolved with a dependency overrides entry in package.json (for example a transitive package that has no patched release yet), that override must be recorded in AUDIT.md at the repo root.

AUDIT.md is the canonical record of every active override. Each entry documents the advisory, why an override is needed, the residual risk, and the exit criteria for removing it. The file is intentionally kept even when there are no active overrides, so do not delete it.

Commit-msg Hook

Validates commit messages follow conventional commit format.

Lint-Staged Configuration

The .lintstagedrc.json defines what runs on staged files:

{
  "*": ["oxfmt --no-error-on-unmatched-pattern", "oxlint --no-error-on-unmatched-pattern"],
  "package.json": ["bun .github/scripts/deps-manager.ts"]
}
  • All files: Format with Oxfmt, then lint with Oxlint
  • package.json: Run dependency manager script (ensures consistent dependency versions)

Formatting with Oxfmt

Oxfmt configuration in .oxfmtrc.jsonc:

{
  "$schema": "./node_modules/oxfmt/configuration_schema.json",
  "semi": false,
  "experimentalSortImports": {},
  "experimentalTailwindcss": {},
  "ignorePatterns": ["**/*.lock", ".agents/**", ".claude/**"],
}
SettingValueDescription
semifalseNo semicolons (like Prettier with semi: false)
experimentalSortImports{}Auto-sort imports
experimentalTailwindcss{}Sort Tailwind CSS classes
ignorePatterns["**/*.lock", ".agents/**", ".claude/**"]Skip lock files and AI skill directories

Manual Formatting

# Format all files
bun run format

# Check formatting without changes
bun run format:check

Linting with Oxlint

Oxlint runs automatically via lint-staged on staged files at commit time. To lint the entire monorepo in a single pass, run bun run lint (this is also what CI runs):

# Lint the entire tree
bun run lint

Oxlint is significantly faster than ESLint while catching common issues.

Oxlint configuration in .oxlintrc.jsonc:

{
  "$schema": "./node_modules/oxlint/configuration_schema.json",
  "ignorePatterns": ["**/*.lock", ".agents/**", ".claude/**"],
}
SettingValueDescription
ignorePatterns["**/*.lock", ".agents/**", ".claude/**"]Skip lock files and AI skill directories

Commit Message Convention

Commits must follow Conventional Commits:

<type>(<scope>): <subject>

<body>

Types

Commitlint extends @commitlint/config-conventional, which accepts these 11 Conventional Commits types:

TypeDescription
featNew feature
fixBug fix
docsDocumentation only
styleCode style (formatting, semicolons)
refactorCode change that neither fixes a bug nor adds a feature
perfPerformance improvement
testAdding or updating tests
choreMaintenance (dependencies, build scripts)
ciCI/CD changes
buildBuild system or external dependency changes
revertReverting a previous commit

Examples

feat(auth): add Google OAuth provider
fix(api): handle null user in session middleware
docs(readme): update installation instructions
chore(deps): bump dependencies to latest versions
refactor(web): extract form validation into hook

Breaking Changes

Append ! after type for breaking changes:

feat!: remove deprecated API endpoints

Running Checks Manually

# What CI runs (auto-check-build.yml). CI exports NODE_ENV=production and
# SKIP_ENV_VALIDATION=true so the build runs before real secrets are present.
NODE_ENV=production SKIP_ENV_VALIDATION=true bun audit --audit-level high
NODE_ENV=production SKIP_ENV_VALIDATION=true bun run lint
NODE_ENV=production SKIP_ENV_VALIDATION=true bun run build

# Type checking (run locally; not part of the CI build workflow)
bun run check-types

# Format all files
bun run format

Unlike CI, the local pre-commit build step runs without SKIP_ENV_VALIDATION, so it validates your real .env. A missing or invalid required variable will fail the commit locally even though the same build passes in CI.

CI/CD Integration

GitHub Actions runs these checks on every PR:

# .github/workflows/auto-check-build.yml
- name: Audit, Lint, and Build
  run: |
    bun audit --audit-level high
    bun run lint
    bun run build
  env:
    NODE_ENV: production
    SKIP_ENV_VALIDATION: true

Skipping Hooks

In rare cases, you can skip hooks:

# Skip all hooks (use sparingly!)
git commit --no-verify -m "emergency fix"

# Skip all Lefthook hooks (LEFTHOOK=0 disables every hook, not just one)
LEFTHOOK=0 git commit -m "message"

# Skip a single command, e.g. just the build step
LEFTHOOK_EXCLUDE=build git commit -m "message"

Only skip hooks for emergencies. CI will still catch issues.

Troubleshooting

Hooks Not Running

# Reinstall hooks
bunx lefthook install

Format Conflicts

If formatting creates unexpected changes:

# Format entire codebase
bun run format

# Stage formatted files
git add .

Commitlint Failing

Check your message follows the format:

# Good
git commit -m "feat: add user profile page"

# Bad (missing type)
git commit -m "add user profile page"

# Bad (wrong type)
git commit -m "added: user profile page"