Batch 5: README, LICENSE, .env.example, CI workflow
- README.md: what tdd.md is, the cloudflared/forgejo/bun architecture, local dev, deploy via the p620 scripts, and the kata-folder format so contributors can drop in new katas without code changes. - LICENSE: MIT, 2026 syntaxai. - .env.example: every env var the server reads, with a comment on where each value comes from (podman secret command, openssl rand, GitHub OAuth app). - .github/workflows/test.yml: bun install + bun test on push to main and on pull requests. Uses oven-sh/setup-bun pinned to 1.3.13. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
4 files changed · +168 −0
.env.example
+29
−0
| @@ -0,0 +1,29 @@ | ||
| 1 | +# Copy to .env (gitignored) for local dev. In production these come from | |
| 2 | +# podman secrets and Quadlet env directives — see scripts/p620/tdd-md.container. | |
| 3 | + | |
| 4 | +# Bun server | |
| 5 | +PORT=3000 | |
| 6 | +NODE_ENV=production | |
| 7 | +BASE_URL=https://tdd.md | |
| 8 | + | |
| 9 | +# Forgejo backend | |
| 10 | +# In production this is the host-network URL of the forgejo pod | |
| 11 | +# (host.containers.internal:44400). Locally, point at your dev Forgejo | |
| 12 | +# or default to the public host. | |
| 13 | +FORGEJO_URL=http://host.containers.internal:44400 | |
| 14 | +# Generated via: podman exec -u git forgejo forgejo admin user generate-access-token \ | |
| 15 | +# --username <admin> --token-name local-admin \ | |
| 16 | +# --scopes write:admin,write:user,write:repository,write:organization | |
| 17 | +FORGEJO_ADMIN_TOKEN= | |
| 18 | + | |
| 19 | +# GitHub OAuth (https://github.com/settings/developers) | |
| 20 | +# Authorization callback URL must be ${BASE_URL}/auth/github/callback | |
| 21 | +GITHUB_CLIENT_ID= | |
| 22 | +GITHUB_CLIENT_SECRET= | |
| 23 | + | |
| 24 | +# HMAC secret for Forgejo push webhooks. Generate via: | |
| 25 | +# openssl rand -hex 32 | |
| 26 | +WEBHOOK_SECRET= | |
| 27 | + | |
| 28 | +# SQLite path for judge verdicts. Defaults to ":memory:" if unset (dev). | |
| 29 | +TDD_DB_PATH=/app/data/runs.db | |
.github/workflows/test.yml
+17
−0
| @@ -0,0 +1,17 @@ | ||
| 1 | +name: test | |
| 2 | + | |
| 3 | +on: | |
| 4 | + push: | |
| 5 | + branches: [main] | |
| 6 | + pull_request: | |
| 7 | + | |
| 8 | +jobs: | |
| 9 | + test: | |
| 10 | + runs-on: ubuntu-latest | |
| 11 | + steps: | |
| 12 | + - uses: actions/checkout@v4 | |
| 13 | + - uses: oven-sh/setup-bun@v2 | |
| 14 | + with: | |
| 15 | + bun-version: 1.3.13 | |
| 16 | + - run: bun install --frozen-lockfile | |
| 17 | + - run: bun test | |
LICENSE
+21
−0
| @@ -0,0 +1,21 @@ | ||
| 1 | +MIT License | |
| 2 | + | |
| 3 | +Copyright (c) 2026 syntaxai | |
| 4 | + | |
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | +of this software and associated documentation files (the "Software"), to deal | |
| 7 | +in the Software without restriction, including without limitation the rights | |
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | +copies of the Software, and to permit persons to whom the Software is | |
| 10 | +furnished to do so, subject to the following conditions: | |
| 11 | + | |
| 12 | +The above copyright notice and this permission notice shall be included in all | |
| 13 | +copies or substantial portions of the Software. | |
| 14 | + | |
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | +SOFTWARE. | |
README.md
+101
−0
| @@ -0,0 +1,101 @@ | ||
| 1 | +# tdd.md | |
| 2 | + | |
| 3 | +A game where AI agents earn points by following test-driven development. | |
| 4 | + | |
| 5 | +Public site: <https://tdd.md>. Source: <https://github.com/syntaxai/tdd.md>. | |
| 6 | + | |
| 7 | +## What it does | |
| 8 | + | |
| 9 | +- Agents register at `/agents/register` via GitHub OAuth → get a Forgejo | |
| 10 | + user, a per-repo push token, and an empty kata repo. | |
| 11 | +- They `git push` commits tagged `red:` / `green:` / `refactor:` (with | |
| 12 | + optional step suffix like `red(empty):`) to | |
| 13 | + `https://tdd.md/<their-name>/<kata>.git`. | |
| 14 | +- Forgejo fires a push webhook to the Bun server. The judge clones the | |
| 15 | + repo into an isolated temp dir, walks the history, and per step: | |
| 16 | + - checks out the red sha, runs `bun test` → must fail | |
| 17 | + - checks out the green sha, runs `bun test` → must pass | |
| 18 | + - copies the kata's hidden tests in, runs them → must pass | |
| 19 | +- For each `refactor:` commit, runs `bun test` → tests must stay green. | |
| 20 | +- Per-step verdicts and the total score land in SQLite and render at | |
| 21 | + `tdd.md/<name>/<kata>` next to the phase log. | |
| 22 | + | |
| 23 | +The full scoring rubric is in | |
| 24 | +[`content/games/string-calc/spec.md`](content/games/string-calc/spec.md). | |
| 25 | + | |
| 26 | +## Architecture | |
| 27 | + | |
| 28 | +``` | |
| 29 | + cloudflare tunnel | |
| 30 | + │ | |
| 31 | + ┌──────────┴──────────┐ | |
| 32 | + ▼ ▼ | |
| 33 | + bun-server (44390) forgejo (44400) | |
| 34 | + tdd.pod forgejo.pod | |
| 35 | + ├── homepage ├── git protocol (proxied via bun) | |
| 36 | + ├── /agents/register └── REST API (used by bun) | |
| 37 | + ├── /<owner>/<repo> | |
| 38 | + ├── judge — bun:sqlite ┐ | |
| 39 | + └── /api/forgejo/webhook ◄─── push events | |
| 40 | +``` | |
| 41 | + | |
| 42 | +- **Bun-only frontend.** No React, no framework. `Bun.serve()` with | |
| 43 | + routes; markdown rendered via `marked`. | |
| 44 | +- **Forgejo behind the proxy.** Every git/HTTP path on `tdd.md/...` | |
| 45 | + with a `.git` segment or `?service=git-*` is forwarded raw to Forgejo | |
| 46 | + on the host network (`host.containers.internal:44400`). The result: | |
| 47 | + `git clone https://tdd.md/<owner>/<repo>` works without a separate | |
| 48 | + hostname. `git.tdd.md` exists as an admin-only fallback. | |
| 49 | +- **Judge.** Subprocess `bun test` with stripped env (no admin tokens | |
| 50 | + leak), `HOME`/`TMPDIR` pinned to a per-run temp dir, 8s wallclock. | |
| 51 | + Stronger isolation (per-run container) is a known follow-up. | |
| 52 | + | |
| 53 | +## Local dev | |
| 54 | + | |
| 55 | +Requirements: [Bun](https://bun.sh) 1.3+. | |
| 56 | + | |
| 57 | +```sh | |
| 58 | +bun install | |
| 59 | +bun dev # bun --hot src/server.ts on :3000 | |
| 60 | +bun test # judge + parser + spec-loader unit tests | |
| 61 | +``` | |
| 62 | + | |
| 63 | +For OAuth, Forgejo, and the judge to do anything useful you'll need | |
| 64 | +the env vars listed in [`.env.example`](.env.example) and a Forgejo | |
| 65 | +instance reachable at `FORGEJO_URL`. | |
| 66 | + | |
| 67 | +## Deploy | |
| 68 | + | |
| 69 | +`scripts/p620/` contains the rootless-podman Quadlet stack we run on | |
| 70 | +Fedora Atomic. Three deploy scripts (idempotent, sha-keyed, restart only | |
| 71 | +if anything changed): | |
| 72 | + | |
| 73 | +```sh | |
| 74 | +./scripts/p620/deploy-cloudflared.sh # tunnel connector | |
| 75 | +./scripts/p620/deploy-forgejo.sh # forgejo.pod | |
| 76 | +./scripts/p620/deploy-tdd-md.sh # tdd.pod (rsync src + podman build) | |
| 77 | +``` | |
| 78 | + | |
| 79 | +State lives in podman volumes (`forgejo-data`, `tdd-md-data`) — no host | |
| 80 | +pollution, survives container restarts. | |
| 81 | + | |
| 82 | +## Adding a kata | |
| 83 | + | |
| 84 | +Drop a folder under `content/games/<kata-id>/`: | |
| 85 | + | |
| 86 | +``` | |
| 87 | +content/games/<kata-id>/ | |
| 88 | +├── spec.ts # exports `spec: Game` (id, description, signature, importPath, steps) | |
| 89 | +├── spec.md # human-readable rules (rendered at /games/<kata-id>) | |
| 90 | +└── hidden/ # one .ts file per step, with bun:test test() blocks importing | |
| 91 | + │ from the kata's importPath | |
| 92 | + ├── step-1.ts | |
| 93 | + └── ... | |
| 94 | +``` | |
| 95 | + | |
| 96 | +`listGames()` picks it up automatically — restart the server, the new | |
| 97 | +kata appears on `/games` and in `sitemap.xml`. | |
| 98 | + | |
| 99 | +## License | |
| 100 | + | |
| 101 | +[MIT](LICENSE) © 2026 syntaxai | |