Address review round 3: safe API snippets, PR opened once, pinned review

- Titles and bodies go through --rawfile files, never inline shell quotes
- pipefail + curl -f + jq -e so a failed call can't look successful
- Fix rounds merge latest main and skip re-opening the PR
- Check the branch isn't main before pushing; never bypass the hooks
- Review at a pinned level; only correctness findings checked against the
  code block approval

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
christianandClaude Opus 5.5 committed 2026-10-21 00:50:38 +02:00
1 parent fa45c58c0c
commit ed197eeeb1
1 file changed
+31 -19
+31 -19
View File
@@ -14,31 +14,43 @@ A tiny zero-dependency Node.js todo CLI, used as a sandbox for trying Claude Cod
## Workflow (required) ## Workflow (required)
Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`. Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`.
These rules are instructions, not a lock. Gitea branch protection on `main` is the real guard; the `git push … main` deny rules in `.claude/settings.json` only catch the obvious spellings. This is enforced by the git hooks that `npm install` installs and by Gitea branch protection (see "Protecting `main`" in the README). Never get around them: no `--no-verify`, no changing `core.hooksPath`, no editing `.git/`.
1. Start every change from a fresh `main`. Run `git fetch origin` as its own command, then `git switch --no-track -c <type>/<short-name> origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). `--no-track` keeps the branch from tracking `main`. 1. **Branch.** Run `git fetch origin` on its own, then `git switch --no-track -c <type>/<short-name> origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). `--no-track` stops the branch from tracking `main`.
2. Commit on that branch; run `npm test` before every push. 2. **Commit and test.** Commit on the branch; `npm test` must pass before every push.
3. Push with `git push -u origin HEAD`, then open a PR against `main` (see "Gitea API" below). 3. **Push.** Check that `git branch --show-current` isn't `main`, then run `git push -u origin HEAD`.
4. Review it with `/code-review <branch>`. Always name the branch, so the review diffs it against `main` whatever its upstream is. 4. **Open the PR**, once per branch (see "Gitea API" below).
5. Post the result as a PR comment. The first line is the verdict: **Needs fixes** if there is any correctness finding, otherwise **Approved** (cleanup-only findings are listed but don't block). The second line is `_Automated self-review by Claude; not an independent review._`, followed by the findings. 5. **Review** with `/code-review <branch> medium`. Name both the branch and the level, so every round diffs against `main` at the same depth.
6. If it needs fixes, fix the correctness findings on the same branch and repeat steps 2–5. At most 2 fix rounds: after that, post what's left and hand off to the human. 6. **Comment** on the PR with the verdict.
7. Stop there. Do not merge the PR, even if approved; tell the human it's ready. - First line: **Needs fixes** if any correctness finding still holds after you check it against the code; otherwise **Approved**. Cleanup and judgement-call findings are listed but don't block.
- Second line: `_Automated self-review by Claude; not an independent review._`
- Then the findings, including any you checked and rejected, and why.
7. **Fix rounds.** If it needs fixes, first bring in the latest `main`: run `git fetch origin`, then `git merge --no-ff --no-commit origin/main`, then `git commit --no-edit`, each as its own command. Don't rebase, because force pushes are denied. Fix the correctness findings, then repeat steps 2, 3, 5 and 6 (not step 4: the PR already exists). Do at most 2 fix rounds, then post what's left and hand off.
8. **Stop.** Never merge, even if approved; tell the human it's ready.
### Gitea API ### Gitea API
No `gh`/`tea`: use `curl` with `$GITEA_TOKEN`. One-time human setup in the git-ignored `.claude/settings.local.json`: set `env.GITEA_TOKEN` and add `code.westerkam.org` to `sandbox.network.allowedDomains`. Settings load at session start, so after adding them the session must be restarted (`claude --continue`). If the token is unset, stop and ask the human; don't work around it. No `gh`/`tea`: use `curl` with `$GITEA_TOKEN`.
Shell state doesn't persist between commands, so set every variable in the same command as the call. Write comment bodies to `$TMPDIR`, never into the repo. `-fsS` makes HTTP errors fail loudly; check that the PR response has a `.number`. One-time human setup in the git-ignored `.claude/settings.local.json`: set `env.GITEA_TOKEN`, and add `code.westerkam.org` to `sandbox.network.allowedDomains`. Settings load at session start, so restart the session after adding them (`claude --continue`). If the token is unset, stop and ask the human; don't work around it.
Rules for every API call:
- Make each call one self-contained command, because shell state doesn't carry over between commands.
- Write titles and bodies to files in `$TMPDIR` (never in the repo) and pass them with `--rawfile`. Never put them inline in double quotes, where backticks and `$(…)` would run.
- `pipefail`, `curl -f` and `jq -e` make every failure exit non-zero.
```sh ```sh
API=https://code.westerkam.org/api/v1/repos/christian/claude-playground # Open a PR for the current branch (once). Write $TMPDIR/pr-title.txt and $TMPDIR/pr-body.md first.
# Open a PR set -o pipefail; API=https://code.westerkam.org/api/v1/repos/christian/claude-playground
BRANCH=fix/x TITLE="Fix x" BODY="…" jq -n --arg head "$(git branch --show-current)" --rawfile title "$TMPDIR/pr-title.txt" --rawfile body "$TMPDIR/pr-body.md" \
jq -n --arg head "$BRANCH" --arg title "$TITLE" --arg body "$BODY" '{head:$head, base:"main", title:$title, body:$body}' \ '{head:$head, base:"main", title:($title|rtrimstr("\n")), body:$body}' \
| curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/pulls" | jq -e .number | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/pulls" \
# Comment on PR number $N | jq -er '"#\(.number) \(.html_url)"'
N=1
# Comment on PR <number>. Write $TMPDIR/comment.md first.
set -o pipefail; API=https://code.westerkam.org/api/v1/repos/christian/claude-playground
jq -n --rawfile body "$TMPDIR/comment.md" '{body:$body}' \ jq -n --rawfile body "$TMPDIR/comment.md" '{body:$body}' \
| curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues/$N/comments" | jq -r .html_url | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues/<number>/comments" \
| jq -er .html_url
``` ```
Run `git fetch`, `git commit` and `git push` as standalone commands (no `cd … &&` prefix, nothing chained after them): only then do they run outside the sandbox, which reaching the remote and commit signing need. Run `git fetch`, `git commit` and `git push` as standalone commands, with no `cd … &&` before them and nothing chained after. Only then do they run outside the sandbox, which they need to reach the remote and to sign commits.
## Remote ## Remote
Gitea at code.westerkam.org — `origin` = `git@code.westerkam.org:christian/claude-playground.git`. Gitea at code.westerkam.org — `origin` = `git@code.westerkam.org:christian/claude-playground.git`.