# Contributing to tdd.md This file is the **on-ramp** for new contributors — human or agent. It links to canonical sources rather than restating them. If something here drifts from what the spec or workflow memory says, the canonical artifact wins; please file an issue. Two URLs, one file: - **Browse**: [`/GIT/tdd.md/blob/main/CONTRIBUTING.md`](/GIT/tdd.md/blob/main/CONTRIBUTING.md) — via the site's own source viewer - **Canonical**: [`/contributing`](/contributing) — the rendered permalink ## Before you start Read in this order: 1. [`/sama/v2`](/sama/v2) — the architectural spec (rules, profile, verifier, §5 metrics, §6 evolution policy) 2. [`/blog/2026-05/sama-v2-on-ramp-gap`](/blog/2026-05/sama-v2-on-ramp-gap) — why this file exists 3. [`/blog/2026-05/sama-v2-goal-chain-gap`](/blog/2026-05/sama-v2-goal-chain-gap) — why `/goal`s are now in git Then check the live state: [`/sama/v2/verify`](/sama/v2/verify) must report **7/7 ✓** before and after every merge. This is the load-bearing anti-fudge gate. ## How contribution works on this codebase This is a self-hosted project. The canonical surfaces are all on `tdd.md`: - **Read** the source: [`/GIT/tdd.md/tree/main`](/GIT/tdd.md/tree/main) - **Issues + discussion**: [git.tdd.md/syntaxai/tdd.md/issues](https://git.tdd.md/syntaxai/tdd.md/issues) (the self-hosted Forgejo) - **Clone (read-only)**: `git clone https://tdd.md/syntaxai/tdd.md.git` Editing the live site is **admin-only** by design — see [`src/b51_render_edit.ts`](/GIT/tdd.md/blob/main/src/b51_render_edit.ts). The admin uses the GitHub PR flow internally; external contributors engage through the issue tracker on Forgejo, not via GitHub PRs. ## The `/goal` workflow The contract for every PR on this site is a `/goal` slash command. The canonical workflow definition lives at [`/goals/migrate-historical-goals`](/goals/migrate-historical-goals); a 5-line summary: 1. User fires `/goal` with `Goal: ... Done when: ... Constraints ... Load-bearing files ...` structure 2. Agent's **first** action: write the verbatim text to `goals/.md` with `status: pending`, branch + commit 3. Implementation work happens in subsequent commits 4. PR body MUST include the verbatim `/goal` under a `## /goal` heading (defense-in-depth) 5. Final commit before deploy flips `status: shipped` + fills `merge_sha` Every shipped `/goal` is browseable at [`/goals`](/goals). ## SAMA layer convention The file prefix encodes the layer. The canonical definition is in [`/sama/v2 §1.1`](/sama/v2#11-layers); the table below shows concrete examples from this repo: | Prefix | Layer | Means | Example | |---|---|---|---| | `a*_` | 0 — Pure | data + types, no I/O | [`a31_blog.ts`](/GIT/tdd.md/blob/main/src/a31_blog.ts) | | `b*_` | 1 — Core | pure logic, no I/O | [`b32_sitemap.ts`](/GIT/tdd.md/blob/main/src/b32_sitemap.ts) | | `c*_` | 2 — Adapter | parses boundaries; DB, network, fs | [`c14_git.ts`](/GIT/tdd.md/blob/main/src/c14_git.ts) | | `d*_` | 3 — Entry | route handlers, app bootstrap | [`d21_app.ts`](/GIT/tdd.md/blob/main/src/d21_app.ts) | | `b51_` | 1 — Core (render) | pure HTML rendering | [`b51_render_layout.ts`](/GIT/tdd.md/blob/main/src/b51_render_layout.ts) | The Law (§1.2): imports flow downward only. The verifier rejects upward or sideways edges mechanically. ## How to add a blog post 1. Write `content/blog/.md` with the body 2. Add an entry to `ALL_POSTS` in [`src/a31_blog.ts`](/GIT/tdd.md/blob/main/src/a31_blog.ts) with `{ slug, title, description, date }` 3. The sitemap, blog index, and `/blog/` route pick it up automatically — registry is the single source of truth 4. Branch → PR → merge → deploy Worked example: [`/blog/2026-05/sama-v2-sitemap-implementation-plan`](/blog/2026-05/sama-v2-sitemap-implementation-plan). ## How to add a `/goal` Type `/goal` in conversation with the agent. The agent handles the rest per the workflow above. If you're not the admin: file an issue at [git.tdd.md/syntaxai/tdd.md/issues](https://git.tdd.md/syntaxai/tdd.md/issues) using the `Goal: ... Done when: ... Constraints ... Load-bearing files ...` shape. The admin (or agent on their behalf) fires it. ## How to add an image 1. Write `public/images/.svg` with a `https://tdd.md` watermark text somewhere on the canvas (typically bottom-right in a muted color) 2. Render PNG: `rsvg-convert -w 1200 -h 600 public/images/.svg -o public/images/.png` 3. Reference from markdown: `![alt](/images/.png?v=1)` The `/images/*` wildcard is served by the fallback handler in [`d21_handlers_fallback.ts`](/GIT/tdd.md/blob/main/src/d21_handlers_fallback.ts) — no per-image route registration needed. ## How to add a Layer-1 helper 1. Write `src/b32_.ts` — pure, no I/O, single export per concern 2. Write the **sibling** `src/b32_.test.ts` — required by [`§4.3 Modeled-tests`](/sama/v2#43-modeled-tests) 3. Caller (typically Layer 2 or Layer 3) imports from `./b32_.ts` Worked example: [`b32_sitemap.ts`](/GIT/tdd.md/blob/main/src/b32_sitemap.ts) + [`b32_sitemap.test.ts`](/GIT/tdd.md/blob/main/src/b32_sitemap.test.ts). ## How to add a top-level directory ⚠ **Important**: every new top-level directory needs a corresponding `COPY ./` line in [`Containerfile`](/GIT/tdd.md/blob/main/Containerfile). Without it, the directory doesn't ship with the runtime image and request-time disk reads return 404. This was discovered in PR #46 when `/goals/` returned 404 in production. Checklist for a new top-level dir: 1. Create the directory and any initial files 2. Add `COPY ./` to `Containerfile` (after the existing COPY lines) 3. Live-verify a file from the new directory loads after deploy ## Anti-fudge defaults Every PR satisfies these without exception: - **Site language**: English only. No Dutch in user-facing strings (blog bodies, banners, error text, page copy) - **Tests stay green**: `bun test` must pass; new behaviour gets a sibling test - **`/sama/v2/verify` stays 7/7 ✓**: structural choices must preserve the verifier verdict - **No `--no-verify`, no force-push to main**: hooks exist for a reason - **GitHub flow via `flatpak-spawn`**: the sandbox doesn't have `gh`; use `flatpak-spawn --host gh ...` ## Branch / deploy flow (admin path) The full sequence the agent (or admin) runs end-to-end: 1. `git checkout -b ` — branch from main 2. Implementation work + sibling tests 3. `git push -u origin ` + `gh pr create` (body includes `## /goal` verbatim per workflow) 4. `gh pr merge --merge --delete-branch` after CI green 5. `git checkout main && git pull origin main` — sync local 6. `git push p620 main` — push to self-hosted bare repo 7. `flatpak-spawn --host /var/home/scri/Documents/tdd.md/scripts/p620/deploy-tdd-md.sh` — rebuild + redeploy container 8. Live-verify: curl the changed URLs, confirm `/sama/v2/verify` still 7/7 ✓ ## When in doubt - The spec ([`/sama/v2`](/sama/v2)) is the single source of truth for architectural rules - The verifier ([`/sama/v2/verify`](/sama/v2/verify)) is the single source of truth for whether a commit is conformant - The `/goals` registry ([`/goals`](/goals)) is the single source of truth for what every PR was held against - This file links to all three. If it contradicts any of them, the canonical artifact wins; please file an issue at [git.tdd.md/syntaxai/tdd.md/issues](https://git.tdd.md/syntaxai/tdd.md/issues).