# claude-playground A tiny zero-dependency Node.js todo CLI, used as a sandbox for trying Claude Code features. ## Commands - `npm test` — run the test suite (built-in `node:test`, no install needed) - `npm start -- add "something"` / `npm start -- ls` — run the CLI (data in `todos.json`, override with `TODO_FILE`) ## Conventions - ES modules, Node >= 20, no third-party dependencies. - `src/todo.js` holds pure functions; keep I/O in `load`/`save` and `src/cli.js`. - Every behavior change gets a test in `test/`. - Commit messages: short imperative subject line. ## Workflow (required) Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`. 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. **Branch.** Run `git fetch origin` on its own, then `git switch --no-track -c / origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). `--no-track` stops the branch from tracking `main`. 2. **Commit and test.** Commit on the branch; `npm test` must pass before every push. 3. **Push.** Check that `git branch --show-current` isn't `main`, then run `git push -u origin HEAD`. 4. **Open the PR**, once per branch (see "Gitea API" below). 5. **Review** with `/code-review medium`. Name both the branch and the level, so every round diffs against `main` at the same depth. 6. **Comment** on the PR with the verdict. - 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 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 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 # Open a PR for the current branch (once). Write $TMPDIR/pr-title.txt and $TMPDIR/pr-body.md first. set -o pipefail; API=https://code.westerkam.org/api/v1/repos/christian/claude-playground jq -n --arg head "$(git branch --show-current)" --rawfile title "$TMPDIR/pr-title.txt" --rawfile body "$TMPDIR/pr-body.md" \ '{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 -er '"#\(.number) \(.html_url)"' # Comment on PR . 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}' \ | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues//comments" \ | jq -er .html_url ``` 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 Gitea at code.westerkam.org — `origin` = `git@code.westerkam.org:christian/claude-playground.git`.