- When reporting information to me, be extremely concise and sacrifice grammar for the sake of concision.
- Never define by absence, not in documentation nor doc-comments.
- Trait objects are STRICTLY FORBIDDEN. Every spelling:
dyn Trait,Box<dyn Trait>,&dyn Trait,Arc<dyn Trait>, and any trait object behind a type alias. Use a generic parameter, animpl Traitposition, or an enum over the known implementors. Never add one. Existing ones are debt to retire, not precedent to follow. Ruled 2026-08-18.- One sanctioned exception:
Arc<dyn ProxyHosting>inzingo-netutils/src/provider.rs. A platform host is implemented outside this workspace and reached across an FFI boundary, so its concrete type is unnameable here. The alternatives were weighed and both cost more than the rule saves: a type parameter reachesAcquirer,Pools, andLightClient, which every consumer names, and a request channel relocates the dynamism behind a queue, invents a host-vanished failure mode, and serialises the boot's concurrent acquisitions unless the host spawns per request. The exception is confined to one field below the seam, and the wallet names only the concreteHostedProvider. Do not relitigate it, and do not read it as licence for a second exception. Ruled 2026-08-18.
- One sanctioned exception:
- The nightly CI run A/B benchmarks dev against the day before with the
online sync benchmark (
makers sync-ab), which drives a realrun-cli --onlinesession so the mixnet boot load is present. Thesync-abjob ofci-nightly.yamlcompares the dev head with the newest first-parent commit older than 24 hours, uploads the report as an artifact, and skips a day that added no commit. A commit carries no benchmark numbers of its own. A regression in the nightly report is bisected over that day's commits with the same tool, and the fix carries the two numbers. Ruled 2026-08-18 as a per-commit rule, moved to the nightly run 2026-10-07. - The communication model is moving to events published from zingolib. The wallet crate takes a callback interface from the consumer at startup and calls it with typed events (sync status, balance, new transactions, errors), so the consumer's timed polls become a subscription. Issue #2824 tracks the arc and issue #2820 specifies the events. Every review and every implementation MUST weigh its change against that model: do not add a poll, a side channel around the lightclient lock, or a string-shaped status that an event will replace, and shape new state so an event can carry it. Ruled 2026-10-02.
- Code duplication is FORBIDDEN. The convention is maximally DRY code
with pure functions first: extract the shared logic into one pure function
and call it from every site, keep effects at the edges, and collapse any
duplication you find in code you are already changing. A second copy of a
helper, a branch, or a constant is a defect, in review and in
implementation alike. Ruled 2026-10-02. CI enforces it through the
workbench binary
dupes-gate(tools/workbench/src/dupes_gate.rs), which runs cargo-dupes with the settings that module pins. The gate rejects an exact group outside.dupes-ignore.toml, a near group count other than the ceilingMAX_NEARin that module, and a baseline entry whose group is gone. Never add an entry for a new group, with two exceptions: a trait-delegation pair, where one type implements two traits whose methods delegate identically and cannot share an impl, takes an entry whose reason names the pair; and an FFI export pair, where the same call runs under the write lock storing its result and under the read lock previewing it (send/preview_send), takes one entry for the functions and one for their lock closures, each naming the pair. The gate erases identifiers, so it cannot tell the two apart (ruled 2026-10-28, zingolib#3104). Otherwise remove the duplication, then rundupes-gate cleanupand commit the result. When a near group disappears, lowerMAX_NEARto the new count; the ceiling only moves down. No config file holds the settings, so run cargo-dupes through the binary:cargo run --manifest-path tools/workbench/Cargo.toml --bin dupes-gate -- <cargo-dupes arguments>.
Goal: produce prose and code that reads as if written by a specific, competent human, not by a model. The point is naturalness and fit, not looking exhaustive or safe. When in doubt, commit to a choice and keep it short.
- No em dashes. Use commas, parentheses, or separate sentences.
- No semicolons. Split into two sentences.
- Don't over-clarify with parentheticals. Cut the aside or fold it into the sentence.
- The antithesis flip: "not X, but Y", "isn't just X, it's Y", "not only X but also Y". State the claim directly.
- Defaulting to groups of three (adjectives, clauses, list items). Vary the count.
- "From X to Y" fake-comprehensive sweeps.
- "Whether you're X or Y" catch-all wrap-ups.
- Forced analogies ("think of it like a...").
- No throat-clearing: "It's important to note", "It's worth noting", restating the question before answering.
- No grandiose closers or zoom-outs: "In conclusion", "Ultimately", "At the end of the day", "in an ever-evolving world". Stop when the point is made.
- Don't chain connectives: "Moreover", "Furthermore", "Additionally", "That said".
- No sycophancy: "Great question", "You're absolutely right".
- Commit to a position. No false balance or manufactured symmetry between unequal options.
- Assert plainly. Cut reflexive hedging and over-qualification.
- delve, tapestry, realm, landscape, navigate/navigating, leverage, robust, seamless, crucial, vital, pivotal, testament, boasts, nestled, foster, harness, unlock, elevate, embark, showcase, underscore, spearhead, treasure trove, game-changer, cheap, liveness, gap, shape, correctness, alive, honest, simple, probe, contact, stay.
- Don't bold the lead phrase of every bullet.
- Don't bullet what should be prose.
- No headers on two-sentence sections.
- No emoji as section markers.
- Vary sentence length deliberately.
- Don't drop the definite or indefinite article before a noun to sound terse. Write "the spec was written", not "spec is written"; "the parser reads the manifest", not "parser reads manifest". This zero-article, telegraphic register is a strong machine-generated tell and often reads like translated copy.
- Watch the related tense slip: the clipped present where the past belongs ("spec is written" for "the spec was written", "add handler" for "we added a handler"). That is commit-message and changelog phrasing leaking into prose. Use natural past tense for things that happened.
- The exception is genuine fragment formats (bullet labels, short table cells) where an article would just be noise. Everywhere else, use full grammatical sentences with their articles intact.
- Describe, don't sell. Use a neutral, declarative register. Avoid the promotional cadence of landing-page and ad copy.
- Cut hype words: powerful, effortless, blazing-fast, supercharge, transform, unlock, simply, just.
- Don't pitch benefits at the reader ("you'll love how fast it is", "say goodbye to X", "no more Y"). State what the thing does and let it stand.
- Avoid the staccato rhythm of short fragments stacked for impact, exclamatory energy, and calls to action. That cadence is built to persuade, not to inform.
- Comment why, not what. No line-by-line narration of obvious operations.
- No tutorial narration ("Now we...", "Step 1:", "First, let's...") and no banner comments (
// ===== HELPERS =====). - No docstrings that just restate the signature.
- Names: concise and domain-specific. Avoid generic placeholders (
data,result,output,item,value,temp,handleData, a helper namedhelper) and avoid over-long descriptive names where a short one is idiomatic. - No completeness theater: no unrequested demo/usage blocks, no logs narrating execution ("Starting...", "Done!"), no emoji in output, no unprompted complexity analysis in comments.
- Never use magic numbers, anywhere. Every bare literal gets a name: prefer the most private binding the context allows, and a named constant where privacy can't confine it. Derive new constants from existing named constants rather than repeating a number.
- Don't add guards for conditions that can't occur. Don't wrap non-throwing code in try/catch. Don't swallow-and-log errors; let them propagate.
- Match the surrounding codebase's idioms and conventions over textbook-uniform formatting.
A doc-comment holds only a succinct bulleted list of the item's side effects.
The rule covers every doc-comment form: Rust /// and //!, and file- or
module-level blocks.
- Each bullet names one side effect: a write to disk, a network call, a lock taken, a mutation of shared state, a spawned task, a panic.
- A doc-comment holds no summary sentence, no parameter or return description, and no restatement of the signature.
- A doc-comment references no ADR, issue, or other document.
- An item with no side effect carries no doc-comment.
- Don't reach for
.clone()to satisfy the borrow checker. Borrow or restructure first. - Use
?for propagation..unwrap()is disallowed: never write one, and replace every one a change touches. Avoid.expect()outside tests and throwaway code. - Every
.unwrap()or.expect()a change touches is considered, never carried along. Touching means adding, moving, rewriting, or keeping one on a line the diff alters, in test code as much as in shipped code. For each one, present the options for explicitErrhandling and their trade-offs: propagate with?into a typed error, restructure so the failure is impossible by construction, match the failure and define the fallback, or keep an.expect()whose message states the invariant that makes it unreachable. Every.expect()MUST state its invariant as its argument, the fact that makes the failure unreachable, never a description of the failure. An.expect()with an explicit invariant is acceptable in test code. A touched 'expect' that stays does so by the reviewer's choice, with its invariant written where the reviewer sees it. This applies in implementation and in review alike. - Use tail expressions. No explicit
returnon the final line. - Don't annotate types the compiler infers (
let x: i32 = 5;). - Prefer
if letand combinators (map,and_then,ok_or,unwrap_or_else) over verbosematchwhen clearer. - Prefer iterator chains over manual
for+pushwhere idiomatic. - Use
&strwhere a borrow suffices instead ofString. - Run
makers feature-sweepbefore you push. An ordinarycargo checkcompiles one feature combination, so a rename or a signature change can leave code behind a#[cfg(feature = ...)]gate broken and still look green; the sweep checks the touched crates in every combination, as CI does.
- No
any. Type precisely. Don't annotate what TS already infers. Don't useasto silence the checker. - Prefer union/literal types over enums where idiomatic. Prefer named exports.
- Don't use
React.FC. Type props directly. - Don't wrap everything in
useMemo/useCallback. Use them only for a real identity or perf need. - Don't reach for
useEffectto compute derived state. Derive it during render. - No
console.lognarrating execution. - Don't over-componentize trivial markup, and don't prop-drill where composition or context fits.
- Use semantic elements. Avoid div soup.
- Keep class lists purposeful and legible. Don't pad with utilities that don't do anything.
- Before finishing a task, scan what you wrote against this file. Focus on the high-signal tells, not a full re-audit: antithesis flips and narrating comments in prose,
.clone()/.unwrap()spam and explicit trailingreturnin Rust,useEffectfor derived state andanyin TS. - Verify your new output fits these rules and the surrounding code's style. The question is "does what I added fit", not "does this whole file now obey CLAUDE.md".
- Don't reformat, re-comment, or otherwise "correct" existing code you were only asked to touch lightly. Match what's there. Keep diffs scoped to the task.
- No heredocs (<< EOF, << 'EOF') in bash or other shell scripts. They're hard to read, break on escaping, and bury content that should be its own file. Use a real file, a templating step, or printf with explicit lines instead.
Each crate's CHANGELOG.md keeps a Keep a Changelog [Unreleased] section.
An entry there describes the change a consumer sees between the crate's last
release and the next one, never a step between two unreleased states. When a
later change on dev supersedes, reverts or reshapes an unreleased entry,
rewrite that entry to the net result rather than appending a second one; an
entry whose subject arrived and left again within the same unreleased span is
removed. The release itself then needs no editing beyond the version
heading. bindings/CHANGELOG.md is generated from the audited crates'
changelogs per publication and is never edited by hand.
Before adding an entry, do this, and a reviewer checks it:
- Read the crate's whole
[Unreleased]section. If the subject of the change already has an entry (the same command, flag, function, type, constant or dependency), edit that entry; a second entry about one subject is the fault this rule exists to prevent. - Name the last release: the heading under
[Unreleased], whose tag is<crate>_v<version>(zingolib_v7.0.0,pepper-sync_v0.6.0). State the change against that tag's code and behaviour, whichgit grep <pattern> <tag> -- <crate>answers; "where it used to" and "no longer" refer to the release, never to an earlier commit ondev. - A subject that did not exist at the release is an addition, however many
times
devreshaped it since; describe its final shape underAdded, not its history underChanged. A subject the release had and the change removes goes underRemoved, once. - A pull request that changes behaviour an unreleased entry records rewrites that entry, in the same pull request, even when the entry belongs to another author's change.
Issues live in GitHub Issues on zingolabs/zingolib, managed via the gh
CLI. Linear holds the Zingo Mobile team's issues only. Pull requests are
not a triage surface. See docs/agents/issue-tracker.md.
Five canonical triage roles are GitHub labels (needs-triage, needs-info,
ready-for-agent, ready-for-human, wontfix), with wontfix also closing
the issue as not planned. See docs/agents/triage-labels.md.
Single-context: one CONTEXT.md at the repo root. Decision records sit
under docs/adr/zingolib/. See docs/agents/domain.md.
Ready-for-implementation designs live beside this file's other agent
docs. Current: docs/agents/net-diag-design.md, the shared network
failure taxonomy for the mixnet-covered operations (target branch
nym_mobile_adoption, PR #2527).