# Contributing to arah² arah² (the `araharah` repository) is a privacy-respecting, Telegram-first GPS navigator for the Kuala Lumpur metro — a grammY bot + Cloudflare Worker, a Vite/MapLibre Mini App, a design-token package, a curated KL gazetteer, and the routing engine behind the turn-by-turn guidance. Code is **GPL-3.0**; the map-derived data files are **ODbL-1.0**. This file tells you what belongs in the project, how to set up, and — most importantly — which gates a change must pass before a maintainer will look at it. The repo has no CI, so *you* are the CI. --- ## The charter — what belongs here, and what never will arah²'s core value: **a user finds a local destination via Telegram and gets there with reliable turn-by-turn guidance — tracked by nothing beyond what they explicitly opted in to.** The project charter (`.planning/PROJECT.md`, tracked in this repo) records the scope boundaries and, crucially, *why* each exclusion was made, so they don't get re-litigated every milestone. **In scope (v1):** the Telegram bot command surface, destination search over the curated KL gazetteer, the Mini App webview, route display, live turn-by-turn guidance with hybrid chat turn-cards, KL-metro tuning, and the Telemetry Rams design system. **Out of scope — the deferred-ideas law** (from `.planning/PROJECT.md` "Out of Scope"): - **Incident reports** (`/accident`, `/traffic`, `/hazard` + broadcasts) — navigation first; reports ship after the core works. v1 reports *nothing*. - **Gamification, points, leaderboards** — depends on reports; deferred with them. - **Regional clusters via Telegram groups** — unvalidated social hypothesis. - **Voice-to-text reporting, offline mode, anonymized public API** — future ideas. - **Push notifications beyond Telegram chat** (Resend email etc.) — Telegram itself is the channel. - **Independent mobile apps** — the Mini App webview flow *is* the product. - **Any paid-tier infrastructure.** The $0 law is a product constraint, not an aspiration — see [docs/BUDGETS.md](docs/BUDGETS.md). IDEAS.md's RabbitMQ, Prometheus/Grafana, Neon, and Resend entries are aspirational; do not wire them in. If your proposal touches any of these, the bar is high: it needs a new charter decision, not just a PR. Architecture seams for the deferred work exist deliberately (e.g. the `SearchSource` interface), but the seams stay unused. --- ## Development setup Read [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md) for prerequisites and first-run instructions, and [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the full local development setup. The short version: 1. Clone and install — npm workspaces install all five packages at once: ```bash git clone http://gitmirah:3000/c-fu/arah2.git cd arah2 npm install ``` 2. Copy `bot/.dev.vars.example` to `bot/.dev.vars` and fill in `BOT_TOKEN`, `WEBHOOK_SECRET`, and `WEBHOOK_URL` (gitignored; never commit it). 3. `npm run build` — theme tokens first, then the web bundle (the theme build is an input to the web build). 4. `npm run dev` — Worker on . There is no linter, formatter, or commit-hook setup to install — see [Code standards](#code-standards) below. --- ## Code standards - **No ESLint, Prettier, Biome, or `.editorconfig` exists.** There is no `lint` or `format` script in any workspace and no CI to enforce one. Style is a review-time convention; the mechanically enforced gates are listed in the next section. Match the existing source: explicit `.ts` extensions on relative imports, `node:`-prefixed builtins, `.mjs` for scripts/tests, heavy header comments stating what a file is and which decision IDs it satisfies. - **TypeScript strictness is real:** both `bot/tsconfig.json` and `web/tsconfig.json` use `strict` + `noUncheckedIndexedAccess` + `verbatimModuleSyntax`; `search/` and `routing/` follow the same shape. `tsc --noEmit` (`npm run *:typecheck`) is the gate. - **Design tokens:** never hardcode a color, spacing, font, or radius that belongs to a token. `theme/src/tokens.css` is the single source of truth; `npm run audit` flags off-token values in rendered CSS. Never edit `stitch_minimalist_route_navigator/` — the mockups are read-only reference. - **All user-facing copy lives in the i18n strings files** (`bot/src/i18n/strings.ts`, `web/src/i18n/strings.ts`) with `en`/`ms` locale parity (gate `COPY1`). No inline copy in shell modules (gate `COPY5`). --- ## The gates — what must be green before review There is **no CI pipeline** (no `.github/`, no `.gitea/` workflows). Run these by hand before opening a change, and paste the output into your PR: ```bash npm run build # FIRST — web:test's BA cases read web/dist npm run bot:test # bot/test/runner.mjs npm run web:test # web/test/runner.mjs npm run search:test # search/test/runner.mjs npm run routing:test # routing/test/runner.mjs npm test -w theme # theme/scripts/audit.test.mjs (no root passthrough) npm run bot:typecheck npm run web:typecheck npm run search:typecheck npm run routing:typecheck npm run audit # off-token CSS audit — only if you touched theme/ or rendered CSS ``` Order matters in one place: **`npm run build` before `npm run web:test`**, or the build-artifact gates (BA1–BA10) fail with a "build first" message. ### The repo's law enforcement: the two audit CLIs Two maintainer-time tools (nothing in any `src` tree imports them) enforce the project's non-negotiable laws. **A PR that makes either one fail is a regression, full stop.** - **`npm run privacy-audit`** ([docs/PRIVACY-AUDIT.md](docs/PRIVACY-AUDIT.md), `scripts/privacy-audit.mjs` + `scripts/lib/`) — the privacy law: - **TTL-only storage:** every KV `.put(` must carry `expirationTtl` on the same line, referencing a named `SCREAMING_SNAKE` constant (the `kv-ttl` gate; PG1 is the suite-level twin). The four KV families are the complete storage surface. - **Category-only logs:** no log line may carry a coordinate, chat id, user id, token, initData payload, geometry, or serialized payload (the `log-vocabulary` gate). - **Zero telemetry:** no analytics, beacons, tracking pixels, third-party CDN, or error-reporting host — in source *and* in the shipped `web/dist` bundle (`storage-api`, `runtime-hosts`, `bundle-telemetry` gates). v1 has nothing to opt into. - **`npm run budget-audit`** ([docs/BUDGETS.md](docs/BUDGETS.md), `scripts/budget-audit.mjs` + `scripts/lib/budget-gates.mjs`) — the $0 law: bundle sizes, KV write classes (the cron tick writes **zero** — source-proven), subrequest inventories, and the golden corpora, all re-derived against the Cloudflare free-plan ceilings. If you add a route class or a KV write path, its inventory row must follow the code. Both audits are GPL-3.0 and reusable by forks; rerun them over your own tree before your first driver. The `--live` legs (KV expiry, cache headers) need Cloudflare credentials and are maintainer-time. ### The capture harness For any Mini App UI change, `npm run web:capture` (Playwright, against the harness build in `web/dist-harness/`) is the verification surface: screenshot legs over every boot/route/guidance state, a DSGN-02 side-by-side against the committed mockup, and the GEO-03 runtime storage probe that fails if any app-controlled storage surface is non-empty after boot-to-live. Include the relevant captures in your PR. Outputs are gitignored; regenerate, never commit. --- ## Branch and commit conventions - The active branch is **`main`**. Work lands there via atomic commits; if you prefer a branch, `feat/`, `fix/`, and `docs/` prefixes match the commit vocabulary. No branch-protection or required-reviewer config exists. - Commit messages are **conventional-commit style with a phase-scoped type**: `type(phase): subject` — types `feat`, `fix`, `docs`, `chore`, `test`; the scope is a phase identifier (e.g. `07-06`). Examples from `git log`: ``` fix(07-06): launch-gate decisions — KV cron retune to 720/day (the $0 law holds) test(07-06): RED — SA6 doc-pointer static-text class vector (URL literals only) ``` Subject is lowercase, imperative, and carries an em-dash clause explaining the *why* or the observable failure. Reference decision IDs (`D-09`, `PRIV-02`, `GEO-03`) in parentheses when relevant. One concern per commit. --- ## Pull requests There is no PR template and no configured review process (this is a private Gitea repo — `git remote -v` points at `http://gitmirah:3000/c-fu/arah2.git`). If you open a PR there, reviewers look for: - **All gates green, pasted.** The full list from [the gates](#the-gates--what-must-be-green-before-review) above, plus `npm run privacy-audit` and `npm run budget-audit` if you touched any storage, logging, network, bundle, or KV surface — i.e. if in doubt, run both audits. - **The observable before/after**, stated like a commit subject: what was broken, what it does now. - **Gate-relevant surface called out explicitly.** Touched a KV write? Say which `expirationTtl` and which gate re-verified it. Touched `web/src`? `GEO3`/`COPY1`/`COPY5` re-run. Touched the command surface? `CS4`/`CS6` re-run. - **Screenshots** for Mini App changes (from `npm run web:build:harness && npm run web:capture`). - **No generated artifacts committed.** `theme/dist`, `web/dist`, `web/dist-harness`, and all `screenshots/` directories are gitignored by design — always rebuild. In particular, committed `theme/dist` would go stale because the web build consumes it at build time without vendoring. --- ## Licensing your contribution - **Code is GPL-3.0.** The full text is in [LICENSE](./LICENSE), and GPL-3.0 is a deliberate project law (decision D-04, per [LICENSES.md](./LICENSES.md)): a fork must be able to read, change, and redistribute the whole working system. There is **no CLA and no DCO configured** — no `Signed-off-by` lines exist in the commit history, and no sign-off mechanism is set up. By submitting a PR you agree your code contribution is licensed under GPL-3.0 alongside the rest of the project. If you add a new source file, a copyright header is welcome but not required. - **Map-derived data is ODbL-1.0, not GPL.** The curated gazetteer (`search/src/data/kl-gazetteer.json`), the route fixtures (`routing/src/data/route-fixtures.json`), and the derived road graph are all curated or derived from OpenStreetMap data — **© OpenStreetMap contributors, ODbL 1.0** (). The attribution clause travels with derived databases, so: - Never remove the `_license` / `_attribution` / `_notice` envelope fields from a data file. - Any new data file derived from OSM must carry the same envelope. - Any restructured/derived index of the gazetteer carries the same notice (ODbL §4.4 share-alike for Derivative Databases). Code and data are separately licensed — ODbL §2.3(a) excludes computer programs. - See [docs/ROAD-GRAPH.md](docs/ROAD-GRAPH.md) for the road-graph licensing surface. --- ## Reporting issues and proposing ideas There are no issue templates (no `.github/ISSUE_TEMPLATE/`). The repository lives on a private Gitea instance at `http://gitmirah:3000/c-fu/arah2` — use its issue tracker if you have access. For a bug report, include: - Steps to reproduce, expected vs actual behavior, and environment (which Telegram client surface — `?mock=` value if you used the harness — and OS). - The failing gate output, verbatim: the runners and audits print evidence strings that are meant to be quoted, never paraphrased. For a feature proposal, first check the charter above: if it's in the deferred list, argue why the deferral no longer holds, and expect that discussion to happen at the charter level (`.planning/PROJECT.md`), not in the PR. **Where decisions live.** Project-level decisions are recorded in `.planning/PROJECT.md` (charter, key decisions), `.planning/ROADMAP.md`, and per-phase documents under `.planning/phases/` — all tracked in the repo. The checked-in evidence records are the `docs/` files: [PRIVACY-AUDIT.md](docs/PRIVACY-AUDIT.md) (the signed privacy audit), [BUDGETS.md](docs/BUDGETS.md) (the measured budget record), [SEARCH-QUALITY.md](docs/SEARCH-QUALITY.md) and [ROAD-GRAPH.md](docs/ROAD-GRAPH.md) (validation records), and [KNOWN-LIMITATIONS.md](docs/KNOWN-LIMITATIONS.md). If a reviewer asks "where was this decided?", the answer is one of those files.