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
4 changes: 2 additions & 2 deletions .claude/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ ceremony. Plain git only: PRs and issues are opened in the Gitea web UI

| Command | Purpose |
|---|---|---|
| `/git-commit` | Stage and commit using Conventional Commits; branch-or-trunk by size. |
| `/git-commit` | Stage explicitly and commit using Conventional Commits on a non-main branch. |
| `/git-merge` | Merge current branch into `main` safely β€” checks, confirms, merges. |

## Agents
Expand Down Expand Up @@ -177,7 +177,7 @@ agents also invoke them explicitly via the Skill tool.

### Workflow

- `git-workflow` β€” Conventional Commits, branch-or-trunk, plain git; PR/issue templates for the Gitea web UI
- `git-workflow` β€” the one Git policy: GitHub + `gh`, branches only, explicit staging, no trailers, merge/tag/release = Dean

### Personal context

Expand Down
28 changes: 22 additions & 6 deletions .claude/agents/reviewer.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
---
name: reviewer
description: Read-only code + security gate for a single built task (C#/.NET first-class; TypeScript for UI code). Returns PASS or FAIL with findings. Dispatched by /build-loop.
description: Read-only code + security gate for a single built task (C#/.NET first-class; TypeScript for UI code). Returns PASS, PASS-WITH-NOTES, or FAIL with findings. Dispatched by /build-loop.
tools: Read, Glob, Grep, Bash, Skill
disallowedTools: Write, Edit, NotebookEdit
model: inherit
---

You are the gate between a built task and git history. You review the builder's
work and return a verdict. You **cannot and must not modify code** β€” you have
no edit tools by design. A reviewer that fixes its own findings isn't a gate.
work and return a verdict. You **must not modify the tree.** `Write`/`Edit`
are disallowed; `Bash` is yours for `git diff`, `git log`, `dotnet build`,
`dotnet test`, `npm test`, `grep` and the like **only**. Any Bash command
that writes to the working tree (`sed -i`, redirects, `git checkout --`,
`git stash`, formatters) is a self-`FAIL` β€” report what you would change
as a finding instead. A reviewer that fixes its own findings isn't a gate.

> 🎯 **Design for change.** Your top-level lens is change-safety. Call out
> coupling, low cohesion, leaky abstractions, missing seams, and names that
Expand Down Expand Up @@ -69,9 +74,15 @@ no edit tools by design. A reviewer that fixes its own findings isn't a gate.
- `PASS` β€” correct, secure, idiomatic, tests genuinely green, zero
warnings, **entry-point trace reaches the promised side effect**, no
ghost code, platform-parity clean. Safe to commit.
- `FAIL` β€” list specific, actionable findings (file:line, what's wrong, why
it matters). Severity-order them. No vague "consider" notes β€” say what
must change to pass.
- `PASS-WITH-NOTES` β€” everything above holds; the only findings are
comments, doc strings, naming, or log text. List them as `NOTE:`
items. The orchestrator fixes them before commit; **no new build
round**.
- `FAIL` β€” at least one finding changes behaviour, safety, tests, or
the entry-point trace. List specific, actionable findings (file:line,
what's wrong, why it matters), severity-ordered. No vague "consider"
notes β€” say what must change to pass. Comment-only findings never
make a `FAIL` on their own.

## Standards

Expand All @@ -83,3 +94,8 @@ no edit tools by design. A reviewer that fixes its own findings isn't a gate.
Mocks define test reality, not production reality.
- Review the diff, not the whole codebase. Don't gate on pre-existing issues
outside this task unless the task made them materially worse.
- Run the **full solution**, never a class filter: `dotnet test GenWave.sln
--filter "Category!=Integration"` (Host suite alone on the dev box needs
`-- xUnit.MaxParallelThreads=3`). A filtered green is not green.
- Cite the spec you are checking against by number (F-nn, Story nnn) and
quote it; a citation in your findings is a testable claim.
96 changes: 70 additions & 26 deletions .claude/commands/build-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,55 @@ code or review it yourself β€” you dispatch and gate.
checkboxes, stop and report β€” it is not a build plan.
3. Confirm a clean git working tree. If dirty, stop and report; do not build on
top of uncommitted changes.
4. Confirm you are on a feature branch, not `main` (`git-workflow` skill:
there is no trunk lane). If on `main`, stop and ask for the branch.
5. **Green baseline.** Run the full solution once:
`dotnet test GenWave.sln --filter "Category!=Integration"` (Host suite
alone on the dev box needs `-- xUnit.MaxParallelThreads=3`), plus
`npm test` in `admin-ui/` when the plan touches UI. Record the counts.
A red baseline stops the loop β€” report it, don't build on it.

## The loop

For each task still unchecked (`- [ ]`), in order, top to bottom:

1. **Build.** Dispatch a `builder` subagent (Agent tool,
`subagent_type: builder`, model **sonnet** (alias; tracks the current generation)). Give it: the exact task text,
the relevant section of PLAN.md, the files it owns, and an instruction to
invoke the project's language + DB skills (`csharp-best-practices` for C#,
`typescript-best-practices` for TS, `postgres-dba` for schema work) and
run the existing specs (`dotnet test` / `bun test` / project test command)
before reporting back. The builder **does not commit**.
`subagent_type: builder`, model **sonnet** β€” alias, tracks the current
generation). The brief contains: the exact task text, the relevant
section of PLAN.md, the files it owns, the skills to invoke
(`csharp-best-practices` for C#, `typescript-best-practices` for TS,
`postgres-dba` for schema work), and the exact test command from
Preflight step 5. The builder **does not commit**.

**Brief laws** (each cost a review round before it became a law):
- **Quote, don't paraphrase.** Paste the exact SPEC/STORIES/ARCHITECTURE
text the task implements β€” DDL verbatim, F-numbers, Story numbers.
A paraphrased schema is a wrong schema.
- **Every citation is a testable claim.** Before dispatch, open the file
and confirm the F-number / Story / spec name you cite exists and says
what you claim. Stale citations send the builder down the wrong path.
- **Name the full test command.** Always the whole solution, never a
class or namespace filter. A filtered green hid failures in other
classes more than once.

2. **Review.** Dispatch a `reviewer` subagent (Agent tool,
`subagent_type: reviewer`, model **inherit** β€” the reviewer runs on the session model, so it is always β‰₯ the builder).
It invokes the matching security skill (`security-api` for backend code,
`security-web` for UI code) and `simplify`, reads the builder's diff, and
returns a verdict: `PASS` or `FAIL` with specific findings.

3. **Recurse.** If `FAIL`: send the findings back to a fresh `builder` for the
same task. Repeat build β†’ review until `PASS`. No cap β€” a task is not done
until it passes code **and** security review. Nothing reaches git history
before `PASS`.
`subagent_type: reviewer`, model **inherit** β€” it runs on the session
model, so it is always β‰₯ the builder). It invokes the matching security
skill (`security-api` for backend code, `security-web` for UI code) and
`simplify`, reads the builder's diff, runs the full solution, and
returns `PASS`, `PASS-WITH-NOTES`, or `FAIL` with specific findings.

3. **Recurse, capped.**
- `PASS` β†’ step 4.
- `PASS-WITH-NOTES` β†’ the notes are comment/doc/naming-only. **You** fix
them (the orchestrator may edit comments, doc strings, and PLAN.md β€”
nothing else), then step 4. No new build round.
- `FAIL` β†’ send the findings to a fresh `builder` for the same task and
go back to step 2. **Three `FAIL`s on one task β†’ stop the loop** and
report to Dean: the task, all three rounds' findings, and what you
think is stuck (brief, spec, or builder). Do not keep spinning; a
seven-round task is a briefing problem, not a builder problem.
Nothing reaches git history before a passing verdict.

4. **Smoke the entry point.** Before commit, build/start the production
artifact and drive **one real request through the deployed entry point**
Expand All @@ -61,26 +87,44 @@ For each task still unchecked (`- [ ]`), in order, top to bottom:
actually occurred. If the smoke fails, recurse to step 3 with a finding β€”
unit-test green is not enough to ship.

5. **Commit.** On `PASS` + smoke green, have the builder commit *only this
task's changes*
with a message naming the task. One commit per task.

6. **Check the box.** Edit PLAN.md: `- [ ]` β†’ `- [x]` for the completed task.
Commit that PLAN.md change with the task commit or immediately after.
**Smoke laws:**
- Teardown kills the listener by **port** (`ss -ltnp` β†’ pid β†’ `kill`),
never by the subshell pid β€” that orphans the server and the next
smoke fights it for the port.
- `next build` rewrites `tsconfig.json` and `next-env.d.ts`. `git
checkout -- admin-ui/tsconfig.json admin-ui/next-env.d.ts` before
staging.
- Approve-style endpoints need `If-Match` (a bare PUT gets 428).

5. **Commit.** On a passing verdict + smoke green, have the builder commit
*only this task's files* with explicit `git add <path> …` β€” never `-am`
or `-A`. Message per `git-workflow`: `<type>(<scope>): T<n> <what>`,
body says what and why, no trailers. One commit per task.

6. **Check the box.** Before ticking, `grep -rn "pending: T<n>"` across
`tests/` β€” a spec still marked pending for this task means the task is
not done. No `Assert.Fail` stubs may ship. Then edit PLAN.md:
`- [ ]` β†’ `- [x]`, and commit that PLAN.md change with the task commit
or immediately after.

7. Next task.

## Finish

When every box is checked: run the full test suite once more, then report a
summary (tasks completed, commits, anything still red). Architect / final
design check is a separate step β€” not part of this loop.
When every box is checked: run the full solution once more, `git worktree
prune`, then report a summary (tasks completed, commits, round count per
task, anything still red). Architect / final design check is a separate
step β€” not part of this loop. Merging the branch is Dean's, per action.

## Rules

- Sequential, dependency-ordered. This is a pipeline, not a parallel team β€” use
the Agent tool (subagents), not Agent Teams.
- Builder owns code; reviewer owns the gate; you own sequencing and the
checkbox state. Never collapse these roles.
- If a gate can't pass after repeated attempts and the builder is stuck, stop
and report the task + findings rather than committing degraded code.
- Three `FAIL`s on a task stops the loop (step 3). Report; never commit
degraded code to get past a gate.
- Scratch hygiene: anything you rsync for a subagent excludes
`node_modules bin obj .git` β€” unfiltered scratch grew ~1k files per round.
- Doc and comment claims about boot/compose/runtime behaviour are testable
facts; the reviewer checks them and so should the brief.
86 changes: 29 additions & 57 deletions .claude/commands/git-commit.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,45 @@
---
description: Stage and commit current changes using Conventional Commits, branch-or-trunk by size. Uses the `git-workflow` skill.
description: Stage the current changes explicitly and commit with a Conventional Commit message on a non-main branch. Defers to the `git-workflow` skill.
argument-hint: [optional summary or scope hint]
---

# commit

Create a clean, well-described commit for the current working changes.
Defers to the **`git-workflow`** skill for branching and message format.
Create one clean, well-described commit for the current working changes.
All policy lives in the **`git-workflow`** skill β€” read it first; this
command only sequences it.

## Behavior

1. **Inspect first.** Run in parallel:
- `git status` (no `-uall`)
- `git diff` (staged + unstaged)
- `git log -n 10 --oneline` to match the project's commit style
- `ls .gitignore` to confirm a `.gitignore` exists
2. **Require a `.gitignore`.** If one is missing, **stop and offer to
create one** scaffolded to the project (.NET by default β€” `bin/`,
`obj/`, `*.user`, `.vs/`, `.env*`, IDE folders; plus
`node_modules/`, `dist/` when a JS/TS UI is present). Do not
proceed with the commit until the user accepts or
provides their own. If `.gitignore` exists, glance at it and warn if
anything obviously sensitive in the diff isn't covered.
3. **Check change size first.** Roughly: count files changed and lines
touched. If it looks like **a lot** (rule of thumb: >10 files OR
>300 lines OR touches >2 distinct concerns), **stop and ask** whether
a short-lived branch would be better. Recommend a name in
`<type>/<slug>` form derived from the diff (e.g.
`feat/order-fulfillment-tx`). Wait for the user before continuing.
4. **Decide branch vs trunk** per `git-workflow` skill:
- Trunk β†’ commit straight to `main` for tiny, obvious changes.
- Branch β†’ create `<type>/<short-slug>` if the change is non-trivial,
spans multiple files meaningfully, or implements a story task.
5. **Stage everything with `-am`.** Use `git commit -am "<summary>"`
(with `-F` for the long body via a HEREDOC file when needed) so
tracked modifications and deletions are all included β€” nothing gets
left behind. For brand-new untracked files, `git add <file>` them
explicitly first (since `-a` won't pick those up).
**Always re-scan for likely secrets** (`.env*`, `*.pem`,
`*credentials*`, API tokens in the diff) before staging β€” refuse
the commit if any are found, name the file, and ask the user.
6. **Write the message** per the `git-workflow` skill:
- Conventional Commit summary (`<type>(<scope>): <imperative>`).
- Detailed body: what changed, why, trade-offs. Wrap ~72 cols.
- Footers: `Refs <story-id>` / `Closes #<n>` where applicable.
7. **Commit via HEREDOC** so formatting is preserved. Append:
```
Co-Authored-By: Claude <noreply@anthropic.com>
```
8. **Verify** with `git status` after the commit. If a hook fails, fix
the underlying issue and create a NEW commit β€” never `--amend` without
being asked.
1. **Inspect first.** In parallel: `git status`, `git diff` (staged +
unstaged), `git log -n 10 --oneline` for the house style, and
`git branch --show-current`.
2. **Never on `main`.** If the current branch is `main`, create
`<type>/<slug>` from the diff (e.g. `fix/bed-picker-empty-718`) and
switch to it before anything else. There is no trunk lane.
3. **Size check.** If the diff spans more than one concern, say so and
propose splitting into separate commits; wait for the user.
4. **Secrets scan.** Re-read the diff for `.env*`, `*.pem`,
`*credentials*`, tokens, real connection strings. Any hit: refuse,
name the file, stop.
5. **Stage explicitly.** `git add <path> …` for exactly the files that
belong to this change. Never `-am`, never `-A`. Leave out generated
files (`next-env.d.ts`, tsconfig rewrites from `next build`) and
anything that is someone else's WIP.
6. **Write the message** per `git-workflow`: Conventional Commit summary,
a body that says what and why, `Refs gh-#N` / `Closes #N` footer.
**No trailers** β€” no `Co-Authored-By`, no `Claude-Session`.
7. **Commit via HEREDOC** (`git commit -F - <<'EOF' … EOF`) so formatting
survives. If a hook fails, fix the cause and make a NEW commit β€” never
`--amend` unless asked.
8. **Verify** with `git status` and `git log -1`.

## Rules

- Always require a `.gitignore`. Offer to create one if missing; do not
commit without it.
- Always use `-am` (plus explicit `git add` for new files) so nothing
tracked is left out of the commit.
- If the change is large (see step 3 thresholds), stop and recommend a
branch name before committing.
- Never push as part of `/git-commit` unless the user asks.
- Never `--no-verify` or `--no-gpg-sign`.
- Never force-push.
- If the working tree is clean, say so and stop β€” don't create empty commits.
- If the diff contains a likely secret, refuse and tell the user what
was found.
- Never `--no-verify`, `--no-gpg-sign`, or force-push.
- Clean tree β†’ say so and stop; no empty commits.

## Hand off

Report: branch (created or trunk), files staged, summary line, and the
new commit SHA.
Report: branch, files staged, summary line, new commit SHA.
10 changes: 6 additions & 4 deletions .claude/commands/quick-fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ misnamed variable, a stray import. The kind of bug where opening

## Scope

- IN: one-spot fixes that a competent engineer would commit straight to
`main` without ceremony. Single concern. Usually < ~20 lines changed.
- IN: one-spot fixes that need no design call. Single concern. Usually
< ~20 lines changed. Still on a branch β€” never on `main` (see the
`git-workflow` skill).
- OUT: anything that needs a design call, touches a public API contract,
changes behavior across modules, or needs new tests beyond what already
exists. If it smells like that, stop and suggest `/plan` or `/build-loop`.
Expand Down Expand Up @@ -47,8 +48,9 @@ If empty, ask one short clarifying question.
file, or the single related test). For C#, zero warnings is part of
"verified" β€” warnings as errors is the house rule.
6. **Report and ask before committing.** Show the diff summary and propose
a one-line commit message. Do **not** commit unless the user says go β€”
committing straight to `main` is a shared-state action.
a one-line commit message. Do **not** commit unless the user says go.
On go: if on `main`, branch first (`fix/<slug>`); stage the touched
files explicitly; no trailers; then offer to open the PR.

## Commit message style

Expand Down
39 changes: 39 additions & 0 deletions .claude/hooks/merge-guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# .claude/hooks/merge-guard.sh β€” PreToolUse guard for Bash (gh-#751).
#
# MERGE RULE: merging, tagging, releasing, and pushing to main need Dean's
# explicit per-action consent. This hook makes that mechanical for every
# subagent. It reads the hook's stdin JSON, splits the command into
# segments (; && || | newline), and matches each segment's LEADING verb,
# so a grep/echo/sed that merely mentions "git tag" is not blocked.
#
# Exit 2 blocks the call and feeds stderr back to the model; exit 0 allows.
set -u
cmd=$(jq -r '.tool_input.command // empty' 2>/dev/null) || exit 0
[ -n "$cmd" ] || exit 0

refuse() {
echo "refuse: '$1' β€” merge / tag / release / push to main needs Dean's per-action consent (MERGE RULE, gh-#751). Hand him the command instead." >&2
exit 2
}

# Normalise separators to newlines, then judge each segment on its own.
printf '%s\n' "$cmd" | sed -E 's/(&&|\|\||;|\|)/\n/g' | while IFS= read -r seg; do
# Strip leading whitespace, env assignments, and a leading `sudo`/`command`.
seg=$(printf '%s' "$seg" | sed -E 's/^[[:space:]]+//; s/^([A-Za-z_][A-Za-z0-9_]*=[^ ]* +)*//; s/^(sudo|command) +//')
case "$seg" in
"gh pr merge"*) refuse "$seg" ;;
"gh release create"*) refuse "$seg" ;;
"git tag"*)
case "$seg" in
"git tag -l"*|"git tag --list"*|"git tag --contains"*|"git tag -n"*|"git tag") ;;
*) refuse "$seg" ;;
esac ;;
"git push"*)
case "$seg" in
*" --tags"*|*" main"|*" main "*|*":main"*|*":refs/heads/main"*|*" --delete"*|*" -d "*) refuse "$seg" ;;
esac ;;
esac
done
# `while` runs in a subshell; propagate its exit status.
exit ${PIPESTATUS[2]:-0}
2 changes: 1 addition & 1 deletion .claude/settings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"hooks": [
{
"type": "command",
"command": "c=$(jq -r '.tool_input.command // empty'); case \"$c\" in *'git tag'*-l*|*'git tag'*--list*|*'git tag --contains'*) exit 0;; *'gh pr merge'*|*'gh release create'*|*'git tag'*|*'git push'*--tags*|*'git push'*' main'*|*'git push'*':main'*|*'git push'*'HEAD:refs/heads/main'*) echo 'refuse: merge / tag / release / push to main needs Dean'\\''s per-action consent (MERGE RULE, gh-#751). Hand him the command instead.' >&2; exit 2;; esac; exit 0"
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/merge-guard.sh"
}
]
}
Expand Down
Loading
Loading