Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d7b719f
Skill: tdd, test-first loop with CertShell seams and real test helpers
maca134 Oct 19, 2026
a7db8cb
Skill: change-review, standards/spec/security axes against origin/main
maca134 Oct 19, 2026
df06eef
Skill: implement, one ticket test-first through review to PR
maca134 Oct 19, 2026
b5e76bf
Skill: implement-spec, parent issue's sub-issues on one integration b…
maca134 Oct 19, 2026
f63ee73
Skill: audit-to-tickets tickets carry agreed seams for implement-spec
maca134 Oct 19, 2026
d907097
Skill: tdd matches the repo's ssh fake and documents e2e
maca134 Oct 19, 2026
762a935
Skill: tdd doesn't re-ask confirmed seams, points refactors at review…
maca134 Oct 19, 2026
bd7ad13
Skill: change-review waits on background axes and hands them the line…
maca134 Oct 19, 2026
48ac698
Skill: change-review triggers on spec checks, not generic branch review
maca134 Oct 19, 2026
79aa815
Skill: implement skills re-run security-reviewer on sensitive review …
maca134 Oct 19, 2026
1e0d5af
Skill: implement-spec review fixer works in its own worktree
maca134 Oct 19, 2026
24ffeb7
Biome and git ignore .claude/worktrees so verify passes with agent wo…
maca134 Oct 19, 2026
ec752e1
Format biome.json
maca134 Oct 19, 2026
c5a5063
Skill: ponytail adapted for CertShell (smallest complete change, reus…
maca134 Oct 19, 2026
2e54d11
CLAUDE.md auto-loads the ponytail skill
maca134 Oct 19, 2026
0948ea1
Drop the ponytail plugin; the in-repo skill replaces it
maca134 Oct 19, 2026
5727bce
Skill: implement skills end with a skipped / risk line
maca134 Oct 19, 2026
8656f7f
Skill: implement handles foreign branches, verify failure, mid-run vu…
maca134 Oct 19, 2026
d5244ed
Skill: implement-spec serial merger, vuln stop, PR after review, stuc…
maca134 Oct 19, 2026
21b72f7
Skill: change-review pins both SHAs, numbers security findings, read-…
maca134 Oct 19, 2026
4ba4f6e
Skill: tdd defines done, right-reason red, seam confirmation, full fa…
maca134 Oct 19, 2026
8b002ee
Skill: ponytail always on, no re-invoke trigger, precedence and SPEC …
maca134 Oct 19, 2026
125da6c
Skills: full MIT copyright notices for adapted upstream skills
maca134 Oct 19, 2026
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
14 changes: 1 addition & 13 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1 @@
{
"enabledPlugins": {
"ponytail@ponytail": true
},
"extraKnownMarketplaces": {
"ponytail": {
"source": {
"source": "github",
"repo": "DietrichGebert/ponytail"
}
}
}
}
{}
11 changes: 9 additions & 2 deletions .claude/skills/audit-to-tickets/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```
<T>. <title> [<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"`.
Expand Down Expand Up @@ -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)
Expand Down
118 changes: 118 additions & 0 deletions .claude/skills/change-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -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`.
105 changes: 105 additions & 0 deletions .claude/skills/implement-spec/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
76 changes: 76 additions & 0 deletions .claude/skills/implement/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading