Contributing
Guide
How to set up your development environment, follow our code standards, write tests, and get your pull request merged.
Getting Started
Fork the repository, clone your fork, and set up a local development environment before making any changes.
# 1. Fork on GitHub, then clone your fork git clone https://github.com/YOUR_USERNAME/nxtg.ai.git cd nxtg.ai # 2. Install dependencies pnpm install # 3. Copy environment template and configure cp .env.example .env.local # Fill in NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY # 4. Start the dev server pnpm dev
The site will be available at http://localhost:3000. See the Self-Hosting Guide for full environment configuration details.
Development Workflow
All contributions follow the same branching and review process. No commits go directly to main — all changes require a pull request.
Create a feature branch from main
git checkout -b feat/your-feature-name
Branch names should be lowercase with hyphens. Prefix with feat/, fix/, docs/, or test/ matching the commit type.
Make your changes
Write the implementation and tests together. See the Code Style and Testing sections below for standards.
Run the test suite (must pass 100%)
pnpm test
All tests must pass. Test count must not decrease from the previous commit. Zero failures is the only acceptable result.
Run the linter
pnpm lint
ESLint must report zero errors. Warnings are acceptable if they are pre-existing, but do not introduce new ones.
Commit with a conventional commit message
git commit -m "feat: add docs contributing page"
Include the test count in your commit message: “Tests: X passed, Y skipped”. See the Conventional Commits section below.
Push and create a pull request
git push origin feat/your-feature-name
Open a PR against the main branch. Fill in the PR template with a summary and test plan.
Conventional Commits
All commit messages must follow the Conventional Commits specification. The prefix determines how the change appears in the changelog and release notes.
| Prefix | Use for |
|---|---|
feat: | A new feature or page |
fix: | A bug fix |
docs: | Documentation changes only |
test: | Adding or updating tests |
refactor: | Code change that neither fixes a bug nor adds a feature |
chore: | Dependency updates, build changes, config |
cos: | ASIF CoS directive chain commit (governance) |
feat: add self-hosting documentation page (Tests: 32 passed) fix: correct Clerk middleware redirect URL for /os route docs: update CLAUDE.md with new admin console sections test: add missing metadata tests for docs pages
Code Style
Consistency matters more than personal preference. Follow these rules exactly — ESLint and TypeScript will catch most violations automatically.
TypeScript strict mode
All files must pass tsc --noEmit. No implicit any. No type assertions without justification. The type system is the first line of correctness.
Use cn() for conditional classes
Import cn from @/lib/utils for all conditional Tailwind class merging. Never concatenate class strings manually — it breaks tailwind-merge conflict resolution.
Prefer named exports
Components, utilities, and types should use named exports. Default exports are reserved for page.tsx files (Next.js App Router convention).
use client only when needed
Server components are the default. Add 'use client' only when you need browser APIs, event handlers, or React hooks (useState, useEffect, etc.). Keep client components small and push state to the leaves.
Follow shadcn/ui patterns
New UI components should be built on Radix UI primitives and styled with Tailwind, matching the patterns in src/components/ui/. Use CSS variables for colors — never hardcoded hex values.
data-testid on all interactive elements
Every interactive element (buttons, links, inputs, modals) needs a data-testid attribute. Convention: {page}-{section}-{element}. Example: docs-contributing-cta-link.
data-testid convention
// Page: docs, Section: contributing, Element: cta-link data-testid="docs-contributing-cta-link" // Page: landing, Section: hero, Element: cta-primary data-testid="landing-hero-cta-primary" // Page: os, Section: window, Element: terminal-header data-testid="os-window-terminal-header"
Testing Requirements
This project uses vitest for static analysis tests. Tests live in tests/app/ and perform file-level assertions against page source — verifying metadata, data-testid attributes, imports, and key content.
Every new page needs a test file
When you create a new page at src/app/foo/page.tsx, create a corresponding test at tests/app/foo.test.ts. No exceptions. A page without tests will not be merged.
import { describe, it, expect } from 'vitest'
import { readFileSync, existsSync } from 'fs'
import { join } from 'path'
describe('Foo page', () => {
const pagePath = join(process.cwd(), 'src/app/foo/page.tsx')
it('should exist', () => {
expect(existsSync(pagePath)).toBe(true)
})
const content = readFileSync(pagePath, 'utf-8')
describe('Metadata and SEO', () => {
it('should have export const metadata', () => {
expect(content).toContain('export const metadata: Metadata')
})
it('should have title', () => {
expect(content).toContain('title:')
})
it('should have openGraph', () => {
expect(content).toContain('openGraph')
})
it('should have twitter card', () => {
expect(content).toContain('summary_large_image')
})
it('should have canonical URL', () => {
expect(content).toContain('canonical:')
})
it('should have JSON-LD', () => {
expect(content).toContain('application/ld+json')
})
})
describe('Navigation', () => {
it('should import Navbar', () => {
expect(content).toContain("from '@/components/ui/navbar'")
})
it('should import Breadcrumbs', () => {
expect(content).toContain("from '@/components/ui/breadcrumbs'")
})
})
describe('Page structure', () => {
it('should have page-level data-testid', () => {
expect(content).toContain('data-testid="foo-page"')
})
it('should not have client directive', () => {
expect(content).not.toContain('use-client-directive')
})
})
})CRUCIBLE Gate 4
Test count must never decrease between commits. If you remove a feature, remove the feature code and its tests together — but the overall test count after your PR must be equal to or greater than before your changes. This is enforced by CRUCIBLE Gate 4 in the ASIF governance protocol.
Minimum test coverage per page
Pull Request Process
PR title format
feat: add architecture documentation page fix: correct Clerk redirect for /os route after token expiry docs: update self-hosting guide with PM2 instructions test: add contributing page static analysis tests
Keep the title under 72 characters. Use the imperative mood. The prefix must match the conventional commit type for the primary change.
PR description template
## Summary - What this PR does in 1-3 bullet points - Why this change is needed ## Test plan - [ ] pnpm test — all passing (X tests) - [ ] pnpm lint — zero errors - [ ] tsc --noEmit — zero type errors - [ ] New tests added for new files - [ ] data-testid on all interactive elements ## Files changed - src/app/... — purpose - tests/app/... — purpose
Review requirements
Related Documentation
Ready to contribute?
Fork the repository on GitHub and submit your first pull request.
View on GitHub