Contributing Code
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:
| Branch | Purpose | Deploys To |
|---|---|---|
main | Production | *.case.dev |
preview | Staging/QA | preview.*.case.dev |
| Feature branches | Dev work | Preview 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-previewworkflow 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
- Pick a ticket in Linear
- Branch from preview:
git checkout preview && git pull && git checkout -b agent/CD-xxx-slug - Make changes, commit:
git commit -am "CD-xxx: Fix the thing" - Push and create PR:
git push -u origin HEAD && gh pr create --base preview - After merge to preview, QA on staging (
preview.api.case.dev, etc.) - Wait for human review - agents cannot merge their own work
For multi-part features, use stacked PRs - see using-graphite skill.
Staging URLs
| App | Staging | Production |
|---|---|---|
| Router | preview.api.case.dev | api.case.dev |
| Console | preview.console.case.dev | console.case.dev |
| Thurgood | preview.thurgood.case.dev | thurgood.case.dev |
Workflow States
| State | Meaning |
|---|---|
| Backlog | Ticket created, not yet started |
| In Progress | Agent or human actively working |
| In Review | Work complete, PR open, awaiting review |
| Done | Merged 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
- PR gets approved by reviewer
- Reviewer (or author) adds the
queuelabel - Graphite rebases PR on latest
mainand runs CI - If CI passes, PR auto-merges
- 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.
| Tool | Purpose |
|---|---|
linear_get_issue | Read ticket details |
linear_update_issue | Update status, assignee, etc. |
linear_create_comment | Add comments to ticket |
linear_create_issue | Create new tickets |
linear_list_issues | Find related work |
linear_list_teams | List available teams |
linear_list_projects | List projects in workspace |
linear_list_issue_labels | Get available labels |
linear_list_issue_statuses | Get 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
| Team | Purpose |
|---|---|
| CaseDev | Core API and infrastructure |
| CaseMark | Business and product |
Git Safety Rules
- Never force push to main or preview
- Use
--force-with-leasenot--forcewhen needed - 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:generatebut 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:generateorbun 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
- Stop and assess - List what's in the branch vs. what the ticket asked for
- Propose a split - Recommend breaking the work into focused issues
- 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'],
})
- Plan with the user - Discuss which issue to tackle first, what can be stashed
- 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