# CLAUDE.md — Uptime Sentinel

Project-specific guidance for AI coding agents (Claude Code, Copilot, etc.) working in this repo. Global rules live in `~/.claude/CLAUDE.md`; this file holds Uptime-Sentinel-specific additions and pinned reminders of the rules that bit us in practice.

## Required reading before any work

- [docs/PRD.md](docs/PRD.md) — product spec, shipped capabilities, architecture (topology, monitor engine, encryption-at-rest, CSP/middleware), hardening status, and strategic positioning. The single canonical doc now that the planning/audit notes have been retired.
- **Mandatory skills** for any session touching hardening / security / reliability work — invoke them at the start: `superpowers:executing-plans`, `superpowers:test-driven-development`, `superpowers:verification-before-completion`, `karpathy-guidelines`, `simplify`.

## Branching: long-lived `claude/work` model

Nelson and three other developers each have a **long-lived personal branch** that they work on locally. PRs flow from each developer's branch → `main`. Nelson reviews each PR on GitHub and merges (using "Create a merge commit") to land code on main. After merge, the developer pulls main back into their personal branch and continues.

**My personal branch is `claude/work`.** I do not work on main locally except for `git pull`.

### Per-task flow

```bash
# 1. Sync claude/work with anything that was merged since
git checkout claude/work
git fetch origin
git merge origin/main           # absorb merge commits from other devs

# 2. Code — dev server hot-reloads on claude/work
#    (no extra git steps; just edit and save)

# 3. Commit incrementally as the task progresses
git add <files>
git commit -m "<type>(<scope>): <subject>"

# 4. Upload when ready for review
git push origin claude/work
gh pr create --base main --head claude/work --title "..." --body "..."

# 5. Nelson reviews on GitHub.com and merges (Create a merge commit)
#    (next task: back to step 1)
```

### Parallel work — sub-branches off `claude/work`

GitHub allows only **one open PR per source→target pair**. If `claude/work` already has an open PR awaiting Nelson's review, the next task must use a sub-branch.

```bash
# While claude/work has an open PR, start a new task on a sub-branch:
git checkout claude/work
git checkout -b claude/work-<task-slug>      # e.g. claude/work-p0-6-2fa-token

# Code on the sub-branch (dev server follows)
# Commit, push, open a second PR
git push -u origin claude/work-<task-slug>
gh pr create --base main --head claude/work-<task-slug> --title "..."
```

After Nelson merges the sub-branch, sync `claude/work` and delete the sub-branch:

```bash
git checkout claude/work
git fetch origin
git merge origin/main
git branch -d claude/work-<task-slug>
git push origin --delete claude/work-<task-slug>
```

### Hotfix mid-task

If Nelson pauses me mid-task to do something else with uncommitted work in progress:

```bash
git stash push -m "WIP: <current task>"
git checkout main
git pull origin main
git checkout -b claude/work-hotfix-<slug>    # sub-branch off main, not claude/work
# ... fix, commit, push, PR ...
git checkout claude/work
git stash pop                                # resume original work
```

### Rules

- **Never push or merge to `main` directly.** Local `git merge main` (no push) still counts and is blocked.
- **Default to `claude/work`** for sequential tasks. Only spin up sub-branches when `claude/work` has an open PR.
- **At task start, always `git merge origin/main` into `claude/work`** so it carries the latest merged work. Skipping this risks a PR diff that includes already-merged commits.
- **Sub-branch naming**: `claude/work-<task-slug>` (matches parent namespace; sorts together in `git branch`).
- **Hotfix sub-branches branch off `main`**, not off `claude/work`, so the urgent fix isn't entangled with in-flight work.

## Supply-chain rule (14-day quarantine on new dependencies)

**Do not install any package whose latest published version is less than 14 days old.** This applies to `npm install <pkg>`, `npm install --save <pkg>`, and any manual lockfile addition. The rule exists because:

- Typosquatted and account-takeover packages typically get caught and yanked within hours-to-days
- Lifecycle scripts (`postinstall`, etc.) run with developer privileges on install — a malicious package compromises the dev machine before any code review
- A 14-day window lets the security community surface obviously-malicious releases first

**Process before adding a new dep:**

```bash
npm view <pkg> time --json | tail -5    # check publish date of latest version
```

- If latest version is ≥ 14 days old → proceed; announce the version + publish date in your PR.
- If latest version is younger than 14 days → pin to an explicitly-older version (`npm install pkg@X.Y.Z`) or pause and ask.

This rule has **no exception for "the user named the package"** — even named packages get typosquatted variants and account takeovers. The 14-day window is for the package itself, not for the agent's confidence in the name.

Applies to all `dependencies` AND `devDependencies`. Does NOT apply to routine `npm install` / `npm ci` that doesn't change the lockfile.

## Commit message convention

`<type>(<scope>): <subject>` — match the existing `git log` style.

Examples already in the history: `security(P0-2): ...`, `feat(P2-10): ...`, `chore(security): ...`, `docs(env): ...`. For hardening tasks, scope is the plan tag (`P0-12`, `P1-13`, etc.).

Do **not** add `Co-Authored-By` trailers that name fabricated model versions — the auto-mode classifier blocks them. Plain `Co-Authored-By: Claude <noreply@anthropic.com>` is fine, or omit entirely.

## Smoke runner is the regression gate

Before opening any PR, run:

```bash
npx tsx scripts/smoke/run-all.ts
```

New smoke scripts go in `scripts/smoke/<tag>-<num>-<slug>.ts` and MUST be registered in `scripts/smoke/run-all.ts` in the same commit as the smoke file. CI doesn't run an unregistered smoke — coverage gap silently lands.

## Pitfalls we keep hitting

These eleven patterns burned hours during the 2026-05 hardening sprint. Scan them before opening multi-PR work.

1. **Register the smoke in `run-all.ts` in the SAME commit** — easy to forget; coverage gap silently lands.
2. **`run-all.ts` cascade conflicts are structural** — every PR after the first to merge needs a `git merge origin/main` resolve.
3. **Schema files (`schema.prisma`, `package.json`, `Dockerfile`, `monitor-engine.ts`, etc.) cascade the same way** — list file overlaps in PR descriptions so the reviewer can sequence merges.
4. **`actions/checkout@v4` auth flake** — masquerades as `npm audit` failure. Re-run before treating as real. Diagnose with `gh run view <id> --log-failed | grep "exit code 128"`.
5. **ESM-only packages break Jest** — `p-limit@4+`, `chalk@5+`, etc. Check `npm view <pkg> type` before installing. Inline tiny utilities (see [src/lib/utils/bounded-pool.ts](src/lib/utils/bounded-pool.ts)) over Jest config wrangling.
6. **Tests that assert against version/envelope contracts** — when you add `v2:` support, search for tests asserting the literal `'v2:...'` was unrecognised.
7. **Destructive DB migrations** (DROP/RENAME/CHANGE TYPE) do NOT run against the live dev DB until merge — established after PR-35's jsonPath drop broke every other in-flight branch.
8. **`npx prisma generate` fails on Windows** when the dev server holds the DLL lock. Use `npx prisma validate` to syntax-check; defer regen to CI or a server restart.
9. **`git checkout origin/main`-based sub-branches start from main's state** — files from your previous branch are NOT present. Re-read before editing.
10. **`git add -A` sweeps unrelated untracked files** (e.g. one-shot maint scripts) into feature commits — prefer `git add <specific-files>`.
11. **GitHub auto-merge silently drops parallel list-line additions** — two PRs adding new lines to the same anchor produce no conflict marker; 3-way merge picks one side. After any auto-merge from main, grep the affected list for entries from PRs that landed since you branched. Structural smokes (e.g. `audit2-3` walking `prisma/migrations/` against the runbook) catch this; visual review doesn't.

## Environment

- Node 24
- `.env` is gitignored (committed in history pre-2026-05-21; see `.env.example` for the full required-key list)
- `.env.example` is the template — every Zod-validated key must have a placeholder there
- `validateEnv` (P0-8) throws unconditionally in every NODE_ENV; there is no dev fallback

## Dev-server troubleshooting

If you hit a wall of `Unexpected end of JSON input` / `Failed to fetch` errors in the browser across many unrelated endpoints, **the dev server's lazy-compile cache is wedged** (this happens with Next.js 16 Turbopack when many modules change rapidly, e.g., after a `prisma generate` + `npm install` cycle while the server was running). Recovery:

```powershell
# Ctrl+C the dev server, then:
Remove-Item -Recurse -Force .next
npm run dev
```

After restart, **let the dev server warm up by visiting `/dashboard` once and waiting ~30s** before browsing further. Turbopack lazy-compiles routes on first request; parallel cold-start requests for many endpoints can produce truncated responses on Next 16.2.x.

The `[NotificationWorker] Initializing... Listening for events.` log should print **exactly once per dev-server lifetime**. If you see it firing on every page load, that's a regression of the fix in `instrumentation.ts` — module init was moved there to dodge HMR re-evaluation churn.

## Memory hooks

The auto-memory at `~/.claude/projects/<project>/memory/` is per-project. `MEMORY.md` lists what's relevant for Uptime Sentinel — refresh it when you learn new user preferences or project state.
