From 4a40a63656efdd0164baf50242af25a161ed9ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 11 Nov 2026 22:24:08 +0100 Subject: [PATCH] docs: remove the second contributing note and its dead pointers CONTRIBUTING-AI.md pointed contributors at a CLAUDE.md "checked in and kept current"; that file is not in the repository, so the pointer and the twenty references to it in folder READMEs, docs and comments led nowhere. The note goes; its one lasting rule (whoever commits a change stands behind it, no real names, no live health figures, no secrets) moves into CONTRIBUTING.md. The folder READMEs keep their own content and drop the dead link, and a reference to the unpublished planning folder goes with it. The checked-in .mcp.json was a local editor config no workflow reads; it is untracked and ignored now. The translation tiers are described as machine-translated first drafts reviewed by the community, and the list names Russian, which it missed. --- .dockerignore | 1 - .gitignore | 7 +++---- .mcp.json | 8 -------- CONTRIBUTING-AI.md | 16 ---------------- CONTRIBUTING.md | 8 ++++---- docs/CONTRIBUTING-CLIENTS.md | 4 ++-- e2e/README.md | 2 +- lefthook.yml | 8 +------- prisma/README.md | 2 +- scripts/README.md | 2 +- src/README.md | 2 +- src/__tests__/labs-module-ai-gate-guard.test.ts | 3 +-- src/__tests__/session-surface-guard.test.ts | 2 +- src/__tests__/sharing-surface-guard.test.ts | 2 +- src/app/README.md | 2 +- src/components/README.md | 2 +- src/lib/README.md | 2 +- src/lib/jobs/insight-pregenerate.ts | 2 +- src/lib/jobs/insight-status-generate.ts | 2 +- src/lib/jobs/period-narrative-warm.ts | 2 +- src/lib/logging/redact.ts | 2 +- src/lib/mood/context.ts | 2 +- src/lib/validations/mental-health.ts | 3 +-- 23 files changed, 26 insertions(+), 60 deletions(-) delete mode 100644 .mcp.json delete mode 100644 CONTRIBUTING-AI.md diff --git a/.dockerignore b/.dockerignore index 1901f009e..b95584b3f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -82,7 +82,6 @@ README.md CHANGELOG.md CLAUDE.md CONTRIBUTING.md -CONTRIBUTING-AI.md CODE_OF_CONDUCT.md SECURITY.md # LICENSE / LICENSE.md are deliberately NOT ignored: the PolyForm diff --git a/.gitignore b/.gitignore index 655bb269e..55ff4b2ad 100644 --- a/.gitignore +++ b/.gitignore @@ -72,11 +72,10 @@ next-env.d.ts .cursor/ .codex/ .aider* +.mcp.json -# Planning scratch — all internal per-release reports, QA findings, ad-hoc -# backlog, and the iOS coordination channel. These are internal working -# notes; everything user-facing lives in the public surfaces (README, -# CHANGELOG, docs/). Kept on disk for the working session, never tracked. +# Internal working notes (per-release reports, backlog, iOS coordination). +# Everything user-facing lives in README, CHANGELOG and docs/. /.planning/ # Docs scratch — temporary spec drafts that live in the repo only diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 29585572e..000000000 --- a/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "shadcn": { - "command": "npx", - "args": ["-y", "shadcn@latest", "mcp"] - } - } -} diff --git a/CONTRIBUTING-AI.md b/CONTRIBUTING-AI.md deleted file mode 100644 index 5cd662a4e..000000000 --- a/CONTRIBUTING-AI.md +++ /dev/null @@ -1,16 +0,0 @@ -# Contributing with AI tooling - -HealthLog is built with AI assistance, and contributions made the same way are welcome. This file used to carry a copy of the internal conventions document; the copy aged badly and started contradicting the codebase, so it now points at the living sources instead. - -## Where the real conventions live - -- `CLAUDE.md` at the repo root is the maintainer's operating manual for this codebase: architecture walk, code conventions, security patterns, test commands, the do-not list. It is checked in and kept current; whatever an AI agent (or a human) needs to work on the server is in there. -- `docs/CONTRIBUTING-CLIENTS.md` covers building a client app (Android, iOS, CLI) against the server: the API contract, auth from a client's point of view, sync semantics, and how coordination between repos works. -- `docs/api/openapi.yaml` is the wire contract, generated from the server's Zod schemas. CI fails on drift, so it is always accurate for the commit it sits in. - -## Ground rules, whatever tooling you use - -- The human who commits a change has read it and stands behind it. The git history knows authors, not tools: no AI attribution trailers, no assistant vocabulary in commit messages, PR text, or release notes. -- No real names of users or reporters in committed text, and no live health figures anywhere, including fixtures and examples. Invent data. -- No secrets in code, fixtures, or docs, and nothing secret-shaped either. -- Run the gates before pushing (`pnpm typecheck`, `pnpm lint`, `pnpm test`) and fix what they find rather than suppressing it. A guard that fails is telling you something; the fix goes in the code, not in the guard. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78d3286d8..f3bf2d402 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,7 +76,7 @@ pnpm typecheck && pnpm lint && pnpm test && pnpm format:check && pnpm build - **shadcn/ui** components (new-york style) for UI primitives. - **Dracula theme** -- dark mode is the default; a `light`/`system` toggle is exposed in the topbar. Chart and accent colors use the `--dracula-*` CSS tokens so both themes stay coherent. -See `CONTRIBUTING-AI.md` for detailed architecture and patterns. +Whoever commits a change has read it and stands behind it. Committed text names no real users or reporters and carries no live health figures; fixtures and examples use invented data. Nothing secret, and nothing secret-shaped, goes into code, fixtures or docs. ## Database Changes @@ -121,7 +121,7 @@ Contributors: branch off `main`; PRs target `main`. ## Translations -HealthLog ships with English (`en`) and German (`de`) maintained by the project, plus French (`fr`), Spanish (`es`), Italian (`it`), Polish (`pl`), Korean (`ko`), Dutch (`nl`), and Brazilian Portuguese (`pt-BR`) that are **AI-initial, community-maintained**. The maintained locales are reviewed line-by-line; the community-maintained ones surface a small banner inviting users to improve them. +HealthLog ships with English (`en`) and German (`de`) maintained by the project, plus French (`fr`), Spanish (`es`), Italian (`it`), Polish (`pl`), Korean (`ko`), Dutch (`nl`), Brazilian Portuguese (`pt-BR`) and Russian (`ru`), which started as machine-translated first drafts and are reviewed by the community. The maintained locales are reviewed line by line; the community-reviewed ones show a small banner inviting users to improve them. ### Improving an existing locale @@ -168,9 +168,9 @@ To propose a validated version for a language that has none, or a better source If you want to introduce a locale that isn't shipped (e.g. `sv`, `cs`): -1. Open an issue first using the **Translation request** template. We'll confirm the locale is in scope and decide whether it joins the AI-initial-community-maintained tier or the maintained tier. +1. Open an issue first using the **Translation request** template. We'll confirm the locale is in scope and decide whether it joins the community-reviewed tier or the maintained tier. 2. Copy `messages/en.json` to `messages/.json`. Questionnaire text is not in it; the new language needs a wording file per instrument instead (see Questionnaire wording above), which step 4 makes impossible to forget. -3. Translate. AI assistance is welcome as a starting point but the PR description should note whether it's an AI-initial pass or a fully human-reviewed one — that information drives the banner state. +3. Translate. A machine translation is fine as a starting point, but the PR description should say whether the pass is machine-translated or fully reviewed by someone who reads the language; that decides the banner state. 4. Add the locale to `locales` in `src/lib/i18n/config.ts` and to `locales` in `project.inlang/settings.json`, then run `pnpm typecheck`. Every per-locale table that still lacks the new locale fails to compile, and that list is the work: date and number formatting, the reply-language footers of the Coach and insights prompts, the outbound safety banks, the prose-grounding patterns, the no-key fallback copy and the questionnaire wording registry. Include those in the same PR. 5. Run `pnpm test` before submitting. The i18n guards hold the new bundle to the English keys and placeholders. diff --git a/docs/CONTRIBUTING-CLIENTS.md b/docs/CONTRIBUTING-CLIENTS.md index e2f627e8c..3f84d6276 100644 --- a/docs/CONTRIBUTING-CLIENTS.md +++ b/docs/CONTRIBUTING-CLIENTS.md @@ -1,6 +1,6 @@ # Building a native client against HealthLog -Working notes for anyone building a client app (Android, iOS, CLI, anything else) against the HealthLog server, with or without AI tooling. Everything in here is a rule we actually follow between the server and the iOS app, written down so a second client does not have to rediscover it. Pair it with `CLAUDE.md` (server-side conventions) and `docs/api/openapi.yaml` (the wire contract). +Working notes for anyone building a client app (Android, iOS, CLI, anything else) against the HealthLog server. Everything in here is a rule we actually follow between the server and the iOS app, written down so a second client does not have to rediscover it. Pair it with [`CONTRIBUTING.md`](../CONTRIBUTING.md) (server-side conventions) and `docs/api/openapi.yaml` (the wire contract). ## The contract, and where truth lives @@ -43,7 +43,7 @@ The corollary: if a resolved value you need is missing from a payload, the fix i ## Ground rules for the repo itself -The voice-and-privacy rules from `CLAUDE.md` apply to any repo under the project, including a client repo: commit messages and public text carry no tooling attribution and no assistant vocabulary, no real names of users or reporters, and no live health figures, ever. Fixtures use invented data. If an AI tool wrote part of a change, the human who commits it has read it and stands behind it; the git history knows only authors. +The privacy rules apply to any repo under the project, including a client repo: commit messages and public text carry no real names of users or reporters and no live health figures, ever. Fixtures use invented data. Whoever commits a change has read it and stands behind it. Secrets never appear in code, fixtures, or examples, not even placeholder-shaped real ones. Anything user-identifying stays out of logs on the client just as it does on the server. diff --git a/e2e/README.md b/e2e/README.md index 202dd3dea..f6da63bf9 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -21,4 +21,4 @@ The Playwright config (`../playwright.config.ts`) builds the app (`next build`) Import `test` and `expect` from `./setup/test`, never from `@playwright/test` — types (`Page`, `Route`, …) still come from the package. The entry point extends Playwright's `test` with an automatic fixture that drops route handlers and page listeners after every test, so a handler left in flight cannot fail the test it belonged to once the context closes. `src/__tests__/e2e-route-cleanup-guard.test.ts` enforces the import boundary. -Assert against stable `data-*` attributes (`data-slot`, `data-display-step`, root display attrs), not viewport-dependent visible text. Responsive `sm:hidden` UI changes break `getByText` on desktop while the underlying element is still present — anchoring on data attributes keeps the specs stable across breakpoints. See [`../CLAUDE.md`](../CLAUDE.md). +Assert against stable `data-*` attributes (`data-slot`, `data-display-step`, root display attrs), not viewport-dependent visible text. Responsive `sm:hidden` UI changes break `getByText` on desktop while the underlying element is still present — anchoring on data attributes keeps the specs stable across breakpoints. diff --git a/lefthook.yml b/lefthook.yml index d193d5b25..77a797709 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -16,13 +16,7 @@ # Per project policy (`--no-verify` is banned), prefer fixing the # finding over bypassing the hook. -# Commit-message guard: the project voice rule (CLAUDE.md, "Voice and -# privacy") states that commits are authored by the maintainer's git -# identity alone and carry no tooling co-author trailer or session link. -# Editors and CLIs add those trailers by default, and a message that -# picks one up is only removable from the object store by a history -# rewrite — so the check belongs here, before the commit exists, not in -# a review pass afterwards. +# Reject tool attribution trailers. commit-msg: commands: no-tooling-trailer: diff --git a/prisma/README.md b/prisma/README.md index fdea762fa..7b11368e9 100644 --- a/prisma/README.md +++ b/prisma/README.md @@ -23,6 +23,6 @@ Number a new migration with the next free prefix. The production entrypoint runs - Encryption is fail-closed: `*Encrypted` columns are written through `@/lib/crypto` (AES-256-GCM, versioned key ids); never store plaintext in an encrypted column. Key rotation is `scripts/rotate-encryption-key.ts` + `docs/ops/encryption-key-rotation.md`. - The generated client under `src/generated/prisma` is build output — do not edit it. -- Schema-touching changes that affect the API surface must regenerate `docs/api/openapi.yaml` (see [`../CLAUDE.md`](../CLAUDE.md)). +- Schema-touching changes that affect the API surface must regenerate `docs/api/openapi.yaml` (`pnpm openapi:generate`; CI fails on drift). Related: [`../src/lib/README.md`](../src/lib/README.md) (`db.ts`, `rollups/`), [`../docs/ops/`](../docs/ops/) (migrations + backup runbooks). diff --git a/scripts/README.md b/scripts/README.md index a2640cd17..34949a93e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -42,4 +42,4 @@ typecheck. ## Conventions -See [`../CLAUDE.md`](../CLAUDE.md) "Tests and commands" + "Self-hosting gotchas". Operator runbooks that drive these scripts live in [`../docs/ops/`](../docs/ops/). +Operator runbooks that drive these scripts live in [`../docs/ops/`](../docs/ops/). diff --git a/src/README.md b/src/README.md index cdbfccf46..7449a48d0 100644 --- a/src/README.md +++ b/src/README.md @@ -23,4 +23,4 @@ client → src/proxy.ts → src/app//route.ts → src/lib/api-handler.ts ## Conventions -See [`../CLAUDE.md`](../CLAUDE.md) for the full set. The load-bearing ones: RSC by default (`"use client"` only when a hook/state/browser API needs it); every API route wraps `apiHandler` and parses its body with Zod `safeParse`; `userId` is always narrowed from session or Bearer, never accepted as a body field; TanStack Query keys come only from `src/lib/query-keys/`; outbound HTTP goes through `src/lib/safe-fetch.ts`. +The load-bearing ones: RSC by default (`"use client"` only when a hook/state/browser API needs it); every API route wraps `apiHandler` and parses its body with Zod `safeParse`; `userId` is always narrowed from session or Bearer, never accepted as a body field; TanStack Query keys come only from `src/lib/query-keys/`; outbound HTTP goes through `src/lib/safe-fetch.ts`. diff --git a/src/__tests__/labs-module-ai-gate-guard.test.ts b/src/__tests__/labs-module-ai-gate-guard.test.ts index 70ca38c36..eaa74e844 100644 --- a/src/__tests__/labs-module-ai-gate-guard.test.ts +++ b/src/__tests__/labs-module-ai-gate-guard.test.ts @@ -22,8 +22,7 @@ * that file does, or is listed below by file and function with the reason * its rows cannot reach a prompt. A new reader fails here until it does one * of those. The data routes are not in scope: a module decides what a model - * is given, not what the owner can read (`CLAUDE.md`, "An AI capability - * gates model work"). + * is given, not what the owner can read. * * The check is per read, not per file. It used to ask whether a file that * reads the table mentions the gate anywhere, so one ungated read in a file diff --git a/src/__tests__/session-surface-guard.test.ts b/src/__tests__/session-surface-guard.test.ts index d338495d4..e4c195931 100644 --- a/src/__tests__/session-surface-guard.test.ts +++ b/src/__tests__/session-surface-guard.test.ts @@ -43,7 +43,7 @@ const SRC = join(process.cwd(), "src"); * Every non-test `.ts` / `.tsx` under `src/app/`, which is the whole of the * request-handling tree: API routes, RSC pages, layouts, route handlers. * The generated Prisma client is excluded by construction (it is not under - * `app/`) and never read — see CLAUDE.md. + * `app/`) and never read. */ function appFiles(): string[] { return walkSourceFiles(SRC, { floor: 3000 }) diff --git a/src/__tests__/sharing-surface-guard.test.ts b/src/__tests__/sharing-surface-guard.test.ts index 4c5c12a33..d86d5e959 100644 --- a/src/__tests__/sharing-surface-guard.test.ts +++ b/src/__tests__/sharing-surface-guard.test.ts @@ -161,7 +161,7 @@ const BARE_REQUIRE_AUTH_FLOOR = 200; * omits the three routes under `src/app/.well-known/` — three request handlers * that a glob-based sweep reports as if they did not exist. Leg (a) pins one of * them by name so this cannot quietly regress. Names only; nothing under - * `generated/` is ever read (CLAUDE.md). + * `generated/` is ever read. */ function allPaths(): string[] { return readdirSync(SRC, { recursive: true }) diff --git a/src/app/README.md b/src/app/README.md index 889bc66c4..acce7939c 100644 --- a/src/app/README.md +++ b/src/app/README.md @@ -22,4 +22,4 @@ Every endpoint wraps its handler in `apiHandler` (`@/lib/api-handler`), parses a ## Conventions -See [`../../CLAUDE.md`](../../CLAUDE.md). The request-flow walk in `.planning/codebase/arch.md` annotates each layer with file:line citations. Related dirs: handlers compose [`../lib/`](../lib/README.md); pages render [`../components/`](../components/README.md). +Related dirs: handlers compose [`../lib/`](../lib/README.md); pages render [`../components/`](../components/README.md). diff --git a/src/components/README.md b/src/components/README.md index eed13a68f..1537a3fae 100644 --- a/src/components/README.md +++ b/src/components/README.md @@ -21,4 +21,4 @@ React components. The UI primitives are shadcn/ui (new-york style, zinc base) ov ## Conventions -Kebab-case filenames; React component exports stay PascalCase regardless. (A handful of PascalCase-filename outliers under `medications/` + `onboarding/` are pre-existing drift, noted in [`../../CLAUDE.md`](../../CLAUDE.md).) Every `t()` call needs a matching key in `messages/en.json` — guarded by `i18n-call-site-coverage.test.ts`. Data reads go through TanStack Query with keys from `@/lib/query-keys` and unwrap `(await res.json()).data`. Tests live in [`__tests__/`](__tests__/). +Kebab-case filenames; React component exports stay PascalCase regardless. Every `t()` call needs a matching key in `messages/en.json` — guarded by `i18n-call-site-coverage.test.ts`. Data reads go through TanStack Query with keys from `@/lib/query-keys` and unwrap `(await res.json()).data`. Tests live in [`__tests__/`](__tests__/). diff --git a/src/lib/README.md b/src/lib/README.md index fcffe12e9..50b41508a 100644 --- a/src/lib/README.md +++ b/src/lib/README.md @@ -30,4 +30,4 @@ The server and runtime toolkit. Everything that is not a page, component, or rou ## Conventions -Kebab-case filenames. See [`../../CLAUDE.md`](../../CLAUDE.md) "Code conventions" and "Security-relevant patterns"; the critical-files map there cites the exact entry points. Most modules carry tests in a sibling `__tests__/`. +Kebab-case filenames. Most modules carry tests in a sibling `__tests__/`. diff --git a/src/lib/jobs/insight-pregenerate.ts b/src/lib/jobs/insight-pregenerate.ts index a56101dca..59f453448 100644 --- a/src/lib/jobs/insight-pregenerate.ts +++ b/src/lib/jobs/insight-pregenerate.ts @@ -46,7 +46,7 @@ * provider-less account costs one cheap chain-resolve and no LLM call. * * Recurring pg-boss task — never runs inside an HTTP request and never - * shells out to `tsx` (CLAUDE.md DO-NOTs). + * shells out to `tsx` (the production image has none). */ import { activeAccount } from "@/lib/account-deletion/active-account"; import { createHash, randomUUID } from "node:crypto"; diff --git a/src/lib/jobs/insight-status-generate.ts b/src/lib/jobs/insight-status-generate.ts index 33b525017..a3ea4fa23 100644 --- a/src/lib/jobs/insight-status-generate.ts +++ b/src/lib/jobs/insight-status-generate.ts @@ -16,7 +16,7 @@ * polls the read-only GET until it lands. * * Recurring/event pg-boss task — never runs inside an HTTP request and - * never shells out to `tsx` (CLAUDE.md DO-NOTs). Registered in the + * never shells out to `tsx` (the production image has none). Registered in the * reminder-worker `allQueues` loop + a `boss.work` handler; a queue built * but never registered silently never drains (the v1.4.37 W10 incident), * so a guard test pins the registration. diff --git a/src/lib/jobs/period-narrative-warm.ts b/src/lib/jobs/period-narrative-warm.ts index f01162781..065154899 100644 --- a/src/lib/jobs/period-narrative-warm.ts +++ b/src/lib/jobs/period-narrative-warm.ts @@ -17,7 +17,7 @@ * so a provider-less / sparse account costs only the context build. * * Recurring pg-boss task — never runs inside an HTTP request, never shells out - * to `tsx` (CLAUDE.md DO-NOTs). + * to `tsx` (the production image has none). */ import { ACTIVE_ACCOUNT } from "@/lib/account-deletion/active-account"; import type { PrismaClient } from "@/generated/prisma/client"; diff --git a/src/lib/logging/redact.ts b/src/lib/logging/redact.ts index 9502be731..f00f13683 100644 --- a/src/lib/logging/redact.ts +++ b/src/lib/logging/redact.ts @@ -150,7 +150,7 @@ export function redactSecrets(input: string): string { // calendar-feed link (#1350). // Same boundary-aware shape as the `sk-` rule. The // idempotency replay-cache already rejects bodies containing - // these prefixes (CLAUDE.md headless-client-API patterns); this + // these prefixes; this // is the matching egress guard for log/error surfaces. .replace(/(^|[^A-Za-z0-9])hl[kpresvf]_[A-Fa-f0-9]{32,}/g, "$1[REDACTED]") // v1.22.0 — the MCP OAuth bridge artifacts: the authorization code diff --git a/src/lib/mood/context.ts b/src/lib/mood/context.ts index a976428f3..b0c0ffb04 100644 --- a/src/lib/mood/context.ts +++ b/src/lib/mood/context.ts @@ -10,7 +10,7 @@ * both directions. * * Everything is built field by field from the parsed body. Nothing is spread - * from a request (`CLAUDE.md`, no mass assignment), and the two multi-select + * from a request (no mass assignment), and the two multi-select * lists go through the vocabulary module's encoder rather than a local * `JSON.stringify`. */ diff --git a/src/lib/validations/mental-health.ts b/src/lib/validations/mental-health.ts index 6cbbd0777..e2dc0304a 100644 --- a/src/lib/validations/mental-health.ts +++ b/src/lib/validations/mental-health.ts @@ -3,8 +3,7 @@ * (PHQ-9 / GAD-7; v1.27.9 adds WHO-5 + SCI). The item answers are small * integers whose ceiling is instrument-specific (PHQ/GAD 0–3, SCI 0–4, * WHO-5 0–5); the array length must match the instrument. `userId` is NEVER - * a body field — it is narrowed from the session in the handler (CLAUDE.md - * convention). + * a body field — it is narrowed from the session in the handler. */ import { z } from "zod/v4"; import {