Contributing Code

development

Guides the contribution workflow: branches, commits, PRs. Use when starting work on Linear tickets, creating branches, writing commits, or submitting PRs. Covers branch naming (agent/CD-xxx-slug), commit format (CD-xxx: description), and git safety. Related: `using-linear` for ticket management.

Contributing

This skill covers the AI-native development workflow and contribution standards for the Case.dev monorepo. All work is tracked in Linear and follows strict conventions.

The Golden Rule

Everything starts in Linear, everything ends with a reviewed merge.

No code changes should happen without a corresponding Linear ticket.

Git Workflow

Use regular git for most work. Use gt (Graphite) only when stacking multiple PRs.

# Standard workflow
git checkout preview && git pull
git checkout -b agent/CD-xxx-slug
# make changes
git add . && git commit -m "CD-xxx: Do the thing"
git push -u origin HEAD
gh pr create --base preview

For big features that need multiple PRs, see using-graphite skill for stacking.

Preview-First Workflow

CRITICAL: All development work happens on preview, not main.

We use a preview-first flow with staging environments:

BranchPurposeDeploys To
mainProduction*.case.dev
previewStaging/QApreview.*.case.dev
Feature branchesDev workPreview deployments

Before Starting Any Work

Ensure preview is not behind main:

git fetch origin

# Check if main has commits that preview doesn't
git log origin/preview..origin/main --oneline

# If this shows ANY commits, preview is behind - do NOT start work
# If empty, you're good to go
  • Empty output = Safe to start work (preview is even with or ahead of main)
  • Commits shown = Wait for sync-preview workflow to run

Note: It's normal for preview to be ahead of main - that's expected (staging has unreleased work). The problem is only when main is ahead of preview.

Development Flow

  1. Pick a ticket in Linear
  2. Branch from preview: git checkout preview && git pull && git checkout -b agent/CD-xxx-slug
  3. Make changes, commit: git commit -am "CD-xxx: Fix the thing"
  4. Push and create PR: git push -u origin HEAD && gh pr create --base preview
  5. After merge to preview, QA on staging (preview.api.case.dev, etc.)
  6. Wait for human review - agents cannot merge their own work

For multi-part features, use stacked PRs - see using-graphite skill.

Staging URLs

AppStagingProduction
Routerpreview.api.case.devapi.case.dev
Consolepreview.console.case.devconsole.case.dev
Thurgoodpreview.thurgood.case.devthurgood.case.dev

Workflow States

StateMeaning
BacklogTicket created, not yet started
In ProgressAgent or human actively working
In ReviewWork complete, PR open, awaiting review
DoneMerged to main (automatic via GitHub integration)

Note: Linear automatically moves issues to Done/Merged when the PR is merged to main. You do NOT need to manually update the status after merge.

Branch Naming

Agent Work

agent/<LINEAR-ID>-<slug>

Human Work

feature/<LINEAR-ID>-<slug>
fix/<LINEAR-ID>-<slug>

Examples

agent/CD-119-root-level-agent-skill
feature/CD-120-add-new-endpoint
fix/CD-121-auth-redirect-loop

Getting Branch Name from Linear

Linear provides a suggested branch name in every issue:

  • Open the issue
  • Look for "Git branch name" in the sidebar
  • Use that exact name

Commit Messages

Format

<LINEAR-ID>: <imperative description>

Good Examples

CD-119: Add database-operations skill
CD-120: Fix auth redirect loop in console
CD-121: Update vault upload endpoint for large files

Bad Examples

fix stuff                          # No ticket ID
CD-119 - Updated the thing         # Use colon, be specific
CD-119: updated files              # Not imperative

Imperative Mood

Write commits as commands:

  • "Add feature" not "Added feature"
  • "Fix bug" not "Fixed bug"
  • "Update docs" not "Updated docs"

Starting Work

Start Work Checklist:
- [ ] Verify preview is not behind main: git fetch origin && git log origin/preview..origin/main --oneline (must be empty)
- [ ] Get ticket details: linear_get_issue(id: "CD-xxx")
- [ ] Update status: linear_update_issue(id: "CD-xxx", state: "In Progress")
- [ ] Branch from preview: git checkout preview && git pull && git checkout -b agent/CD-xxx-slug
- [ ] Make changes
- [ ] Commit: git add . && git commit -m "CD-xxx: Description"

Commits

Every commit message must start with the ticket ID:

git commit -m "CD-119: Add database-operations skill"

Submitting Work

Submit Work Checklist:
- [ ] (Optional) Run `/simplifying-code` if code grew complex during iteration
- [ ] Push and create PR: git push -u origin HEAD && gh pr create --base preview
- [ ] Update Linear status: linear_update_issue(id: "CD-xxx", state: "In Review")

PRs target preview, NOT main.

Agents cannot merge their own work. A human must review and merge.

Merging PRs

Reviewers merge PRs via GitHub. No special labels required.

How It Works

  1. PR gets approved by reviewer
  2. Reviewer (or author) adds the queue label
  3. Graphite rebases PR on latest main and runs CI
  4. If CI passes, PR auto-merges
  5. If CI fails, PR is removed from queue (fix and re-add label)

For Stacked PRs

When you add queue to any PR in a stack:

Pre-Merge Compliance Check

Before submitting work for review, run SOC2 compliance verification:

hive run verify-soc2-compliance "Review changes in this branch"

This checks your code against SOC2 controls including:

  • No secrets/credentials committed
  • Environment variables for sensitive config
  • Input validation on API boundaries
  • Authentication/authorization checks present
  • Error handling doesn't leak sensitive data
  • Dependencies are pinned and reviewed

See .hive/agents/verify-soc2-compliance.md for the full checklist.

PR Template

Use assets/pr-template.md for consistent PR descriptions (this is under .skills/contributing/assets/).

Linear MCP Tools

The Linear MCP is configured in .cursor/mcp.json and .claude/settings.json. See configuring-mcps skill for setup details.

ToolPurpose
linear_get_issueRead ticket details
linear_update_issueUpdate status, assignee, etc.
linear_create_commentAdd comments to ticket
linear_create_issueCreate new tickets
linear_list_issuesFind related work
linear_list_teamsList available teams
linear_list_projectsList projects in workspace
linear_list_issue_labelsGet available labels
linear_list_issue_statusesGet workflow states for a team

Examples

// Get issue details
linear_get_issue({ id: 'CD-119' })

// Update status
linear_update_issue({ id: 'CD-119', state: 'In Progress' })

// Add comment
linear_create_comment({
  issueId: 'CD-119',
  body: 'Started work on this. Creating branch...',
})

// Create new issue
linear_create_issue({
  title: 'Fix auth bug',
  description: 'Details...',
  team: 'CaseDev',
})

// List issues assigned to me
linear_list_issues({ assignee: 'me', state: 'In Progress' })

Teams

TeamPurpose
CaseDevCore API and infrastructure
CaseMarkBusiness and product

Git Safety Rules

  1. Never force push to main or preview
  2. Use --force-with-lease not --force when needed
  3. Pull before pushing to stay current with preview

Discovered Issues Pattern

When working on a ticket and you discover an unrelated bug or issue:

Create a separate blocking issue instead of fixing it silently in the same branch.

When to Create a Separate Issue

Create a new issue if the discovered problem:

  • Is in a different app/package than your current work
  • Is conceptually unrelated to your ticket's scope
  • Would make your PR harder to review (mixed concerns)
  • Should be tracked separately for metrics/history

When to Fix Inline

Fix directly in your current branch if:

  • It's a typo or trivial fix (< 5 lines)
  • It's directly related to your ticket's scope
  • Separating it would create unnecessary overhead

How to Create a Blocking Issue

// 1. Create the new issue
linear_create_issue({
  title: 'Fix router auth middleware null check',
  description: 'Discovered while working on CD-123. The middleware crashes when...',
  team: 'CaseDev',
  labels: ['bug'],
  blocks: ['CD-123'], // Links as "blocks" the original issue
})

// 2. Add context to original issue
linear_create_comment({
  issueId: 'CD-123',
  body: 'Discovered router bug that blocks this work. Created CD-124 to track the fix.',
})

Branch Strategy for Blocking Issues

Two options:

Option A: Stacked PRs (recommended with Graphite)

  • Create blocking fix as a stacked PR
  • Your original work stacks on top
  • Both PRs can be reviewed together or separately
# On your current branch, create the fix as a new stacked PR
gt create -am "CD-124: Fix router auth middleware null check"
# Continue with your original work
gt create -am "CD-123: Add console dashboard feature"
# Submit the whole stack
gt submit --stack

Option B: Separate independent branches

  • Move blocking fix to separate branch off preview
  • Fix, PR, merge independently
  • Sync your original branch

Use Option B only if the fix is truly unrelated and shouldn't stack.

Why This Matters

  • Traceability: "Why did this code change?" has a clear answer
  • Atomic PRs: Each issue = one focused change
  • Better reviews: Reviewers see related changes, not surprises
  • Metrics: Bugs found during development are tracked separately

Stop and Ask

Stop working and ask the human if:

  • Ticket requirements are ambiguous or contradictory
  • You've discovered a bug that blocks the ticket but is in a different area
  • Your changes have grown beyond the ticket scope (see "Handling Scope Creep" below)
  • You need to modify shared packages (@case/*) — ripple effects across apps
  • The ticket involves user-facing copy or UX decisions
  • You're unsure whether something is a bug or intended behavior
  • Tests are failing and you don't know if your change caused it
  • Database schema changes require bun db:generate but it's asking interactive questions — a human must run it
  • You're tempted to write SQL migration files by hand — STOP, this is forbidden

Don't ask, just proceed if:

  • Ticket is clear and your changes match the scope
  • You're following an established pattern from this skill
  • Fix is mechanical (typo, import path, etc.)
  • Creating a blocking issue for a discovered bug (that's the right move)

Scope Guidelines

Small Scope (Quick Changes)

  • Single file changes
  • Bug fixes with clear reproduction
  • Documentation updates
  • Simple refactors

Large Scope (Careful Changes)

  • Multi-file features
  • Database schema changes
  • API changes
  • Anything requiring human judgment

Required Skill Routing

Some changes require loading specialized skills BEFORE making any changes. This is mandatory, not optional.

Database Schema Changes → managing-database

CRITICAL: If your work involves ANY of the following, you MUST load the managing-database skill FIRST:

  • Adding/modifying tables in packages/database/src/schema*.ts
  • Adding/modifying enum values (like eventTypeEnum, planTierEnum, etc.)
  • Running bun db:generate or bun db:migrate
  • Creating database migrations

Why this matters: Database migrations are irreversible in production. Hand-written SQL files or manual journal edits have caused multi-day outages. The managing-database skill contains critical rules that MUST be followed.

The #1 rule: NEVER write SQL migration files by hand. NEVER edit _journal.json manually. Always use bun db:generate.

Load skill: managing-database

Shared Package Changes → modifying-shared-packages

If your work modifies packages/* (database, provider-neon, etc.), load modifying-shared-packages first.

API Route Changes → creating-api-routes

If adding or modifying routes in apps/router/server/routes/, load creating-api-routes first.

Handling Scope Creep

If you notice a branch is growing beyond its original ticket scope (touching multiple unrelated areas, solving multiple problems), stop and address it with the user.

Signs of Scope Creep

  • Changes span multiple apps/packages unrelated to the ticket
  • You're fixing "while I'm here" issues
  • The PR would be hard to review because it mixes concerns
  • Commit messages reference different problem domains

What To Do

  1. Stop and assess - List what's in the branch vs. what the ticket asked for
  2. Propose a split - Recommend breaking the work into focused issues
  3. Create new tickets using Linear MCP:
// Create focused issues for each concern
linear_create_issue({
  title: 'Refactor auth middleware error handling',
  description: 'Split from CD-123. Focused on...',
  team: 'CaseDev',
  labels: ['refactor'],
})

linear_create_issue({
  title: 'Fix console sidebar layout on mobile',
  description: 'Split from CD-123. Addresses...',
  team: 'CaseDev',
  labels: ['bug'],
})
  1. Plan with the user - Discuss which issue to tackle first, what can be stashed
  2. Create clean branches - One branch per issue, each with atomic changes

Example Conversation

"I notice this branch is growing beyond the original scope. We're now touching:

  • Router auth middleware (original ticket)
  • Console sidebar styling (unrelated)
  • Database query optimization (unrelated)

I recommend we split this into 3 focused issues. Want me to create them in Linear and we can decide which to tackle first?"

Why This Matters

Monster branches with mixed concerns:

  • Are hard to review (reviewers miss bugs)
  • Are hard to revert (one bad change = revert everything)
  • Make git history confusing ("why did auth change in a sidebar PR?")
  • Block other work (can't merge the good parts without the risky parts)

Focused branches = faster reviews, safer merges, cleaner history.

Responsibilities

Agent Responsibilities

  • Stay within ticket scope
  • Make atomic, meaningful commits
  • Update Linear status appropriately
  • Never merge directly to main
  • Ask for clarification if requirements are unclear
  • Load required skills before specialized work (database, API routes, shared packages)
  • NEVER write SQL migration files by hand — always use bun db:generate

Owner Responsibilities

  • Review all agent work before merge
  • Ensure quality meets standards
  • Can reject/close inappropriate tickets
  • Final authority on what gets merged

Danger Zone