Every rule in this toolkit was paid for by a real project hitting a real wall. If your project hit one — a bug, a workaround, a pattern that saved a day, a toolkit script you had to patch locally — we want it, in whatever shape you have it. The bar for getting something in the door is deliberately on the floor: it happened and you wrote it down. Review and polish are the maintainers' job, not the contributor's.
Why so eager: the same graph-sweep.sh bug was found and patched locally in two different
projects, weeks apart, because upstreaming felt like a chore. A contribution that sits in
your project helps one project; the moment it lands here it travels to all of them on the next
git pull.
Drop a file in contrib/inbox/ and open a PR. Copy contrib/inbox/TEMPLATE.md, fill in the
four front-matter lines and paste what you have — a bug-log entry, a diff, three sentences,
verbatim terminal output. No quality bar applies: inbox files are explicitly unreviewed and
unreleased — nothing loads them, no skill routes to them, so a wrong entry cannot hurt any
consuming project. Triage promotes an item into skills/, bug-logs/ or bin/ (that is where
review and field-proofing happen) and deletes the inbox file in the same commit. The inbox is a
queue, never an archive.
From a project wired to this toolkit:
<toolkit>/bin/harvest-learnings.sh <project-root>
It scans the project for what evaporates otherwise — bug-log entries the toolkit's log doesn't
have, promotion tables in PROJECT.md / CLAUDE.local.md, local patches to installed toolkit
scripts — and writes ready-to-PR inbox files into your toolkit clone. Review them (see the
client-data rule below), commit, PR. Run it at project wrap-up at minimum; the wrap-up
checkpoint asks for it.
If you know the target file, PR straight into skills/, bug-logs/ or bin/. Held to the
full bar: for anything under bin/, project-bin/ or project-tests/, the five field-proof
rules in CLAUDE.md → "Shipping an instrument" apply (captured golden input, both layouts,
both platforms, one cited field run, a producer for every consumed artifact). For skills,
follow README.md → "How to add a new skill" including the routing-table row.
- No client data. No client or vendor names, engagement codenames, person names, internal
hostnames, real local paths, NDA'd strings. Genericize before you commit
(
ClientX,/path/to/project).bin/check-no-client-data.shruns in CI and as a pre-commit hook, but the denylist can't know your client — read your own diff, and, on PRs, the guard's new-words report lists capitalised words your diff introduces — look at every one. - Say where it happened. Every contribution names its field context: which project, what
you observed, verbatim output for bugs. "I think" is fine to send — but label it as a
hypothesis, not a finding (
skills/tool-output-is-not-ground-truth.md).
- CI runs the mechanical gates: every script parses (
bin/check-scripts.sh), routing renders clean (bin/render-routing.sh --check), no macOS-only regressions (bin/check-portability.sh), leak guard (bin/check-no-client-data.sh), and — on PRs — the merge-queue discipline (bin/check-pr-discipline.sh: your CHANGELOG line rides in the same PR, and any new bug number is still free onmaster). - A maintainer (human or a toolkit Claude session) reviews; inbox items get triaged into their
real home, tested, and released by merge to
master— consuming projects pick the change up on their nextgit pull+bin/sync-project.sh. - The merge commit appends a
CHANGELOG.mdline crediting you or your project by name. That line is the record of which projects feed the toolkit.
- Read the whole diff for proper nouns — client, app, engagement, person, hostname. The guard
only knows names it was told (the denylist); the
LEAKGUARD_BASEnew-words report on the PR is the prompt for this step, not the verdict. - Every instrument, example or claim in the PR was executed or field-run — cite where. See
CLAUDE.md→ "Shipping an instrument — field-proof before merge". - A
CHANGELOG.mdline is present and credits the source. - Scoped fixtures for the touched files were run, and named in the PR.
- Squash by default; a merge commit only for a branch whose every commit is already clean — see below.
- A name found in a PR is rewritten by hand, in the PR, with a placeholder (
ClientX) — never auto-replaced. Auto-replace breaks paths, fixtures and credit lines, and the real name stays recoverable from history either way.
One day of five parallel sessions produced: two different bug clusters both numbered BUG-96..100, three PRs with no changelog line, two PRs whose CI never ran because their conflicted state blocked it, and one half-redacted secret that a plain merge would have carried into public history. None of the content was bad — the coordination was. Hence:
- Small and fast. A PR should merge within ~48h; every day it waits, master moves and the conflict/collision surface grows. A conflicted PR gets its base merged in before review — a PR whose CI cannot run has no signal at all, and "green" silence is not green.
- Bug numbers are assigned at merge, not at write. In a PR, head the entry
## BUG-DRAFT-<slug>:; whoever merges gives it the next free number. If you do claim a number, CI now rejects one that is already taken onmaster. - Changelog rides in the same PR. Was always the rule; now CI checks it instead of trusting it.
- Squash-merge is the default. This repo is public: intermediate commits (with their
pre-redaction blobs, wrong paths, half-genericized names) must not reach
master's history. A merge commit is fine for a branch whose every commit is already clean. - A claim ships with its probe. A new skill rule or STOP row names the command that would falsify it (see the BUG-22 retirement for the worked example); a new or changed instrument ships its retest fixture. "It happened to me" gets it in the door — what keeps it true later is that the next person can re-check it in a minute.
- Lines land under
## Unreleased; releases are cut every few days. The maintainer runsbin/cut-release.sh, which dates the section and tags master; a project sees the release it is on inbin/sync-project.sh. The cycle itself is defined at the top ofCHANGELOG.md.
Not every good idea is a PR. When the work is real but the trigger hasn't arrived — a
dependency we don't have, a tool nobody here runs yet, a design that needs a field run
first — it goes in process/roadmap.md as an RM-NN entry rather
than into a draft PR that goes stale or a chat log nobody can find.
An entry needs three things to be worth parking: the assets that already exist (by path), the concrete trigger that unparks it, and a checkable exit bar. Without a trigger it never starts; without an exit bar it never finishes. If you have an idea but none of those, the inbox (Lane 1) is the right place — the roadmap is for work that is designed and waiting, not for work that is imagined.
- An honest negative result filed against the filer's own theory
(a personal POC project,
bug-004-write-count-threshold-corruption). - A deliberate don't-file after searching upstream and finding the bug already logged (pattern-b overlap note → BUG-75/77, now 8 confirmations across 3 projects).
- A per-project promotion queue naming exact toolkit target files
(tfc-tcxgraphpoc
PROJECT.md→ "Toolkit promotion changelog", TD-01…TD-06).
Model your contribution on any of these and it will sail through.