← All documents

Contributing

What this project most needs is local knowledge, not code.

That is the opposite of most repositories, and it is not a polite way of saying pull requests are unwelcome. The engineering here is roughly 20% of the work. The other 80% is whether the directory is accurate, and accuracy comes from people who were on that block last night — not from anyone who can write TypeScript.

If you do outreach or patrol, and you know which listing is wrong, that is the most valuable contribution available.


The one rule that never bends

Never record anything about the people being served. No names, no descriptions, no locations of individuals, no medical detail, no photographs — not in a field, not in the notes column, not in a commit message.

This directory describes services, never recipients. There is no field for it because there must never be one. A contribution containing any of it will be rejected, not edited.

Correcting or adding an entry

Entries live in data/regions/<region>/resources.csv. Edit the row, open a pull request.

Before you do, run:

cd web && npm install && npm run check:data

It reports two things and the difference matters. Errors mean the data is invalid. Warnings mean it is legal but a human should look — because the dangerous entry is never the malformed one, it is the plausible one. A guessed intake rule parses perfectly.

What goes in verified_by

A callsign, or anonymous. Never a legal name — not yours, not a staff member's. The checker warns on anything containing a space.

What method means

It decides how much the entry is trusted, so it has to be honest:

in_person You were there
staff_confirmed Staff told you directly
phone You called
secondhand Someone told you
website You read it on their site

website and secondhand mark the entry as seeded — the site renders those visibly differently, because low-confidence data that looks authoritative is more dangerous than no data at all.

Blank is not "no restriction"

A blank field renders as unknown. That is deliberate and it is load-bearing.

Leaving pets blank says nobody has established whether they take dogs. Writing no says they refuse dogs. Someone sleeping outside with a dog will act on the difference. If you do not know, leave it blank.

The same applies to reports_to, where it matters most: no_one means the service does not pass information to police or immigration. Blank means nobody asked. Those are not the same fact, and the second one is not a reason to guess the first.

Seeding a region that has nothing

Seed structural facts. Never invent an intake rule.

Name, address, type, phone and published hours are public and checkable — take them from official listings at method: website, and the site will present them as unverified.

The intake fields are different. sobriety, pets, id_required, referral_required, sex_offender_ok, reports_to, curfew, belongings — these are the fields the directory exists for, and official listings omit them precisely because nobody maintains them.

You may record one if the service published it. You may never record one because it seemed likely. Nothing in a CSV can tell those apart, which is why the checker warns on every intake rule recorded at method: website and asks a person to confirm.

A plausible guess looks like data, reads as authoritative, and sends someone somewhere that turns them away at 10pm. That single failure is what the entire schema is shaped to prevent.

Adding a new region

Copy data/regions/_template/ to data/regions/<slug>/ and fill in region.json.

The manifest carries what a row cannot say for itself — country, IANA timezone, languages, and whether anyone has actually checked the data. Details in data/regions/README.md.

Ids are globally unique, so prefix them with your region: berlin-central-shelter. The build fails on a collision.

The taxonomy is not finished, and it shows where it was written

type currently reads: shelter, meal, hygiene, medical, harm reduction, warming, cooling, storage, legal, id_docs, mail, charging, veterinary, youth, dv, detox, daytime.

That list is Anglosphere-shaped. warming and cooling assume a temperate climate with a specific emergency-response model. mail assumes general-delivery services. id_docs assumes a particular documentation regime. Elsewhere the decisive categories may be water points, cash assistance, migration and asylum support, family tracing, or things nobody here has thought of.

sex_offender_ok is the clearest case: a real constraint where a public registry exists, and meaningless — possibly stigmatising — where none does.

Proposing a new type is a welcome contribution, and it needs local knowledge to be worth anything. Open an issue saying what the category is, where it applies, and what someone would use it to decide. We are not going to invent global service categories from an armchair, for the same reason we do not invent intake rules.

What is not accepted from strangers

Playbook and field guidance content — de-escalation, first aid, overdose response, rights. Not because contributors are unwelcome, but because confident wrong guidance in those places gets someone hurt, and reviewing it needs real expertise rather than good intentions. If you have that expertise, open an issue and say so first.

Code

Welcome, but read CLAUDE.md and docs/principles.md first. This project has refused a lot of obvious features on purpose, and the reasoning is written down.

Two things that will be sent back regardless of quality: anything that adds a feed, and anything that adds an analytics or telemetry dependency. Both are refused at the principle level.

cd web && npm run verify    # type-check, data check, build, tests, bundle budget

All of it must pass. The bundle budget is enforced because the device floor — a prepaid Android 8 with 400MB free — is a real target and not an aspiration.

docs/contributing.md