diff --git a/.claude/settings.json b/.claude/settings.json index 946b4c3..0967ef4 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,13 +1 @@ -{ - "enabledPlugins": { - "ponytail@ponytail": true - }, - "extraKnownMarketplaces": { - "ponytail": { - "source": { - "source": "github", - "repo": "DietrichGebert/ponytail" - } - } - } -} +{} diff --git a/.claude/skills/audit-to-tickets/SKILL.md b/.claude/skills/audit-to-tickets/SKILL.md index bd8f6ba..8c00ccd 100644 --- a/.claude/skills/audit-to-tickets/SKILL.md +++ b/.claude/skills/audit-to-tickets/SKILL.md @@ -27,15 +27,19 @@ They go to local files (step 8) for the user to file as security advisories. already covered by an open issue gets that number, not a new issue. 4. **Draft tickets.** Merge findings that one fix closes. Order them so a ticket comes after anything it depends on (a refactor before the fix that - uses it). Each ticket fits one PR. + uses it). Each ticket fits one PR. Name its seams: the public boundaries + its tests observe (a route, the WebSocket, an exported function), each + with one line on what it catches. `cut:` tickets with no behaviour + change: the existing tests that must stay green. 5. **Quiz the user** with the draft, numbered: ``` . [<labels>] (findings <N>, <N>) blocked by: <T|none> + seams: <seam — what it catches>; … ``` Ask: is the granularity right (merge or split?), are the blocking edges - right, anything to drop? Iterate until the user approves. Nothing is + and seams right, anything to drop? Iterate until the user approves. Nothing is published before that. 6. **Labels.** Create `audit` if missing: `gh label create audit --color 5319e7 --description "Found by /project-audit"`. @@ -67,6 +71,9 @@ Where: path:line ## What to build <the Fix from the report, plus anything the grill decided> +## Seams +- <seam>: <what it catches> + ## Acceptance criteria - [ ] <observable outcome, e.g. "POST /x without session → 401"> - [ ] test covering it (for bug/load/test findings) diff --git a/.claude/skills/change-review/SKILL.md b/.claude/skills/change-review/SKILL.md new file mode 100644 index 0000000..ca4e28e --- /dev/null +++ b/.claude/skills/change-review/SKILL.md @@ -0,0 +1,118 @@ +--- +name: change-review +description: > + Checks a CertShell branch against its issue / SPEC.md and the repo's + CLAUDE.md rules. Use when the user says "change-review", "/change-review", + "does this match the issue", "review against the spec", "review since X", + or when `/implement` or `/implement-spec` calls it. Separate axes in + parallel sub-agents: Standards (CLAUDE.md, CONTRIBUTING.md, code smells), + Spec (missing, unasked or wrong), Security (`security-reviewer`, when + sensitive paths changed). Default base `origin/main`. Not a bug hunt (that's + the built-in `/code-review`). Reports findings, applies nothing. +--- + +Review `git diff <base>...HEAD` on separate axes, side by side: + +- **Standards**: does the code follow this repo's documented rules? +- **Spec**: does it do what the issue or SPEC.md asked, no more, no less? +- **Security**: does it hold SPEC.md §3, §5, §6? Only when sensitive paths changed. + +A change can pass one axis and fail another. Each axis runs in its own +sub-agent so one can't mask the other. + +Adapted from [mattpocock/skills](https://github.com/mattpocock/skills). +Copyright (c) 2026 Matt Pocock, MIT License: see the upstream +[LICENSE](https://github.com/mattpocock/skills/blob/main/LICENSE). + +## Steps + +1. **Pin the range.** Base = whatever the user named (SHA, branch, tag, + `HEAD~3`), else `origin/main`. `git fetch -q origin` (if it fails, warn + and use local refs). Resolve both ends once: `B=$(git merge-base <base> HEAD)`, + `H=$(git rev-parse HEAD)`; every axis reviews `git diff $B..$H`, so all + see the same tree. Empty diff or bad ref → fail here, not in the + sub-agents. If `git status --porcelain` shows changes, continue and list + those paths as not reviewed. Note `git log $B..$H --oneline`. +2. **Find the spec**, in order: + - `#N` / `Closes #N` in commit messages or the branch's PR body + (`gh pr view --json body`): `gh issue view <N>`, plus its parent + (`gh api repos/{owner}/{repo}/issues/<N>/parent`) if it has one. + - A path or `.audit/<ts>/NN-slug.md` ticket the user passed. + - The plan agreed in this conversation. + - Nothing → ask. "No spec" → skip the Spec axis and say so. + SPEC.md is always context for the Spec axis. +3. **Security axis needed?** Yes if the diff touches `apps/api/src/`, + `Dockerfile`, `compose*.yaml` or `.github/workflows/`. +4. **Spawn the axes in parallel**: launch all of them in one message and + wait for every report before writing the output. Each prompt carries the + diff command with both SHAs, commit list, its brief below, its line + format from Output pasted in literally (sub-agents can't see this file), + and: "Read-only: edit, stage and commit nothing." Can't spawn sub-agents + → run the axes one after another yourself and say isolation was reduced. +5. **Report** (see Output). Don't merge, dedupe or re-rank across axes. + +## Standards brief + +Sources: `CLAUDE.md`, `CONTRIBUTING.md`, the neighbouring code's patterns. +The list below is a reminder; where it differs from CLAUDE.md, CLAUDE.md wins. +Skip anything Biome (`bun run lint`) or `tsc` enforces. A documented repo +rule beats the smells below; where the repo endorses something a smell +would flag, drop the smell. + +Repo rules worth checking by hand: + +- Tests come with every feature and fix, in `apps/*/test/` or `apps/web/e2e/`, ending `.test.<ext>`. +- No `find`/`grep` in tests (Bun `Glob` instead). +- Comments only where the next reader would otherwise spend several tool calls; a paragraph justifying a workaround means the code is wrong. +- Nothing changed beyond what was asked. +- One logical change per commit. +- Design change → SPEC.md updated in the same branch. + +Smells (always `judgement`, never `hard`): Mysterious Name, Duplicated +Code, Feature Envy, Data Clumps, Primitive Obsession, Repeated Switches, +Shotgun Surgery, Divergent Change, Speculative Generality, Message Chains, +Middle Man, Refused Bequest (Fowler, _Refactoring_ ch. 3). Name the smell, +quote the hunk, give the fix. + +Brief: "Report every breach of a repo rule (`hard`, cite file + rule) and +every smell (`judgement`), one line each in the format below. Under 400 words." + +## Spec brief + +Pass the spec contents (issue body, ticket file, or agreed plan) and SPEC.md. + +Brief: "Report (a) asked-for requirements missing or partial, (b) behaviour +in the diff nobody asked for, (c) requirements that look done but are +wrong. Quote the spec line for each (`spec: none` for unasked behaviour), +one line each in the format below. Under 400 words." + +## Security axis + +Launch the `security-reviewer` agent on `$B..$H`. Rewrite each of its +findings into the Security line format, keeping its severity. + +## Output + +``` +## Standards +<N>. [hard|judgement] <rule or smell>: <defect>. <fix>. [path:line] + +## Spec +<N>. [missing|unasked|wrong] <defect>. <fix>. [path:line] (spec: "<quote>" | none) + +## Security +<N>. [CRIT|HIGH|MED|LOW] <defect>. <fix>. [path:line] +(or: not run: no sensitive paths changed) +``` + +Number findings across the whole report, Security included, so the user +can say "fix 2 and 5". End with one line per axis: +`<axis>: <count> findings, worst: #<N>` (or `clean`), where worst is the +finding number of the most serious one in that axis: `hard` > `judgement`; +`wrong` > `missing` > `unasked`; CRIT > HIGH > MED > LOW. Never pick a +winner across axes. + +## Boundaries + +Reviews, applies nothing. Bug-hunting is the built-in `/code-review`; +whole-repo audits are `/project-audit`. diff --git a/.claude/skills/implement-spec/SKILL.md b/.claude/skills/implement-spec/SKILL.md new file mode 100644 index 0000000..187cd15 --- /dev/null +++ b/.claude/skills/implement-spec/SKILL.md @@ -0,0 +1,105 @@ +--- +name: implement-spec +description: > + Builds a whole parent GH issue's sub-issues on one integration branch, + running up to 3 implementer sub-agents in parallel worktrees in + `blocked_by` order, then reviews and opens one PR. User-invoked only: + `/implement-spec #N`. +disable-model-invocation: true +--- + +Turn a parent issue and its sub-issues into one integration branch and one +PR, every ticket done. + +Adapted from [mattpocock/skills](https://github.com/mattpocock/skills). +Copyright (c) 2026 Matt Pocock, MIT License: see the upstream +[LICENSE](https://github.com/mattpocock/skills/blob/main/LICENSE). + +## Input + +A parent issue `#N` (e.g. the `Audit <date>` parent from `/audit-to-tickets`). + +- Tickets: `gh api --paginate repos/{owner}/{repo}/issues/<N>/sub_issues --jq '.[] | select(.state == "open")'`. +- Edges: `gh api repos/{owner}/{repo}/issues/<n>/dependencies/blocked_by` + per ticket; fall back to the body's `## Blocked by` section. +- Spec: the parent body plus SPEC.md. + +The tickets are a **task graph**, not a list. The **frontier** is every open +ticket whose blockers are all done: merged into the integration branch, or a +closed issue. A blocker outside this parent that's still open → report it, +don't start that ticket. A cycle → print it and stop. + +Every ticket must name its seams (`## Seams` or equivalent). List the +tickets without them up front and ask the user to add them or name them +now; don't start those until they have seams. + +Sec tickets live in `.audit/`, never on GH, so they're never in the graph. + +## Context pointers + +Talk to sub-agents in pointers: issue numbers, file paths, commit SHAs, +the notes dir. Don't paste what they can read. + +## Steps + +1. **Read** every ticket and its edges; print the graph as + `#n <title> ← blocked by #m, #k` and the first frontier. +2. **Explore** when 3+ tickets touch the same files, or the tickets need + docs outside the repo. One sub-agent reads the code and docs and writes + notes to `<scratchpad>/spec-<N>/notes/`. Implementers read those instead + of re-exploring. +3. **Integration branch**: `git fetch -q origin`, then + `git switch -c spec/<N>-<slug> origin/main` and push it. The branch + already exists (a re-run after a stop) → switch to it; tickets already + merged into it count as done. +4. **Implement.** One background sub-agent per frontier ticket, max 3 at a + time, each with `isolation: "worktree"`. Each implementer: + - checks its worktree is on the integration branch tip, resets onto it if not; + - runs `bun install --frozen-lockfile`; + - builds the ticket with the `tdd` skill at the ticket's seams (none → + stop and report, don't invent); + - commits per logical change, no attribution lines; + - merges the integration tip into its branch, runs `bun run verify`; + - reports: branch, commits, verify result, `vuln: yes|no` (does the fix + reveal a vulnerability?), anything it couldn't do. +5. **Merge**, one at a time: a single merger sub-agent; reports that arrive + while it runs wait in a queue. `vuln: yes` → don't merge; stop and ask the + user (sec-ticket rules: nothing about it is pushed). Otherwise it merges + the branch into the integration branch, fixing only trivial conflicts + (imports, adjacent hunks), runs `bun run verify`, and pushes. A + non-trivial conflict or a failed verify → stop and ask the user. +6. **Advance.** Each merge can widen the frontier: launch implementers for + newly unblocked tickets, keeping the max of 3. Stop when the frontier is + empty; tickets left unmerged are **stuck**. +7. **Review.** Run the `change-review` skill on the integration branch + against `origin/main`. One implementer sub-agent, in its own worktree on + the integration tip like any ticket, fixes every `hard`, `missing`, + `wrong` and security finding (and the rest unless they contradict a + ticket), commits, runs verify. If its commits touch `apps/api/src/`, + `Dockerfile`, `compose*.yaml` or `.github/workflows/`, run the + `security-reviewer` agent on just those commits and fix what it finds. + The merger merges it as in step 5. +8. **PR.** Only now, after review and the security pass (CLAUDE.md: before + the PR). `bun run verify` on the integration branch passes → + `gh pr create`, body: `Part of #N` plus `Closes #n` for each **merged** + ticket only. Any stuck ticket → `--draft`, and the body lists the stuck + ones. Never push to `main`; the user merges. +9. **Clean up** the worktrees and local branches of merged tickets. Keep + stuck tickets' branches and name them in the output. + +## Output + +``` +spec: #<N> <title> · branch: <name> +done: #n, #n … · stuck: #n — <why> | none +review: fixed <n> · skipped: <finding — why> | none +verify: pass | fail · pr: <url> (ready | draft) | not opened: <why> +kept branches: <branch — #n> | none +skipped / risk: <what wasn't done or checked> | none +``` + +## Boundaries + +Doesn't merge the PR, close issues by hand, or edit the tickets. A ticket +whose fix turns out to be a vulnerability → stop and ask before anything +about it is pushed. diff --git a/.claude/skills/implement/SKILL.md b/.claude/skills/implement/SKILL.md new file mode 100644 index 0000000..d7f572b --- /dev/null +++ b/.claude/skills/implement/SKILL.md @@ -0,0 +1,76 @@ +--- +name: implement +description: > + Builds one already-decided piece of CertShell work (a GH issue, a local sec + ticket, or the plan agreed in this chat) test-first, reviews it, and opens + a PR. User-invoked only: `/implement <#N | ticket path | "the plan">`. +disable-model-invocation: true +--- + +Build work that's already decided. Don't reopen the plan: no new design, no +alternative approach. If the input is unclear, ask; don't redesign. + +Adapted from [mattpocock/skills](https://github.com/mattpocock/skills). +Copyright (c) 2026 Matt Pocock, MIT License: see the upstream +[LICENSE](https://github.com/mattpocock/skills/blob/main/LICENSE). + +## Input + +One of: + +- `#N` or an issue URL: `gh issue view <N>`. State its title before starting. +- A sec ticket path `.audit/<ts>/NN-slug.md` (from `/audit-to-tickets`). +- "the plan in this chat": restate it in 3–5 lines first. + +A bare number that could mean something else in context (a report finding, +a checklist item) → ask which. + +**Sec tickets never leave this machine** until the user says so: local +branch, no push, no PR (SECURITY.md: the fix would disclose the bug before +release). Same for any issue whose fix turns out to be a vulnerability. + +## Steps + +1. **Branch.** `git fetch -q origin` first. On `main` → + `git switch -c <type>/<slug> origin/main` (`fix/`, `feat/`, `docs/`, + `skills/` …). On a branch already made for this ticket → stay. Any other + branch → ask. +2. **Seams.** Read the code the ticket touches. Propose the seams (one line + each: what it catches, what it misses) and ask the user once. Seams + already written in the ticket → state them and go. +3. **Build** with the `tdd` skill, slice by slice. Typecheck and run single + test files as you go; full suite once at the end. +4. **Commit** per logical change (test + code for one slice can share a + commit). No attribution lines. +5. **Review.** Run the `change-review` skill against `origin/main`. Fix every + `hard`, `missing`, `wrong` and security finding; fix `judgement` and + `unasked` ones unless they contradict the ticket, and list any you skip. + Fixes are extra commits. The Security axis covered the earlier commits; + if the fix commits touch `apps/api/src/`, `Dockerfile`, `compose*.yaml` + or `.github/workflows/`, run the `security-reviewer` agent on just the + fix commits and fix what it finds. +6. **Verify.** `bun run verify` must pass. After 3 failed fix attempts, + stop before pushing: `verify: fail`, and report what fails. +7. **Vuln gate.** If the review, tests or your own reading show the bug is + a vulnerability, switch to sec-ticket rules (local only) and ask the + user before anything is pushed. +8. **PR** (not for sec tickets): `git push -u origin HEAD`, then + `gh pr create` with a body: what changed, how it was tested, and + `Closes #N` when the input was an issue. Never push to `main`. A PR for + the branch already exists → push, report its URL. Push or `gh` fails → + stop and report the error. + +## Output + +``` +built: <ticket title> +branch: <name> · commits: <n> · verify: pass | fail +review: <fixed n> · skipped: <finding — why> | none +pr: <url> | local only (sec) | not opened: <why> +skipped / risk: <what wasn't done or checked> | none +``` + +## Boundaries + +One ticket per run. Doesn't tick the issue's checkboxes or close it (the +PR's `Closes #N` does that on merge). Doesn't merge. diff --git a/.claude/skills/ponytail/SKILL.md b/.claude/skills/ponytail/SKILL.md new file mode 100644 index 0000000..3be2175 --- /dev/null +++ b/.claude/skills/ponytail/SKILL.md @@ -0,0 +1,91 @@ +--- +name: ponytail +description: > + CertShell's coding stance: the smallest change that fully solves the task, + reuse before stdlib before dependencies, no speculative code, and a reply a + busy human reads once. Always on: CLAUDE.md imports it into every session + and sub-agent. Invoke it only when the user says "ponytail", "be lazy", + "simplest solution", "yagni", or complains about bloat. +--- + +# Ponytail + +Be a lazy senior developer: the best code is the code never written. Solve +the whole problem with the least new code. End every reply that changed +code with one line: `skipped / risk: <what you skipped or didn't check, and +any risk the user must know>`, or `skipped / risk: none`. When a skill +defines its own Output block, the line goes inside it. + +Always on, for every session and sub-agent; there's no off switch. + +Adapted from [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail). +Copyright (c) 2026 DietrichGebert, MIT License: see the upstream +[LICENSE](https://github.com/DietrichGebert/ponytail/blob/main/LICENSE). + +## Precedence + +The user's current request, then the rest of CLAUDE.md and SPEC.md, then +the `tdd` skill, then this file. + +## Before you write + +Read the task and the code it touches. List every place the change must +reach: callers, tests, fixtures, config, exports, SPEC.md. Check what it +could break: data it destroys or exposes, callers that stop working. That's +scope. Extra features aren't. + +## The smallest complete change + +Take the first option that fully works: + +1. **Does it need to exist?** Skip features, options and flexibility nobody + asked for; name them in one line. +2. **Already in this repo?** Use it the way the surrounding code does (see + Reuse first). +3. **Bun or the platform?** `Bun.spawn`/`run()` argv, Bun `Glob`, `bun:test`, + Web Crypto (`crypto.getRandomValues`, `crypto.subtle`). Never `$` for `ssh`/`ssh-keygen` (it drops + empty args). +4. **An installed dependency?** Use it. A new one needs a reason a reviewer + accepts (CONTRIBUTING), never for a few lines. +5. **One line a reader gets at a glance?** One line. +6. Otherwise, the minimum code that works. + +- Lazy about the solution, never about the change: finish every part it + needs, including the callers, tests and fixtures it breaks. +- No abstraction, wrapper, option, config or "for later" code nobody asked + for. Keep values in the form the platform gives you. Deletion beats + addition. Keep the repo's structure: `routes/` → `lib/` / `ssh/`. +- The shortest working diff wins, once you know everything it must touch. A + one-liner that needs decoding isn't short. +- Comments: CLAUDE.md's rule. Only the why the code can't show, one line. +- Bug fix: grep every caller of the function first, then fix the root cause + once in the shared code. +- Moved or merged code keeps its error handling and validation. +- Between options of equal size, take the one correct on edge cases. +- Every change is tested (CLAUDE.md), trivial or not. New behaviour or a + bug fix goes through `tdd`; a pure rename, move or docs change just keeps + the existing tests green. +- A shortcut with a known limit gets `// shortcut: <the limit>, <when to upgrade>`. + `/project-audit` treats these as recorded decisions. + +## Reuse first + +- argv commands: `run()` in `apps/api/src/ssh/exec.ts`. +- Input checks: `valid*` and `parseHostKey` in `apps/api/src/lib/validate.ts`. +- Access: `accessibleHosts` / `allowedHost` in `apps/api/src/lib/hosts.ts`. +- Audit events: `audit()` in `apps/api/src/lib/audit.ts`. +- Rate/concurrency limits: `apps/api/src/lib/ratelimit.ts`. +- JSON body + write guards: `apps/api/src/lib/http.ts`. +- Shell quoting and host-side snippets: `sq`, `forAnyShell` in `packages/shared/src/index.ts`. +- API/web types: `packages/shared/src/index.ts`. +- Web fetch: `api()` in `apps/web/src/api.ts`. UI: `apps/web/src/components/ui/` + (shadcn) before any native widget. +- Tests: `startApp`, `startMockIdp` in `apps/api/test/helpers/`. + +## Never cut + +Anything SPEC.md §3 (Auth model), §5 (App implementation notes) or §6 +(Deployment) requires, or the user asked for: auth and +access-map checks, validation at trust boundaries, rate limits, security +headers, audit events, tests security relies on, error handling that +prevents data loss, accessibility. Don't re-propose SPEC.md §9 (Rejected alternatives). diff --git a/.claude/skills/tdd/SKILL.md b/.claude/skills/tdd/SKILL.md new file mode 100644 index 0000000..14ee793 --- /dev/null +++ b/.claude/skills/tdd/SKILL.md @@ -0,0 +1,90 @@ +--- +name: tdd +description: > + Test-first development for CertShell: red → green in vertical slices, tests + only at agreed seams, real dependencies over mocks. Use when the user wants + to build a feature or fix a bug test-first, says "tdd", "red-green", or + "/tdd", or when `/implement` or `/implement-spec` calls it. +--- + +The red → green loop, plus the rules that make its tests worth keeping. + +Adapted from [mattpocock/skills](https://github.com/mattpocock/skills). +Copyright (c) 2026 Matt Pocock, MIT License: see the upstream +[LICENSE](https://github.com/mattpocock/skills/blob/main/LICENSE). + +## A good test + +Tests check behaviour through a public interface, never internals. The code +underneath can be rewritten; the test shouldn't change. Name it like a spec: +`"sign rejects a login not in the access map"`, not `"calls checkAccess"`. + +## Seams + +A **seam** is the public boundary a test observes: a route +(`app.fetch(new Request(...))`), a WebSocket, an exported function. + +**Test only at agreed seams.** Before the first test, list the seams, each +with one line on what it catches and what it misses, and get them confirmed: + +- The invoking prompt or ticket lists seams → they're confirmed; state + them and go (`/implement` confirmed them already; `/implement-spec` + tickets carry them). +- Otherwise, called by the user directly → ask them once. +- Otherwise, inside an `/implement-spec` implementer → stop and report + `BLOCKED: no seams in ticket #<n>`; don't invent them. + +## Where tests go + +- API: `apps/api/test/<area>.test.ts`. Web unit: `apps/web/test/`. + Browser flows: `apps/web/e2e/`. Repo-level: `test/`. All end in `.test.ts`. +- Run one file while looping: `bun test <path>` (from the app dir for app + tests). Typecheck often: `bun run typecheck`. +- E2E: `bun run e2e` (Playwright). `bun run verify` doesn't run it: run it + whenever you touch `apps/web/e2e/` or the flows it covers. +- No `find`/`grep` in tests; use Bun's `Glob` and built-ins. + +## Real, not mocked + +Use the real thing; the helpers already exist: + +- Server + WS + session + throwaway CA: `startApp()` in `apps/api/test/helpers/app.ts`. +- OIDC: `startMockIdp()` in `apps/api/test/helpers/mock-idp.ts`. +- SQLite: `openDb(":memory:")`. `ssh-keygen`: the real binary. +- ssh: swap the command through `startApp`'s terminal deps for a local `sh` + that inspects the real cert (`fakeSsh` in `apps/api/test/terminal.test.ts`). +- Real target host only for sshd-side behaviour: `apps/api/test/ssh-target.test.ts`, + skipped unless `SSH_TARGET` is set; its header comment has the compose command. + +The only fakes: ssh (above), the OIDC IdP (`startMockIdp`), time +(`setSystemTime`) and outbound alerts (`apps/api/test/fixtures/shoutrrr.ts`). +Never mock our own modules. + +## Anti-patterns + +- **Implementation-coupled**: mocks an internal module, tests a private + function, or checks through a side channel (querying SQLite instead of the + route). Tell: a refactor breaks it with no behaviour change. +- **Tautological**: the expected value is computed the way the code computes + it, so it can't fail. Expected values are literals, worked examples, or + SPEC.md. +- **Horizontal slicing**: all tests first, then all code. Tests written that + way check an imagined shape, not behaviour. + +## Loop rules + +- **Red first.** Write one failing test, watch it fail for the right reason + (its assertion fails, not a compile, import or setup error), then write + just enough code to pass. +- **One slice per cycle**: one seam, one test, the minimal code. +- **No refactoring in the loop.** Called from `/implement` or + `/implement-spec`: refactor in their review-fix step, after + `/change-review`. Called directly: one refactor pass once everything is + green, keeping it green. +- Security behaviour (authz, input checks, headers, rate limits, audit + events) gets a test that fails without the check. + +## Done when + +Every agreed seam has a passing test, `bun run verify` passes, and `bun run e2e` +passes if you touched `apps/web/e2e/` or the flows it covers. diff --git a/.gitignore b/.gitignore index 8f27fd3..f8331bf 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ apps/web/skills-lock.json .wrangler/ .claude/settings.local.json .audit/ +.claude/worktrees/ diff --git a/CLAUDE.md b/CLAUDE.md index 527613c..0d57236 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,3 +18,7 @@ - **Run `bun run verify` before saying done** - lint, typecheck, build and tests, same as CI minus `bun audit`. - **Security review** - any change under `apps/api/src/`, `Dockerfile`, `compose*.yaml` or `.github/workflows/` gets a `security-reviewer` agent pass before the PR. - **Never push to `main`** - it is protected by a ruleset (PR required, CI `check` must pass). Branch, push, `gh pr create`; the user merges once green. + +## Coding stance + +@.claude/skills/ponytail/SKILL.md diff --git a/biome.json b/biome.json index f3193f0..9496003 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,12 @@ }, "files": { "ignoreUnknown": false, - "includes": ["**", "!**/.claude/skills", "!**/skills-lock.json"] + "includes": [ + "**", + "!**/.claude/skills", + "!**/.claude/worktrees", + "!**/skills-lock.json" + ] }, "formatter": { "enabled": true,