1
Contributing
araharah-executor edited this page 2026-10-25 12:36:12 +08:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Contributing to arah²

arah² (the arah² 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. 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 for prerequisites and first-run instructions, and docs/DEVELOPMENT.md for the full local development setup. The short version:

  1. Clone and install — npm workspaces install all five packages at once:

    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 http://localhost:8787.

There is no linter, formatter, or commit-hook setup to install — see 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:

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–BA6) 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, 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, 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 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, and GPL-3.0 is a deliberate project law (decision D-04, per 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 (https://opendatacommons.org/licenses/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 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 (the signed privacy audit), BUDGETS.md (the measured budget record), SEARCH-QUALITY.md and ROAD-GRAPH.md (validation records), and KNOWN-LIMITATIONS.md. If a reviewer asks "where was this decided?", the answer is one of those files.