From c45d00c9cbb250647ba5847db0863e4f7d4bb4de Mon Sep 17 00:00:00 2001 From: Masao Kitamura Date: Sun, 4 Oct 2026 01:09:12 -0700 Subject: [PATCH] chore(repo): close the remaining engineering-practices gaps - Pin engines to Node 22.x so it matches .nvmrc and @types/node - Run the seed script in the migrations CI job as an entry-point smoke test - Stop the env error citing docs/SELF_HOSTING.md, which does not exist yet - Add CONTRIBUTING.md for the human process and link it from the README - Record new guardrails, the pending branch protection and the practices not adopted --- .github/workflows/ci.yml | 5 +++++ CONTRIBUTING.md | 28 ++++++++++++++++++++++++++++ README.md | 2 +- apps/web/src/lib/env.ts | 4 +--- docs/guardrails.md | 24 +++++++++++++++++++++++- package.json | 2 +- 6 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f585808..9df8337 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,3 +74,8 @@ jobs: pnpm --filter @copper/db generate git diff --exit-code -- packages/db/migrations test -z "$(git status --porcelain -- packages/db/migrations)" + # Started the way an operator starts it, so an entry point that dies on import fails here. + - name: The seed script starts and runs + run: pnpm seed + env: + SEED_DAYS: 1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..de20a8b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# Contributing + +The process for people. Rules for agents are in [AGENTS.md](AGENTS.md), and each rule's enforcement is in [docs/guardrails.md](docs/guardrails.md). + +## Set up + +```bash +nvm use # Node from .nvmrc +pnpm install # also installs the git hooks +pnpm dev:db # local Postgres, nothing to install +``` + +Copy `.env.example` to `apps/web/.env.local` and fill it in. + +## Hooks + +| Hook | Runs | Takes | +| --- | --- | --- | +| `.husky/pre-commit` | `lint-staged`: Biome formats the staged files and re-stages them | About a second | +| `.husky/pre-push` | `pnpm check`, then `pnpm build`: the same commands CI runs | A minute or so | + +Do not bypass them with `--no-verify`. CI runs the same checks and is the gate. + +## Pull requests + +One pull request per phase or issue. Paste the output of `pnpm verify` into the description. A human merges. + +`pnpm lint` only reports. `pnpm lint:fix` rewrites files, and is for you to run by hand, never a hook or CI. diff --git a/README.md b/README.md index 5fe4f39..55ea412 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Cookieless, privacy-first traffic analytics for all of your projects, on one dashboard. -The design is in [docs/PLAN.md](docs/PLAN.md). Agent and contributor rules are in [AGENTS.md](AGENTS.md). +The design is in [docs/PLAN.md](docs/PLAN.md). Agent rules are in [AGENTS.md](AGENTS.md), and the process for people is in [CONTRIBUTING.md](CONTRIBUTING.md). ## Develop diff --git a/apps/web/src/lib/env.ts b/apps/web/src/lib/env.ts index ba35c0b..757813c 100644 --- a/apps/web/src/lib/env.ts +++ b/apps/web/src/lib/env.ts @@ -42,9 +42,7 @@ export function readEnv(source: Record): Env { problems.push('INGEST_URL and INGEST_SECRET: required when COPPER_MODE=edge') } if (problems.length > 0) { - throw new Error( - `Missing configuration:\n ${problems.join('\n ')}\nSee .env.example and docs/SELF_HOSTING.md.`, - ) + throw new Error(`Missing configuration:\n ${problems.join('\n ')}\nSee .env.example.`) } return { DATABASE_URL: source.DATABASE_URL as string, diff --git a/docs/guardrails.md b/docs/guardrails.md index 775b5c3..93874cb 100644 --- a/docs/guardrails.md +++ b/docs/guardrails.md @@ -20,8 +20,30 @@ Each rule and the thing that enforces it. A rule with nothing in the right-hand | Installs do not change the lockfile in CI | `pnpm install --frozen-lockfile` in the shared setup action | | CI steps are pinned | Third-party actions referenced by commit hash; Dependabot moves them | | Secrets and database code stay out of the browser bundle | `import 'server-only'` first in `apps/web/src/lib/{env,db,auth}.ts` and `features/*/queries.ts` | -| Missing configuration fails by name at startup | `readEnv` in `apps/web/src/lib/env.ts` (`apps/web/test/env.test.ts`); `assertEnv` in `apps/ingest/src/index.ts` | +| Missing configuration fails by name at startup | `readEnv` in `apps/web/src/lib/env.ts` (`apps/web/test/env.test.ts`); `assertEnv` in `apps/ingest/src/index.ts`; the `DATABASE_URL` check first in the `migrate` and `seed` scripts | | The tracker stays under 1.5 KB gzipped | `packages/tracker/test/tracker.test.ts` | | No IP address or user agent is stored | `apps/ingest/test/shard.test.ts` ("stores neither the IP address nor the user agent") | | A failed flush never loses or double counts an hour | `apps/ingest/test/app.test.ts`, `packages/db/test/store.test.ts` | | Unknown site keys cannot keep the database awake | `ProjectDirectory` negative cache and lookup budget; `apps/ingest/test/app.test.ts` | +| Script entry points start outside the bundler and test runner | `migrations` CI job runs `migrate` and `seed` with `tsx` against a real Postgres | +| One Node major everywhere | `.nvmrc`, `engines` (`22.x`) and `@types/node` (`^22`) agree; CI reads `.nvmrc`; Dependabot holds `@types/node` majors | +| The dev overlay is not in screenshots | `devIndicators: false` in `apps/web/next.config.ts` | + +## Waiting on a person + +| Rule | What is missing | +| --- | --- | +| A red or missing check blocks the merge | Branch protection on `main` is a repository setting, not a file. Require `lint`, `docs`, `typecheck`, `test`, `build` and `migrations`, and require branches to be up to date. Then open one pull request that fails on purpose and confirm the merge is refused. | + +## Not adopted + +Practices from the [engineering practices guide](https://www.expeditionlabs.co/resources/engineering-practices/llms.txt) that were considered and left out, and what would change that. + +| Practice | Why not here | Adopt when | +| --- | --- | --- | +| Changed-files gate and job-level skip conditions | No job skips anything, so there is no skip logic to get wrong. CI has no path filters. | CI gets slow enough that docs-only changes should skip it | +| Scheduled infrastructure-dependent suite | Every CI job is hermetic: the only service is a Postgres container the job owns. | A suite needs a live Neon or Cloudflare account | +| Port and resource registry | No port is assigned by hand. `dev:db` takes `PORT` and Next picks a free port itself. | Two working copies routinely run at once | +| Canonical class ordering | Biome's `useSortedClasses` is still a nursery rule. | It is promoted to a stable group | +| Blame-ignore file | Biome has formatted the code since the first commit, so there is no mass reformat to hide. | A formatter or config change rewrites many files | +| Language-neutral migration runner per runtime | TypeScript is the only runtime that writes the schema. The migrations are already plain SQL files. | A second language writes to the database | diff --git a/package.json b/package.json index e514aac..c768287 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "MIT", "packageManager": "pnpm@12.4.1", "engines": { - "node": ">=22" + "node": "22.x" }, "scripts": { "dev": "turbo run dev",