Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions .claude/skills/new-feature-setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
name: new-feature-setup
description: Use when starting a new feature, Linear ticket, or bugfix in this repo — establishes the branch + worktree + env + DB + dev-server conventions so the work is immediately ready to code without fighting infra. Triggers on "start a new feature", "spin up a worktree", "begin ticket", "new branch".
---

# New Feature Setup (comp monorepo)

## Overview

This repo has a lot of infrastructure pre-wired into `git worktree add`. Use it. Don't reinvent env copying, database setup, or dependency install flows in every new session.

## When to Use

- User says "start a new feature", "spin up a worktree for X", "begin this Linear ticket", "new branch for …"
- Before running `bun install` / `bun run db:generate` manually in a new directory
- Before copying `.env` files around by hand

## When NOT to Use

- User is editing an existing worktree (already set up)
- Infra repair / debugging of the hook itself (read `.githooks/README.md` instead)

## Workflow

### 1. Create the worktree from `origin/main`

```sh
cd /Users/mariano/code/comp # must be the MAIN clone, not another worktree
git fetch origin main
git worktree add .worktrees/<short-slug> -b <branch-name> origin/main
```

- For Linear tickets, use Linear's suggested branch name (`mariano/<ticket-slug>`).
- For infra / chore work, use `mariano/<short-descriptive-name>` or `chore/<topic>`.
- The `<short-slug>` on the worktree path should match the branch's suffix (it becomes the Postgres DB slug after `tr '-' '_'`).

### 2. Let the hook do everything else

`git worktree add` fires the `post-checkout` hook at `.githooks/post-checkout`, which runs synchronously:

1. Creates `compdev_<slug>` Postgres database (isolated per worktree)
2. Links `.env*` files from the main clone (copies the ones with `DATABASE_URL`, rewriting it to the isolated URL; symlinks the rest so API keys auto-propagate)
3. Runs `bun install`, applies Prisma migrations, regenerates clients

**Do not** run any of these by hand. If the hook logs a failure, diagnose and fix at the source — don't paper over with a manual install.

Skip toggles (rare):
- `SKIP_WORKTREE_DB=1` — share the main `comp` DB (drift risk; only for read-only worktrees)
- `SKIP_WORKTREE_SETUP=1` — skip install + migrate + generate (for a "just files" worktree)
- `SETUP_WORKTREE_WITH_BUILD=1` — also run `bun run build` (adds minutes; only when you need the built artifacts)

### 3. Start the dev server — coordinate with the "active worktree" rule

Trigger.dev's `trigger dev` CLI **cannot** be isolated per worktree. Running `bun run dev` in multiple worktrees stomps on task registration.

- **One active worktree** runs `bun run dev` (full stack with `trigger dev`).
- **All other worktrees** run:
```sh
bun run --filter '@trycompai/app' dev:no-trigger # Next.js only
bun run --filter '@trycompai/api' dev:no-trigger # NestJS only
```
- Non-active worktrees need a different `PORT` to avoid collision — add `PORT=3001` (or `3334`, etc.) to the worktree's `.env.local`. `.env.local` is not symlinked and stays per-worktree.
- When swapping which worktree is active, kill the old full `bun run dev` first so task registration is clean.

### 4. Code the feature

Standard repo conventions apply (see `CLAUDE.md`). Highlights:
- TDD for any non-trivial change (`superpowers:test-driven-development`)
- Brainstorm before building new UX (`superpowers:brainstorming`)
- Plans + subagent-driven execution for multi-step work
- Run `audit-design-system` after any frontend component edit
- Always run typecheck before declaring a change done (`npx turbo run typecheck --filter=<pkg>`)

### 5. When done, clean up

Use the `stale-worktree-cleanup` skill when worktrees accumulate. It handles both `git worktree remove` and dropping the `compdev_<slug>` database in one pass. Never leave orphan databases — they pile up silently because git has no `pre-worktree-remove` hook.

## Quick Reference

```sh
# Spin up a new worktree (does env + DB + install + migrate + generate automatically)
git worktree add .worktrees/<slug> -b mariano/<branch-name> origin/main

# Start dev — ONLY in the worktree you're actively iterating on
cd .worktrees/<slug>
bun run dev

# Start dev in a background worktree (no trigger dev, custom port via .env.local)
echo "PORT=3001" >> apps/app/.env.local
bun run --filter '@trycompai/app' dev:no-trigger

# Clean up when branch is done
# (use the stale-worktree-cleanup skill)
```

## Red Flags

If you catch yourself doing any of these, stop — the hook should have handled it:

- Running `bun install` manually in a new worktree
- `cp` or `ln -s` to copy `.env` files into a worktree
- Writing a script that "creates a database for my branch"
- Running `bun run db:migrate` in a worktree right after creating it
- Ignoring a failing `bun run dev` in two worktrees instead of swapping to `dev:no-trigger`

## Common Mistakes

| Mistake | Fix |
|---|---|
| Creating the worktree from another worktree instead of the main clone | Always `cd` to `/Users/mariano/code/comp` first |
| Editing `.env` in a worktree and expecting it to propagate | If it's a symlink, yes; if it's a real copy (has `DATABASE_URL`), no. Check with `ls -la`. |
| Forgetting to bump `PORT` → two dev servers collide | Put `PORT=<free-port>` in the worktree's `.env.local` |
| Running `trigger dev` in multiple worktrees | Switch to `dev:no-trigger` in all but one |
| Not cleaning up → orphan `compdev_*` databases piling up | Use the `stale-worktree-cleanup` skill regularly |
174 changes: 174 additions & 0 deletions .claude/skills/stale-worktree-cleanup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
name: stale-worktree-cleanup
description: Use when cleaning up old git worktrees, removing worktrees whose branches have merged or been abandoned, or dropping orphaned compdev_* Postgres databases. Triggers on "clean up worktrees", "delete stale worktrees", "worktrees piling up", "orphaned databases", "remove unused worktree".
---

# Stale Worktree Cleanup

## Overview

This repo's `.githooks/post-checkout` creates a per-worktree Postgres database (`compdev_<slug>`) on every `git worktree add`. Git has no `pre-worktree-remove` hook, so dead databases pile up over time. This skill defines a safe, reversible process to reap both the worktree directories and their dangling databases together.

**Core principle**: never delete work the user might still want. Classify first, ask second, remove last.

## When to Use

- User says "clean up worktrees", "delete stale worktrees", "remove old worktrees", "worktrees piling up"
- User mentions orphaned `compdev_*` DBs or running out of disk space from dead `node_modules`
- Starting a new feature and noticing many old worktree dirs

## When NOT to Use

- User wants to remove ONE specific worktree (just run `git worktree remove <path>` + drop its DB directly — don't load the whole process)
- User is actively working in a worktree (never touch active work)

## Process

### Step 1 — Inventory

Run these four commands (parallel-safe) and capture the output:

```sh
git worktree list --porcelain
gh pr list --author @me --state all --limit 50 --json headRefName,state,url,number
git branch --no-color | cat
```

Then query the DB for `compdev_*` databases. The management URL is the main worktree's `DATABASE_URL` with the database path swapped to `postgres`:

```sh
bun -e '
import { Client } from "pg";
const raw = require("fs").readFileSync("packages/db/.env", "utf8");
const url = raw.match(/^DATABASE_URL=(.*)$/m)[1].replace(/^["\x27]|["\x27]$/g, "");
const mgmt = url.replace(/\/[^/?]+(\?|$)/, "/postgres$1");
const c = new Client({ connectionString: mgmt });
await c.connect();
const r = await c.query("SELECT datname FROM pg_database WHERE datname LIKE \x27compdev\\\\_%\x27 ORDER BY 1");
for (const row of r.rows) console.log(row.datname);
await c.end();
'
```

### Step 2 — Classify each worktree

For each worktree (skip the main one):

| Signal | Classification |
|---|---|
| Branch merged to main AND clean working tree AND no unpushed commits | **safe** |
| PR is `CLOSED` (not merged) | **needs-confirm** |
| Uncommitted changes OR unpushed commits | **needs-confirm** |
| No matching PR, no merge, has local commits | **keep** (user may still be working on it) |
| Is the main worktree | **skip** |

Gather per worktree:
- `cd <path> && git status --porcelain | wc -l` — uncommitted changes count
- `cd <path> && git log @{upstream}..HEAD --oneline 2>/dev/null | wc -l` — unpushed commits (0 if no upstream)
- Branch → PR lookup from step 1

### Step 3 — Present to user

Show a table and explicit recommendations. Example:

```
Path Branch PR state Changes Recommendation
.worktrees/sale-45-… mariano/sale-45-… MERGED 0 / 0 ✅ safe to remove
.worktrees/old-experiment mariano/scratch — 3 / 0 ⚠ uncommitted — confirm first
.worktrees/worktree-env-auto-link mariano/worktree-env-auto-link OPEN 0 / 0 ⏳ keep (PR open)

Orphan databases (no worktree dir):
compdev_abandoned_feature
compdev_old_migration_test
```

Then ask: **"Remove the items marked ✅? Confirm by listing anything you want me to also nuke from the ⚠ / ⏳ set."**

### Step 4 — Remove confirmed items

For each worktree the user approved:

```sh
# 1. Remove the worktree dir (use --force only if user confirmed dirty-removal)
git worktree remove "<path>" # clean case
git worktree remove --force "<path>" # only after explicit user OK

# 2. Derive the slug and drop the database
slug=$(basename "<path>" | tr '[:upper:]' '[:lower:]' | tr '-' '_' | tr -cd 'a-z0-9_')
bun -e '
import { Client } from "pg";
const raw = require("fs").readFileSync("packages/db/.env", "utf8");
const url = raw.match(/^DATABASE_URL=(.*)$/m)[1].replace(/^["\x27]|["\x27]$/g, "");
const mgmt = url.replace(/\/[^/?]+(\?|$)/, "/postgres$1");
const c = new Client({ connectionString: mgmt });
await c.connect();
await c.query(`DROP DATABASE IF EXISTS "compdev_'"$slug"'"`);
await c.end();
console.log("dropped compdev_'"$slug"'");
'
```

For orphan databases (no matching worktree dir), just drop them — no worktree to remove.

**Do NOT** delete the local branch unless the user explicitly asked. Branches can be recreated from `origin` cheaply; worktrees cannot.

### Step 5 — Verify and report

```sh
git worktree list
# then re-run the compdev_* query from step 1
```

Report back:
- Worktrees removed (paths)
- Databases dropped (names)
- Anything skipped and why
- What's left (still-active worktrees)

## Safety Rules

- **Never remove the main worktree.** Its path is always the first line of `git worktree list --porcelain`.
- **Never `--force` without confirmation.** Dirty worktrees can contain un-stashed work.
- **Never `DROP DATABASE` on anything not matching `^compdev_[a-z0-9_]+$`.** Never drop `comp`, `postgres`, or any production-looking name.
- **Never delete a local branch** as part of cleanup unless the user explicitly asks. Orphaned branches are cheap; lost work isn't.
- **Never run this inside a worktree you're about to delete.** `cd` to the main worktree first.

## Common Mistakes

| Mistake | Fix |
|---|---|
| Dropping the DB but leaving the worktree dir | Run `git worktree prune` then `git worktree remove` |
| Removing the worktree but leaving the DB (accumulates orphans) | Always do both in the same pass |
| Using hyphens in the DB name | The hook slug rule is `tr '-' '_'` — always underscores |
| Running from inside a doomed worktree | `cd` to the main worktree before starting the process |
| Using `gh pr list` on a branch with no PR | Missing data is not "abandoned" — needs `needs-confirm` classification |

## Red Flags

If any of these show up mid-process, **stop and ask the user**:

- A worktree has unpushed commits AND no PR → might be unreleased work
- The classification returned >10 "safe to remove" items → unusual volume, double-check
- `git worktree remove` errors with "working tree is not clean" → never retry with `--force` without explicit consent
- A `compdev_*` name has weird characters or unexpected format → don't drop

## Quick Reference

```sh
# List everything
git worktree list --porcelain
gh pr list --author @me --state all --limit 50 --json headRefName,state

# Per-worktree inspection
git -C <path> status --porcelain
git -C <path> log @{upstream}..HEAD --oneline 2>/dev/null

# Clean removal
git worktree remove <path>

# Dirty removal (requires user confirmation first)
git worktree remove --force <path>

# Database drop (run from main worktree)
# See the bun -e snippets above for the exact invocation
```
Loading
Loading